/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "bus-unit-procs.h" #include "hashmap.h" #include "list.h" #include "locale-util.h" #include "macro.h" #include "path-util.h" #include "process-util.h" #include "sort-util.h" #include "string-util.h" #include "terminal-util.h" struct CGroupInfo { char *cgroup_path; bool is_const; /* If false, cgroup_path should be free()'d */ Hashmap *pids; /* PID → process name */ bool done; struct CGroupInfo *parent; LIST_FIELDS(struct CGroupInfo, siblings); LIST_HEAD(struct CGroupInfo, children); size_t n_children; }; static int add_cgroup(Hashmap *cgroups, const char *path, bool is_const, struct CGroupInfo **ret) { struct CGroupInfo *parent = NULL, *cg; int r; assert(cgroups); assert(ret); path = empty_to_root(path); cg = hashmap_get(cgroups, path); if (cg) { *ret = cg; return 0; } if (!empty_or_root(path)) { const char *e, *pp; e = strrchr(path, '/'); if (!e) return -EINVAL; pp = strndupa(path, e - path); r = add_cgroup(cgroups, pp, false, &parent); if (r < 0) return r; } cg = new0(struct CGroupInfo, 1); if (!cg) return -ENOMEM; if (is_const) cg->cgroup_path = (char*) path; else { cg->cgroup_path = strdup(path); if (!cg->cgroup_path) { free(cg); return -ENOMEM; } } cg->is_const = is_const; cg->parent = parent; r = hashmap_put(cgroups, cg->cgroup_path, cg); if (r < 0) { if (!is_const) free(cg->cgroup_path); free(cg); return r; } if (parent) { LIST_PREPEND(siblings, parent->children, cg); parent->n_children++; } *ret = cg; return 1; } static int add_process( Hashmap *cgroups, const char *path, pid_t pid, const char *name) { struct CGroupInfo *cg; int r; assert(cgroups); assert(name); assert(pid > 0); r = add_cgroup(cgroups, path, true, &cg); if (r < 0) return r; r = hashmap_ensure_allocated(&cg->pids, &trivial_hash_ops); if (r < 0) return r; return hashmap_put(cg->pids, PID_TO_PTR(pid), (void*) name); } static void remove_cgroup(Hashmap *cgroups, struct CGroupInfo *cg) { assert(cgroups); assert(cg); while (cg->children) remove_cgroup(cgroups, cg->children); hashmap_remove(cgroups, cg->cgroup_path); if (!cg->is_const) free(cg->cgroup_path); hashmap_free(cg->pids); if (cg->parent) LIST_REMOVE(siblings, cg->parent->children, cg); free(cg); } static int cgroup_info_compare_func(struct CGroupInfo * const *a, struct CGroupInfo * const *b) { return strcmp((*a)->cgroup_path, (*b)->cgroup_path); } static int dump_processes( Hashmap *cgroups, const char *cgroup_path, const char *prefix, unsigned n_columns, OutputFlags flags) { struct CGroupInfo *cg; int r; assert(prefix); cgroup_path = empty_to_root(cgroup_path); cg = hashmap_get(cgroups, cgroup_path); if (!cg) return 0; if (!hashmap_isempty(cg->pids)) { const char *name; size_t n = 0, i; pid_t *pids; void *pidp; int width; /* Order processes by their PID */ pids = newa(pid_t, hashmap_size(cg->pids)); HASHMAP_FOREACH_KEY(name, pidp, cg->pids) pids[n++] = PTR_TO_PID(pidp); assert(n == hashmap_size(cg->pids)); typesafe_qsort(pids, n, pid_compare_func); width = DECIMAL_STR_WIDTH(pids[n-1]); for (i = 0; i < n; i++) { _cleanup_free_ char *e = NULL; const char *special; bool more; name = hashmap_get(cg->pids, PID_TO_PTR(pids[i])); assert(name); if (n_columns != 0) { unsigned k; k = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U); e = ellipsize(name, k, 100); if (e) name = e; } more = i+1 < n || cg->children; special = special_glyph(more ? SPECIAL_GLYPH_TREE_BRANCH : SPECIAL_GLYPH_TREE_RIGHT); fprintf(stdout, "%s%s%*"PID_PRI" %s\n", prefix, special, width, pids[i], name); } } if (cg->children) { struct CGroupInfo **children, *child; size_t n = 0, i; /* Order subcgroups by their name */ children = newa(struct CGroupInfo*, cg->n_children); LIST_FOREACH(siblings, child, cg->children) children[n++] = child; assert(n == cg->n_children); typesafe_qsort(children, n, cgroup_info_compare_func); if (n_columns != 0) n_columns = MAX(LESS_BY(n_columns, 2U), 20U); for (i = 0; i < n; i++) { _cleanup_free_ char *pp = NULL; const char *name, *special; bool more; child = children[i]; name = strrchr(child->cgroup_path, '/'); if (!name) return -EINVAL; name++; more = i+1 < n; special = special_glyph(more ? SPECIAL_GLYPH_TREE_BRANCH : SPECIAL_GLYPH_TREE_RIGHT); fputs(prefix, stdout); fputs(special, stdout); fputs(name, stdout); fputc('\n', stdout); special = special_glyph(more ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE); pp = strjoin(prefix, special); if (!pp) return -ENOMEM; r = dump_processes(cgroups, child->cgroup_path, pp, n_columns, flags); if (r < 0) return r; } } cg->done = true; return 0; } static int dump_extra_processes( Hashmap *cgroups, const char *prefix, unsigned n_columns, OutputFlags flags) { _cleanup_free_ pid_t *pids = NULL; _cleanup_hashmap_free_ Hashmap *names = NULL; struct CGroupInfo *cg; size_t n_allocated = 0, n = 0, k; int width, r; /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as * combined, sorted, linear list. */ HASHMAP_FOREACH(cg, cgroups) { const char *name; void *pidp; if (cg->done) continue; if (hashmap_isempty(cg->pids)) continue; r = hashmap_ensure_allocated(&names, &trivial_hash_ops); if (r < 0) return r; if (!GREEDY_REALLOC(pids, n_allocated, n + hashmap_size(cg->pids))) return -ENOMEM; HASHMAP_FOREACH_KEY(name, pidp, cg->pids) { pids[n++] = PTR_TO_PID(pidp); r = hashmap_put(names, pidp, (void*) name); if (r < 0) return r; } } if (n == 0) return 0; typesafe_qsort(pids, n, pid_compare_func); width = DECIMAL_STR_WIDTH(pids[n-1]); for (k = 0; k < n; k++) { _cleanup_free_ char *e = NULL; const char *name; name = hashmap_get(names, PID_TO_PTR(pids[k])); assert(name); if (n_columns != 0) { unsigned z; z = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U); e = ellipsize(name, z, 100); if (e) name = e; } fprintf(stdout, "%s%s %*" PID_PRI " %s\n", prefix, special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), width, pids[k], name); } return 0; } int unit_show_processes( sd_bus *bus, const char *unit, const char *cgroup_path, const char *prefix, unsigned n_columns, OutputFlags flags, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; Hashmap *cgroups = NULL; struct CGroupInfo *cg; int r; assert(bus); assert(unit); if (flags & OUTPUT_FULL_WIDTH) n_columns = 0; else if (n_columns <= 0) n_columns = columns(); prefix = strempty(prefix); r = sd_bus_call_method( bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "GetUnitProcesses", error, &reply, "s", unit); if (r < 0) return r; cgroups = hashmap_new(&path_hash_ops); if (!cgroups) return -ENOMEM; r = sd_bus_message_enter_container(reply, 'a', "(sus)"); if (r < 0) goto finish; for (;;) { const char *path = NULL, *name = NULL; uint32_t pid; r = sd_bus_message_read(reply, "(sus)", &path, &pid, &name); if (r < 0) goto finish; if (r == 0) break; r = add_process(cgroups, path, pid, name); if (r == -ENOMEM) goto finish; if (r < 0) log_warning_errno(r, "Invalid process description in GetUnitProcesses reply: cgroup=\"%s\" pid="PID_FMT" command=\"%s\", ignoring: %m", path, pid, name); } r = sd_bus_message_exit_container(reply); if (r < 0) goto finish; r = dump_processes(cgroups, cgroup_path, prefix, n_columns, flags); if (r < 0) goto finish; r = dump_extra_processes(cgroups, prefix, n_columns, flags); finish: while ((cg = hashmap_first(cgroups))) remove_cgroup(cgroups, cg); hashmap_free(cgroups); return r; }