cgroup/cpuset: Add cpuset_can_fork() and cpuset_cancel_fork() methods
authorWaiman Long <longman@redhat.com>
Tue, 11 Apr 2023 13:35:59 +0000 (09:35 -0400)
committerTejun Heo <tj@kernel.org>
Wed, 12 Apr 2023 18:23:58 +0000 (08:23 -1000)
In the case of CLONE_INTO_CGROUP, not all cpusets are ready to accept
new tasks. It is too late to check that in cpuset_fork(). So we need
to add the cpuset_can_fork() and cpuset_cancel_fork() methods to
pre-check it before we can allow attachment to a different cpuset.

We also need to set the attach_in_progress flag to alert other code
that a new task is going to be added to the cpuset.

Fixes: ef2c41cf38a7 ("clone3: allow spawning processes into cgroups")
Suggested-by: Michal Koutný <mkoutny@suse.com>
Signed-off-by: Waiman Long <longman@redhat.com>
Cc: stable@vger.kernel.org # v5.7+
Signed-off-by: Tejun Heo <tj@kernel.org>
kernel/cgroup/cpuset.c

index 2ccfae7..166a450 100644 (file)
@@ -2453,6 +2453,20 @@ static int fmeter_getrate(struct fmeter *fmp)
 
 static struct cpuset *cpuset_attach_old_cs;
 
+/*
+ * Check to see if a cpuset can accept a new task
+ * For v1, cpus_allowed and mems_allowed can't be empty.
+ * For v2, effective_cpus can't be empty.
+ * Note that in v1, effective_cpus = cpus_allowed.
+ */
+static int cpuset_can_attach_check(struct cpuset *cs)
+{
+       if (cpumask_empty(cs->effective_cpus) ||
+          (!is_in_v2_mode() && nodes_empty(cs->mems_allowed)))
+               return -ENOSPC;
+       return 0;
+}
+
 /* Called by cgroups to determine if a cpuset is usable; cpuset_rwsem held */
 static int cpuset_can_attach(struct cgroup_taskset *tset)
 {
@@ -2467,16 +2481,9 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
 
        percpu_down_write(&cpuset_rwsem);
 
-       /* allow moving tasks into an empty cpuset if on default hierarchy */
-       ret = -ENOSPC;
-       if (!is_in_v2_mode() &&
-           (cpumask_empty(cs->cpus_allowed) || nodes_empty(cs->mems_allowed)))
-               goto out_unlock;
-
-       /*
-        * Task cannot be moved to a cpuset with empty effective cpus.
-        */
-       if (cpumask_empty(cs->effective_cpus))
+       /* Check to see if task is allowed in the cpuset */
+       ret = cpuset_can_attach_check(cs);
+       if (ret)
                goto out_unlock;
 
        cgroup_taskset_for_each(task, css, tset) {
@@ -2493,7 +2500,6 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
         * changes which zero cpus/mems_allowed.
         */
        cs->attach_in_progress++;
-       ret = 0;
 out_unlock:
        percpu_up_write(&cpuset_rwsem);
        return ret;
@@ -3265,6 +3271,68 @@ static void cpuset_bind(struct cgroup_subsys_state *root_css)
 }
 
 /*
+ * In case the child is cloned into a cpuset different from its parent,
+ * additional checks are done to see if the move is allowed.
+ */
+static int cpuset_can_fork(struct task_struct *task, struct css_set *cset)
+{
+       struct cpuset *cs = css_cs(cset->subsys[cpuset_cgrp_id]);
+       bool same_cs;
+       int ret;
+
+       rcu_read_lock();
+       same_cs = (cs == task_cs(current));
+       rcu_read_unlock();
+
+       if (same_cs)
+               return 0;
+
+       lockdep_assert_held(&cgroup_mutex);
+       percpu_down_write(&cpuset_rwsem);
+
+       /* Check to see if task is allowed in the cpuset */
+       ret = cpuset_can_attach_check(cs);
+       if (ret)
+               goto out_unlock;
+
+       ret = task_can_attach(task, cs->effective_cpus);
+       if (ret)
+               goto out_unlock;
+
+       ret = security_task_setscheduler(task);
+       if (ret)
+               goto out_unlock;
+
+       /*
+        * Mark attach is in progress.  This makes validate_change() fail
+        * changes which zero cpus/mems_allowed.
+        */
+       cs->attach_in_progress++;
+out_unlock:
+       percpu_up_write(&cpuset_rwsem);
+       return ret;
+}
+
+static void cpuset_cancel_fork(struct task_struct *task, struct css_set *cset)
+{
+       struct cpuset *cs = css_cs(cset->subsys[cpuset_cgrp_id]);
+       bool same_cs;
+
+       rcu_read_lock();
+       same_cs = (cs == task_cs(current));
+       rcu_read_unlock();
+
+       if (same_cs)
+               return;
+
+       percpu_down_write(&cpuset_rwsem);
+       cs->attach_in_progress--;
+       if (!cs->attach_in_progress)
+               wake_up(&cpuset_attach_wq);
+       percpu_up_write(&cpuset_rwsem);
+}
+
+/*
  * Make sure the new task conform to the current state of its parent,
  * which could have been changed by cpuset just after it inherits the
  * state from the parent and before it sits on the cgroup's task list.
@@ -3292,6 +3360,11 @@ static void cpuset_fork(struct task_struct *task)
        percpu_down_write(&cpuset_rwsem);
        guarantee_online_mems(cs, &cpuset_attach_nodemask_to);
        cpuset_attach_task(cs, task);
+
+       cs->attach_in_progress--;
+       if (!cs->attach_in_progress)
+               wake_up(&cpuset_attach_wq);
+
        percpu_up_write(&cpuset_rwsem);
 }
 
@@ -3305,6 +3378,8 @@ struct cgroup_subsys cpuset_cgrp_subsys = {
        .attach         = cpuset_attach,
        .post_attach    = cpuset_post_attach,
        .bind           = cpuset_bind,
+       .can_fork       = cpuset_can_fork,
+       .cancel_fork    = cpuset_cancel_fork,
        .fork           = cpuset_fork,
        .legacy_cftypes = legacy_files,
        .dfl_cftypes    = dfl_files,