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);