From dd36116a9763c80daedd03d265308d2f595ce0b8 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 15 Dec 2025 16:54:30 +0100 Subject: [PATCH] Add housekeeping functions to clean up unused cgroups This should not be needed, but for some reason we don't get events when early processes exit, so we end up with lingering cgroups. Signed-off-by: Joachim Wiberg --- src/cgroup.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/cgroup.h | 2 ++ src/sm.c | 9 ++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/cgroup.c b/src/cgroup.c index 9a1803ee..ccb44a77 100644 --- a/src/cgroup.c +++ b/src/cgroup.c @@ -21,6 +21,7 @@ * THE SOFTWARE. */ +#include #include #include #include @@ -673,6 +674,79 @@ int cgroup_add(char *name, char *cfg, int is_protected) return 0; } +/* + * Walk a cgroup directory and remove all empty subdirectories recursively. + */ +static void cgroup_prune_recursive(const char *path) +{ + struct dirent *d; + DIR *dir; + + dir = opendir(path); + if (!dir) + return; + + while ((d = readdir(dir)) != NULL) { + char subpath[PATH_MAX]; + + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + if (d->d_type != DT_DIR) + continue; + + snprintf(subpath, sizeof(subpath), "%s/%s", path, d->d_name); + cgroup_prune_recursive(subpath); + } + closedir(dir); + + /* + * Try to remove this directory. If it still has children or processes, + * rmdir() will fail with ENOTEMPTY/EBUSY - that's fine. + */ + if (rmdir(path) && errno != ENOTEMPTY && errno != EBUSY) + warn("Failed to prune %s", path); +} + +/* + * Housekeeping: prune all empty cgroup subdirectories. + * Called after runlevel transitions to clean up finished run/task cgroups. + */ +void cgroup_prune(void) +{ + struct dirent *entry; + struct cg *cg; + char path[256]; + DIR *dir; + + if (!avail) + return; + + dbg("Pruning empty cgroup directories..."); + + /* Walk through each top-level cgroup (init, system, user) */ + TAILQ_FOREACH(cg, &cgroups, link) { + snprintf(path, sizeof(path), FINIT_CGPATH "/%s", cg->name); + + dir = opendir(path); + if (!dir) + continue; + + /* Check each subdirectory */ + while ((entry = readdir(dir)) != NULL) { + char subpath[512]; + + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + if (entry->d_type != DT_DIR) + continue; + + snprintf(subpath, sizeof(subpath), "%s/%s", path, entry->d_name); + cgroup_prune_recursive(subpath); + } + closedir(dir); + } +} + /* * Remove inactive top-level cgroup */ @@ -696,7 +770,8 @@ int cgroup_del(char *dir) } if (rmdir(dir) && errno != ENOENT) { - dbg("Failed removing %s: %s", dir, strerror(errno)); + if (errno != EBUSY) + warn("Failed rmdir(%s): %s", dir, strerror(errno)); return -1; } diff --git a/src/cgroup.h b/src/cgroup.h index 58d2f3bd..7c792bde 100644 --- a/src/cgroup.h +++ b/src/cgroup.h @@ -56,4 +56,6 @@ int cgroup_watch (const char *group, const char *name); int cgroup_move_pid(const char *group, const char *name, int pid, int delegate); int cgroup_move_svc(svc_t *svc); +void cgroup_prune (void); + #endif /* FINIT_CGROUP_H_ */ diff --git a/src/sm.c b/src/sm.c index 079a8673..00b53053 100644 --- a/src/sm.c +++ b/src/sm.c @@ -29,6 +29,7 @@ #include #include "finit.h" +#include "cgroup.h" #include "cond.h" #include "conf.h" #include "log.h" @@ -191,6 +192,10 @@ static void sm_check_bootstrap(void *work) dbg("Clean up all bootstrap-only tasks/services ..."); svc_prune_bootstrap(); + /* Clean up empty cgroups from bootstrap tasks (e.g., udevadm, coldplug) */ + dbg("Pruning empty cgroups from bootstrap ..."); + cgroup_prune(); + /* All services/tasks/etc. in configure runlevel have started */ dbg("Running svc up hooks ..."); plugin_run_hooks(HOOK_SVC_UP); @@ -402,6 +407,10 @@ restart: dbg("All services have been stopped, calling runlevel change hooks ..."); plugin_run_hooks(HOOK_RUNLEVEL_CHANGE); /* Reconfigure HW/VLANs/etc here */ + /* Clean up empty cgroups from previous runlevel */ + dbg("Pruning empty cgroups from previous runlevel ..."); + cgroup_prune(); + dbg("Starting services new to this runlevel ..."); sm.in_reload = 0; service_step_all(SVC_TYPE_ANY);