summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-09-14 16:07:40 +0100
committerMark Brown <broonie@kernel.org>2026-09-14 16:07:40 +0100
commitf17c9807984ca28568f23d42e8c0c70e0ba54482 (patch)
tree2b02516826acfaa05fdce1f9097635919fb276c9 /kernel
parentaec0dc241ed65208c3c93147565ffac5d15514d7 (diff)
parente8f69fe59a104b4a97a3365dbb43c72996c2acde (diff)
downloadlinux-next-f17c9807984ca28568f23d42e8c0c70e0ba54482.tar.gz
linux-next-f17c9807984ca28568f23d42e8c0c70e0ba54482.zip
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git
Diffstat (limited to 'kernel')
-rw-r--r--kernel/cgroup/cgroup-internal.h2
-rw-r--r--kernel/cgroup/cgroup-v1.c16
-rw-r--r--kernel/cgroup/cgroup.c121
-rw-r--r--kernel/cgroup/debug.c10
-rw-r--r--kernel/cgroup/dmem.c4
-rw-r--r--kernel/cgroup/namespace.c2
6 files changed, 91 insertions, 64 deletions
diff --git a/kernel/cgroup/cgroup-internal.h b/kernel/cgroup/cgroup-internal.h
index 7c367c8d0cbe..233fb8b45d2c 100644
--- a/kernel/cgroup/cgroup-internal.h
+++ b/kernel/cgroup/cgroup-internal.h
@@ -254,7 +254,7 @@ void cgroup_procs_write_finish(struct task_struct *task,
enum cgroup_attach_lock_mode lock_mode)
__releases(&cgroup_threadgroup_rwsem);
-void cgroup_lock_and_drain_offline(struct cgroup *cgrp);
+int cgroup_lock_and_drain_offline(struct cgroup *cgrp);
int cgroup_mkdir(struct kernfs_node *parent_kn, const char *name, umode_t mode);
int cgroup_rmdir(struct kernfs_node *kn);
diff --git a/kernel/cgroup/cgroup-v1.c b/kernel/cgroup/cgroup-v1.c
index a4337c9b5287..167bf6555a49 100644
--- a/kernel/cgroup/cgroup-v1.c
+++ b/kernel/cgroup/cgroup-v1.c
@@ -506,8 +506,8 @@ static ssize_t __cgroup1_procs_write(struct kernfs_open_file *of,
enum cgroup_attach_lock_mode lock_mode;
cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!cgrp)
- return -ENODEV;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
task = cgroup_procs_write_start(buf, threadgroup, &lock_mode);
ret = PTR_ERR_OR_ZERO(task);
@@ -569,8 +569,8 @@ static ssize_t cgroup_release_agent_write(struct kernfs_open_file *of,
return -EPERM;
cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!cgrp)
- return -ENODEV;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
spin_lock(&release_agent_path_lock);
strscpy(cgrp->root->release_agent_path, strstrip(buf),
sizeof(cgrp->root->release_agent_path));
@@ -1097,7 +1097,9 @@ int cgroup1_reconfigure(struct fs_context *fc)
int ret = 0;
u32 added_mask, removed_mask;
- cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp);
+ ret = cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp);
+ if (unlikely(ret))
+ return ret;
/* See what subsystems are wanted */
ret = check_cgroupfs_options(fc);
@@ -1262,7 +1264,9 @@ int cgroup1_get_tree(struct fs_context *fc)
if (!ns_capable(ctx->ns->user_ns, CAP_SYS_ADMIN))
return -EPERM;
- cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp);
+ ret = cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp);
+ if (unlikely(ret))
+ return ret;
ret = cgroup1_root_to_use(fc);
if (!ret && !percpu_ref_tryget_live(&ctx->root->cgrp.self.refcnt))
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index 2d532bf2c0c7..16c0362330ac 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -1379,7 +1379,10 @@ static void cgroup_destroy_root(struct cgroup_root *root)
trace_cgroup_destroy_root(root);
- cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp);
+ /* runs off a workqueue, no signal can interrupt the drain */
+ ret = cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp);
+ if (WARN_ON_ONCE(ret))
+ cgroup_lock();
BUG_ON(atomic_read(&root->nr_cgrps));
BUG_ON(!list_empty(&cgrp->self.children));
@@ -1685,9 +1688,10 @@ void cgroup_kn_unlock(struct kernfs_node *kn)
* This helper is to be used by a cgroup kernfs method currently servicing
* @kn. It breaks the active protection, performs cgroup locking and
* verifies that the associated cgroup is alive. Returns the cgroup if
- * alive; otherwise, %NULL. A successful return should be undone by a
- * matching cgroup_kn_unlock() invocation. If @drain_offline is %true, the
- * cgroup is drained of offlining csses before return.
+ * alive; otherwise, an ERR_PTR value. A successful return should be undone by
+ * a matching cgroup_kn_unlock() invocation. If @drain_offline is %true, the
+ * cgroup is drained of offlining csses before return, and an interrupted drain
+ * fails with -ERESTARTSYS.
*
* Any cgroup kernfs method implementation which requires locking the
* associated cgroup should use this helper. It avoids nesting cgroup
@@ -1697,6 +1701,7 @@ void cgroup_kn_unlock(struct kernfs_node *kn)
struct cgroup *cgroup_kn_lock_live(struct kernfs_node *kn, bool drain_offline)
{
struct cgroup *cgrp;
+ int ret;
if (kernfs_type(kn) == KERNFS_DIR)
cgrp = kn->priv;
@@ -1710,19 +1715,25 @@ struct cgroup *cgroup_kn_lock_live(struct kernfs_node *kn, bool drain_offline)
* break the active_ref protection.
*/
if (!cgroup_tryget(cgrp))
- return NULL;
+ return ERR_PTR(-ENODEV);
kernfs_break_active_protection(kn);
- if (drain_offline)
- cgroup_lock_and_drain_offline(cgrp);
- else
+ if (drain_offline) {
+ ret = cgroup_lock_and_drain_offline(cgrp);
+ if (unlikely(ret)) {
+ kernfs_unbreak_active_protection(kn);
+ cgroup_put(cgrp);
+ return ERR_PTR(ret);
+ }
+ } else {
cgroup_lock();
+ }
if (!cgroup_is_dead(cgrp))
return cgrp;
cgroup_kn_unlock(kn);
- return NULL;
+ return ERR_PTR(-ENODEV);
}
static void cgroup_rm_file(struct cgroup *cgrp, const struct cftype *cft)
@@ -3320,16 +3331,20 @@ out_finish:
* @cgrp: root of the target subtree
*
* Because css offlining is asynchronous, userland may try to re-enable a
- * controller while the previous css is still around. This function grabs
- * cgroup_mutex and drains the previous css instances of @cgrp's subtree.
+ * controller while the previous css is still around. This function grabs
+ * cgroup_mutex and waits until no css in @cgrp's subtree is dying. A dying css
+ * offlines only after every task that still pins it has finished exiting, which
+ * can take arbitrarily long, so the wait is interruptible.
+ *
+ * Returns 0 with cgroup_mutex held once the subtree is drained, or -ERESTARTSYS
+ * without it if interrupted by a signal.
*/
-void cgroup_lock_and_drain_offline(struct cgroup *cgrp)
- __acquires(&cgroup_mutex)
+int cgroup_lock_and_drain_offline(struct cgroup *cgrp)
{
struct cgroup *dsct;
struct cgroup_subsys_state *d_css;
struct cgroup_subsys *ss;
- int ssid;
+ int ssid, ret;
restart:
cgroup_lock();
@@ -3343,17 +3358,20 @@ restart:
continue;
cgroup_get_live(dsct);
- prepare_to_wait(&dsct->offline_waitq, &wait,
- TASK_UNINTERRUPTIBLE);
-
+ ret = prepare_to_wait_event(&dsct->offline_waitq, &wait,
+ TASK_INTERRUPTIBLE);
cgroup_unlock();
- schedule();
+ if (!ret)
+ schedule();
finish_wait(&dsct->offline_waitq, &wait);
-
cgroup_put(dsct);
+ if (unlikely(ret))
+ return ret;
goto restart;
}
}
+
+ return 0;
}
/**
@@ -3650,8 +3668,8 @@ static ssize_t cgroup_subtree_control_write(struct kernfs_open_file *of,
}
cgrp = cgroup_kn_lock_live(of->kn, true);
- if (!cgrp)
- return -ENODEV;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
for_each_subsys(ss, ssid) {
if (enable & (1 << ssid)) {
@@ -3790,8 +3808,8 @@ static ssize_t cgroup_type_write(struct kernfs_open_file *of, char *buf,
/* drain dying csses before we re-apply (threaded) subtree control */
cgrp = cgroup_kn_lock_live(of->kn, true);
- if (!cgrp)
- return -ENOENT;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
/* threaded can only be enabled */
ret = cgroup_enable_threaded(cgrp);
@@ -3833,8 +3851,8 @@ static ssize_t cgroup_max_descendants_write(struct kernfs_open_file *of,
return -ERANGE;
cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!cgrp)
- return -ENOENT;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
WRITE_ONCE(cgrp->max_descendants, descendants);
@@ -3876,8 +3894,8 @@ static ssize_t cgroup_max_depth_write(struct kernfs_open_file *of,
return -ERANGE;
cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!cgrp)
- return -ENOENT;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
WRITE_ONCE(cgrp->max_depth, depth);
@@ -4075,8 +4093,8 @@ static ssize_t pressure_write(struct kernfs_open_file *of, char *buf,
ssize_t ret = 0;
cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!cgrp)
- return -ENODEV;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
ctx = of->priv;
if (!ctx) {
@@ -4192,8 +4210,8 @@ static ssize_t cgroup_pressure_write(struct kernfs_open_file *of,
return -ERANGE;
cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!cgrp)
- return -ENOENT;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
psi = cgroup_psi(cgrp);
if (psi->enabled != enable) {
@@ -4268,8 +4286,8 @@ static ssize_t cgroup_freeze_write(struct kernfs_open_file *of,
return -ERANGE;
cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!cgrp)
- return -ENOENT;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
cgroup_freeze(cgrp, freeze);
@@ -4330,8 +4348,8 @@ static ssize_t cgroup_kill_write(struct kernfs_open_file *of, char *buf,
return -ERANGE;
cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!cgrp)
- return -ENOENT;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
/*
* Killing is a process directed operation, i.e. the whole thread-group
@@ -5485,8 +5503,8 @@ static ssize_t __cgroup_procs_write(struct kernfs_open_file *of, char *buf,
enum cgroup_attach_lock_mode lock_mode;
dst_cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!dst_cgrp)
- return -ENODEV;
+ if (IS_ERR(dst_cgrp))
+ return PTR_ERR(dst_cgrp);
task = cgroup_procs_write_start(buf, threadgroup, &lock_mode);
ret = PTR_ERR_OR_ZERO(task);
@@ -6120,8 +6138,8 @@ int cgroup_mkdir(struct kernfs_node *parent_kn, const char *name, umode_t mode)
return -EINVAL;
parent = cgroup_kn_lock_live(parent_kn, false);
- if (!parent)
- return -ENODEV;
+ if (IS_ERR(parent))
+ return PTR_ERR(parent);
if (!cgroup_check_hierarchy_limits(parent)) {
ret = -EAGAIN;
@@ -6397,7 +6415,7 @@ int cgroup_rmdir(struct kernfs_node *kn)
int ret = 0;
cgrp = cgroup_kn_lock_live(kn, false);
- if (!cgrp)
+ if (IS_ERR(cgrp))
return 0;
ret = cgroup_destroy_locked(cgrp);
@@ -7194,15 +7212,19 @@ static void do_cgroup_task_dead(struct task_struct *tsk)
* the cgroup and task_struct can be pinned indefinitely. Bounce through lazy
* irq_work to allow batching while ensuring timely completion.
*/
-static DEFINE_PER_CPU(struct llist_head, cgrp_dead_tasks);
-static DEFINE_PER_CPU(struct irq_work, cgrp_dead_tasks_iwork);
+struct cgroup_dead {
+ struct irq_work iwork;
+ struct llist_head tasks;
+};
+static DEFINE_PER_CPU(struct cgroup_dead, cgroup_dead);
static void cgrp_dead_tasks_iwork_fn(struct irq_work *iwork)
{
+ struct cgroup_dead *cgrp_dead = container_of(iwork, struct cgroup_dead, iwork);
struct llist_node *lnode;
struct task_struct *task, *next;
- lnode = llist_del_all(this_cpu_ptr(&cgrp_dead_tasks));
+ lnode = llist_del_all(&cgrp_dead->tasks);
llist_for_each_entry_safe(task, next, lnode, cg_dead_lnode) {
do_cgroup_task_dead(task);
put_task_struct(task);
@@ -7214,17 +7236,20 @@ static void __init cgroup_rt_init(void)
int cpu;
for_each_possible_cpu(cpu) {
- init_llist_head(per_cpu_ptr(&cgrp_dead_tasks, cpu));
- per_cpu(cgrp_dead_tasks_iwork, cpu) =
- IRQ_WORK_INIT_LAZY(cgrp_dead_tasks_iwork_fn);
+ struct cgroup_dead *cgrp_dead = per_cpu_ptr(&cgroup_dead, cpu);
+
+ init_llist_head(&cgrp_dead->tasks);
+ cgrp_dead->iwork = IRQ_WORK_INIT_LAZY(cgrp_dead_tasks_iwork_fn);
}
}
void cgroup_task_dead(struct task_struct *task)
{
+ struct cgroup_dead *cgrp_dead = this_cpu_ptr(&cgroup_dead);
+
get_task_struct(task);
- llist_add(&task->cg_dead_lnode, this_cpu_ptr(&cgrp_dead_tasks));
- irq_work_queue(this_cpu_ptr(&cgrp_dead_tasks_iwork));
+ llist_add(&task->cg_dead_lnode, &cgrp_dead->tasks);
+ irq_work_queue(&cgrp_dead->iwork);
}
#else /* CONFIG_PREEMPT_RT */
static void __init cgroup_rt_init(void) {}
diff --git a/kernel/cgroup/debug.c b/kernel/cgroup/debug.c
index 883347b87842..96004cd65d09 100644
--- a/kernel/cgroup/debug.c
+++ b/kernel/cgroup/debug.c
@@ -45,7 +45,7 @@ static int current_css_set_read(struct seq_file *seq, void *v)
struct cgroup_subsys_state *css;
int i, refcnt;
- if (!cgroup_kn_lock_live(of->kn, false))
+ if (IS_ERR(cgroup_kn_lock_live(of->kn, false)))
return -ENODEV;
spin_lock_irq(&css_set_lock);
@@ -206,8 +206,8 @@ static int cgroup_subsys_states_read(struct seq_file *seq, void *v)
int i;
cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!cgrp)
- return -ENODEV;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
for_each_subsys(ss, i) {
css = rcu_dereference_check(cgrp->subsys[ss->id], true);
@@ -254,8 +254,8 @@ static int cgroup_masks_read(struct seq_file *seq, void *v)
struct cgroup *cgrp;
cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!cgrp)
- return -ENODEV;
+ if (IS_ERR(cgrp))
+ return PTR_ERR(cgrp);
cgroup_masks_read_one(seq, "subtree_control", cgrp->subtree_control);
cgroup_masks_read_one(seq, "subtree_ss_mask", cgrp->subtree_ss_mask);
diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c
index 47a41646de4b..d49ed5d4a923 100644
--- a/kernel/cgroup/dmem.c
+++ b/kernel/cgroup/dmem.c
@@ -706,8 +706,8 @@ EXPORT_SYMBOL_GPL(dmem_cgroup_uncharge);
*
* When this function fails with -EAGAIN and @ret_limit_pool is non-null, it
* will be set to the pool for which the limit is hit. This can be used for
- * eviction as argument to dmem_cgroup_evict_valuable(). This reference must be freed
- * with @dmem_cgroup_pool_state_put().
+ * eviction as argument to dmem_cgroup_state_evict_valuable(). This reference
+ * must be freed with @dmem_cgroup_pool_state_put().
*
* Return: 0 on success, -EAGAIN on hitting a limit, or a negative errno on failure.
*/
diff --git a/kernel/cgroup/namespace.c b/kernel/cgroup/namespace.c
index ea4ee13936be..76660332ec64 100644
--- a/kernel/cgroup/namespace.c
+++ b/kernel/cgroup/namespace.c
@@ -53,8 +53,6 @@ struct cgroup_namespace *copy_cgroup_ns(u64 flags,
struct ucounts *ucounts;
struct css_set *cset;
- BUG_ON(!old_ns);
-
if (!(flags & CLONE_NEWCGROUP)) {
get_cgroup_ns(old_ns);
return old_ns;