]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core,systemctl: add bus API to retrieve processes of a unit
authorLennart Poettering <lennart@poettering.net>
Wed, 20 Apr 2016 13:28:28 +0000 (15:28 +0200)
committerLennart Poettering <lennart@poettering.net>
Fri, 22 Apr 2016 14:06:20 +0000 (16:06 +0200)
This adds a new GetProcesses() bus call to the Unit object which returns an
array consisting of all PIDs, their process names, as well as their full cgroup
paths. This is then used by "systemctl status" to show the per-unit process
tree.

This has the benefit that the client-side no longer needs to access the
cgroupfs directly to show the process tree of a unit. Instead, it now uses this
new API, which means it also works if -H or -M are used correctly, as the
information from the specific host is used, and not the one from the local
system.

Fixes: #2945
18 files changed:
Makefile.am
src/basic/process-util.c
src/basic/process-util.h
src/core/busname.c
src/core/dbus-manager.c
src/core/dbus-unit.c
src/core/dbus-unit.h
src/core/mount.c
src/core/org.freedesktop.systemd1.conf
src/core/service.c
src/core/socket.c
src/core/swap.c
src/core/unit.c
src/core/unit.h
src/shared/bus-unit-util.c [new file with mode: 0644]
src/shared/bus-unit-util.h [new file with mode: 0644]
src/shared/cgroup-show.c
src/systemctl/systemctl.c

index f2ef83b0c7dcd76c09d28abae6016b797f34f879..b0118fd9a50ac6643e99ff7c74836a31d09ffc1b 100644 (file)
@@ -1037,6 +1037,8 @@ libshared_la_SOURCES = \
        src/shared/machine-pool.h \
        src/shared/resolve-util.c \
        src/shared/resolve-util.h \
+       src/shared/bus-unit-util.c \
+       src/shared/bus-unit-util.h \
        src/shared/tests.h \
        src/shared/tests.c
 
index ae3f6109addbd2b2c71454f3e3b6092452118a93..f2cea0197915a7e9c073bc42eb622755c5d3b925 100644 (file)
@@ -731,6 +731,18 @@ void valgrind_summary_hack(void) {
 #endif
 }
 
+int pid_compare_func(const void *a, const void *b) {
+        const pid_t *p = a, *q = b;
+
+        /* Suitable for usage in qsort() */
+
+        if (*p < *q)
+                return -1;
+        if (*p > *q)
+                return 1;
+        return 0;
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",
index f5d193e76215e2c2dd5b6cc6f92d0ecb5e97c1e4..ffd4bcb0ffdbed10e10bb80d9c30abe52c10f547 100644 (file)
@@ -101,3 +101,5 @@ int sched_policy_from_string(const char *s);
 #define PID_TO_PTR(p) ((void*) ((uintptr_t) p))
 
 void valgrind_summary_hack(void);
+
+int pid_compare_func(const void *a, const void *b);
index bbe61af4f024968b5cc1e405f948f308cabc74b3..f4f433340c7b9f40cee50ee0d67be10df99f8921 100644 (file)
@@ -999,6 +999,14 @@ static bool busname_supported(void) {
         return supported;
 }
 
+static int busname_control_pid(Unit *u) {
+        BusName *n = BUSNAME(u);
+
+        assert(n);
+
+        return n->control_pid;
+}
+
 static const char* const busname_result_table[_BUSNAME_RESULT_MAX] = {
         [BUSNAME_SUCCESS] = "success",
         [BUSNAME_FAILURE_RESOURCES] = "resources",
@@ -1052,6 +1060,8 @@ const UnitVTable busname_vtable = {
 
         .supported = busname_supported,
 
+        .control_pid = busname_control_pid,
+
         .bus_vtable = bus_busname_vtable,
 
         .status_message_formats = {
index 0c86791fe31488b9c68ba83e1d2be0b9fd30917b..d2eb388f7cffa63f66dc132b1812f524e403e983 100644 (file)
@@ -642,6 +642,30 @@ static int method_set_unit_properties(sd_bus_message *message, void *userdata, s
         return bus_unit_method_set_properties(message, u, error);
 }
 
+static int method_get_unit_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        Manager *m = userdata;
+        const char *name;
+        Unit *u;
+        int r;
+
+        assert(message);
+        assert(m);
+
+        r = sd_bus_message_read(message, "s", &name);
+        if (r < 0)
+                return r;
+
+        r = manager_load_unit(m, name, NULL, error, &u);
+        if (r < 0)
+                return r;
+
+        r = bus_unit_check_load_state(u, error);
+        if (r < 0)
+                return r;
+
+        return bus_unit_method_get_processes(message, u, error);
+}
+
 static int transient_unit_from_message(
                 Manager *m,
                 sd_bus_message *message,
@@ -2042,6 +2066,7 @@ const sd_bus_vtable bus_manager_vtable[] = {
         SD_BUS_METHOD("ResetFailedUnit", "s", NULL, method_reset_failed_unit, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("SetUnitProperties", "sba(sv)", NULL, method_set_unit_properties, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("StartTransientUnit", "ssa(sv)a(sa(sv))", "o", method_start_transient_unit, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("GetUnitProcesses", "s", "a(sus)", method_get_unit_processes, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("GetJob", "u", "o", method_get_job, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("CancelJob", "u", NULL, method_cancel_job, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("ClearJobs", NULL, NULL, method_clear_jobs, SD_BUS_VTABLE_UNPRIVILEGED),
index c50726507012b3970f3aee0c5a20fefc2a64b2e2..ed207f15b9787a23feff6c324fec8e4b7b587649 100644 (file)
 #include "cgroup-util.h"
 #include "dbus-unit.h"
 #include "dbus.h"
+#include "fd-util.h"
 #include "locale-util.h"
 #include "log.h"
+#include "process-util.h"
 #include "selinux-access.h"
 #include "signal-util.h"
 #include "special.h"
@@ -841,6 +843,146 @@ static int property_get_cgroup(
         return sd_bus_message_append(reply, "s", t);
 }
 
+static int append_process(sd_bus_message *reply, const char *p, pid_t pid, Set *pids) {
+        _cleanup_free_ char *buf = NULL, *cmdline = NULL;
+        int r;
+
+        assert(reply);
+        assert(pid > 0);
+
+        r = set_put(pids, PID_TO_PTR(pid));
+        if (r == -EEXIST || r == 0)
+                return 0;
+        if (r < 0)
+                return r;
+
+        if (!p) {
+                r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &buf);
+                if (r == -ESRCH)
+                        return 0;
+                if (r < 0)
+                        return r;
+
+                p = buf;
+        }
+
+        (void) get_process_cmdline(pid, 0, true, &cmdline);
+
+        return sd_bus_message_append(reply,
+                                     "(sus)",
+                                     p,
+                                     (uint32_t) pid,
+                                     cmdline);
+}
+
+static int append_cgroup(sd_bus_message *reply, const char *p, Set *pids) {
+        _cleanup_closedir_ DIR *d = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
+
+        assert(reply);
+        assert(p);
+
+        r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, p, &f);
+        if (r == ENOENT)
+                return 0;
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                pid_t pid;
+
+                r = cg_read_pid(f, &pid);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                if (is_kernel_thread(pid) > 0)
+                        continue;
+
+                r = append_process(reply, p, pid, pids);
+                if (r < 0)
+                        return r;
+        }
+
+        r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, p, &d);
+        if (r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                _cleanup_free_ char *g = NULL, *j = NULL;
+
+                r = cg_read_subgroup(d, &g);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                j = strjoin(p, "/", g, NULL);
+                if (!j)
+                        return -ENOMEM;
+
+                r = append_cgroup(reply, j, pids);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        _cleanup_(set_freep) Set *pids = NULL;
+        _cleanup_free_ char *p = NULL;
+        Unit *u = userdata;
+        pid_t pid;
+        int r;
+
+        assert(message);
+
+        pids = set_new(NULL);
+        if (!pids)
+                return -ENOMEM;
+
+        r = sd_bus_message_new_method_return(message, &reply);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_open_container(reply, 'a', "(sus)");
+        if (r < 0)
+                return r;
+
+        if (u->cgroup_path) {
+                r = append_cgroup(reply, u->cgroup_path, pids);
+                if (r < 0)
+                        return r;
+        }
+
+        /* The main and control pids might live outside of the cgroup, hence fetch them separately */
+        pid = unit_main_pid(u);
+        if (pid > 0) {
+                r = append_process(reply, NULL, pid, pids);
+                if (r < 0)
+                        return r;
+        }
+
+        pid = unit_control_pid(u);
+        if (pid > 0) {
+                r = append_process(reply, NULL, pid, pids);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_close_container(reply);
+        if (r < 0)
+                return r;
+
+        return sd_bus_send(NULL, reply, NULL);
+}
+
 const sd_bus_vtable bus_unit_cgroup_vtable[] = {
         SD_BUS_VTABLE_START(0),
         SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0),
@@ -848,6 +990,7 @@ const sd_bus_vtable bus_unit_cgroup_vtable[] = {
         SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0),
         SD_BUS_PROPERTY("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0),
         SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0),
+        SD_BUS_METHOD("GetProcesses", NULL, "a(sus)", bus_unit_method_get_processes, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_VTABLE_END
 };
 
index 07948b9cd05c9c23ca19b56539d2b7066874d4ce..4db88dbebc17e402792699b75519bcf335d22e33 100644 (file)
@@ -36,5 +36,6 @@ int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus
 int bus_unit_queue_job(sd_bus_message *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible, sd_bus_error *error);
 int bus_unit_set_properties(Unit *u, sd_bus_message *message, UnitSetPropertiesMode mode, bool commit, sd_bus_error *error);
 int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bus_error *error);
 
 int bus_unit_check_load_state(Unit *u, sd_bus_error *error);
index 632c5c824cbbbcf358e081bc551c496faf10d769..188fb0aa40efe726d347fe42efa5e8b731efb4c7 100644 (file)
@@ -1790,6 +1790,14 @@ static int mount_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) {
         return unit_kill_common(u, who, signo, -1, MOUNT(u)->control_pid, error);
 }
 
+static int mount_control_pid(Unit *u) {
+        Mount *m = MOUNT(u);
+
+        assert(m);
+
+        return m->control_pid;
+}
+
 static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = {
         [MOUNT_EXEC_MOUNT] = "ExecMount",
         [MOUNT_EXEC_UNMOUNT] = "ExecUnmount",
@@ -1851,6 +1859,8 @@ const UnitVTable mount_vtable = {
 
         .reset_failed = mount_reset_failed,
 
+        .control_pid = mount_control_pid,
+
         .bus_vtable = bus_mount_vtable,
         .bus_set_property = bus_mount_set_property,
         .bus_commit_properties = bus_mount_commit_properties,
index f78eedbd6e097d961dcdea25a9e9a11f00f04d44..b73250136481fb6325766dcd0f0330ac0ef91f0c 100644 (file)
                        send_interface="org.freedesktop.systemd1.Manager"
                        send_member="GetUnitFileState"/>
 
+                <allow send_destination="org.freedesktop.systemd1"
+                       send_interface="org.freedesktop.systemd1.Manager"
+                       send_member="GetUnitProcesses"/>
+
                 <allow send_destination="org.freedesktop.systemd1"
                        send_interface="org.freedesktop.systemd1.Manager"
                        send_member="ListJobs"/>
index 58084e2f82be5588f9984d608e4ac621862e1c60..b46dd8bcdd32828db865883936a9b4739d5d5999 100644 (file)
@@ -3195,6 +3195,22 @@ static int service_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) {
         return unit_kill_common(u, who, signo, s->main_pid, s->control_pid, error);
 }
 
+static int service_main_pid(Unit *u) {
+        Service *s = SERVICE(u);
+
+        assert(s);
+
+        return s->main_pid;
+}
+
+static int service_control_pid(Unit *u) {
+        Service *s = SERVICE(u);
+
+        assert(s);
+
+        return s->control_pid;
+}
+
 static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
         [SERVICE_RESTART_NO] = "no",
         [SERVICE_RESTART_ON_SUCCESS] = "on-success",
@@ -3303,6 +3319,9 @@ const UnitVTable service_vtable = {
         .notify_cgroup_empty = service_notify_cgroup_empty_event,
         .notify_message = service_notify_message,
 
+        .main_pid = service_main_pid,
+        .control_pid = service_control_pid,
+
         .bus_name_owner_change = service_bus_name_owner_change,
 
         .bus_vtable = bus_service_vtable,
index 65da0e3c5e97c794d415826c3eb49e8ea134a779..a9fff9c2593dab772b849d7bfe25dda7b7635d27 100644 (file)
@@ -2781,6 +2781,14 @@ char *socket_fdname(Socket *s) {
         return UNIT(s)->id;
 }
 
+static int socket_control_pid(Unit *u) {
+        Socket *s = SOCKET(u);
+
+        assert(s);
+
+        return s->control_pid;
+}
+
 static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = {
         [SOCKET_EXEC_START_PRE] = "StartPre",
         [SOCKET_EXEC_START_CHOWN] = "StartChown",
@@ -2846,6 +2854,8 @@ const UnitVTable socket_vtable = {
 
         .reset_failed = socket_reset_failed,
 
+        .control_pid = socket_control_pid,
+
         .bus_vtable = bus_socket_vtable,
         .bus_set_property = bus_socket_set_property,
         .bus_commit_properties = bus_socket_commit_properties,
index c6502eb821123b450feffb7256bd264dd299bf5b..d8802470d262cd8f465671f0d3d435ce909d07e1 100644 (file)
@@ -1426,6 +1426,14 @@ static bool swap_supported(void) {
         return supported;
 }
 
+static int swap_control_pid(Unit *u) {
+        Swap *s = SWAP(u);
+
+        assert(s);
+
+        return s->control_pid;
+}
+
 static const char* const swap_exec_command_table[_SWAP_EXEC_COMMAND_MAX] = {
         [SWAP_EXEC_ACTIVATE] = "ExecActivate",
         [SWAP_EXEC_DEACTIVATE] = "ExecDeactivate",
@@ -1487,6 +1495,8 @@ const UnitVTable swap_vtable = {
 
         .reset_failed = swap_reset_failed,
 
+        .control_pid = swap_control_pid,
+
         .bus_vtable = bus_swap_vtable,
         .bus_set_property = bus_swap_set_property,
         .bus_commit_properties = bus_swap_commit_properties,
index 1f57293a0b0f8d4c0d3874a4bb3b14aaa08a5daa..cb79c7c6b1d7cde10c541d822465ccedbd0a559a 100644 (file)
@@ -3801,3 +3801,21 @@ bool unit_is_pristine(Unit *u) {
                  u->job ||
                  u->merged_into);
 }
+
+pid_t unit_control_pid(Unit *u) {
+        assert(u);
+
+        if (UNIT_VTABLE(u)->control_pid)
+                return UNIT_VTABLE(u)->control_pid(u);
+
+        return 0;
+}
+
+pid_t unit_main_pid(Unit *u) {
+        assert(u);
+
+        if (UNIT_VTABLE(u)->main_pid)
+                return UNIT_VTABLE(u)->main_pid(u);
+
+        return 0;
+}
index cfdac852a556c119096c5409fff8c7e19d933cfe..5909652976a13d114b3a22556c09ad3a10405928 100644 (file)
@@ -390,6 +390,12 @@ struct UnitVTable {
         /* Returns the next timeout of a unit */
         int (*get_timeout)(Unit *u, usec_t *timeout);
 
+        /* Returns the main PID if there is any defined, or 0. */
+        pid_t (*main_pid)(Unit *u);
+
+        /* Returns the main PID if there is any defined, or 0. */
+        pid_t (*control_pid)(Unit *u);
+
         /* This is called for each unit type and should be used to
          * enumerate existing devices and load them. However,
          * everything that is loaded here should still stay in
@@ -601,6 +607,9 @@ bool unit_type_supported(UnitType t);
 
 bool unit_is_pristine(Unit *u);
 
+pid_t unit_control_pid(Unit *u);
+pid_t unit_main_pid(Unit *u);
+
 static inline bool unit_supported(Unit *u) {
         return unit_type_supported(u->type);
 }
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
new file mode 100644 (file)
index 0000000..25ecbd3
--- /dev/null
@@ -0,0 +1,446 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2016 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "alloc-util.h"
+#include "bus-internal.h"
+#include "bus-unit-util.h"
+#include "bus-util.h"
+#include "cgroup-util.h"
+#include "env-util.h"
+#include "escape.h"
+#include "hashmap.h"
+#include "list.h"
+#include "locale-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "rlimit-util.h"
+#include "signal-util.h"
+#include "string-util.h"
+#include "syslog-util.h"
+#include "terminal-util.h"
+#include "utf8.h"
+#include "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 bool IS_ROOT(const char *p) {
+        return isempty(p) || streq(p, "/");
+}
+
+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);
+
+        if (IS_ROOT(path))
+                path = "/";
+
+        cg = hashmap_get(cgroups, path);
+        if (cg) {
+                *ret = cg;
+                return 0;
+        }
+
+        if (!IS_ROOT(path)) {
+                const char *e, *pp;
+
+                e = strrchr(path, '/');
+                if (!e)
+                        return -EINVAL;
+
+                pp = strndupa(path, e - path);
+                if (!pp)
+                        return -ENOMEM;
+
+                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(const void *a, const void *b) {
+        const struct CGroupInfo *x = *(const struct CGroupInfo* const*) a, *y = *(const struct CGroupInfo* const*) b;
+
+        assert(x);
+        assert(y);
+
+        return strcmp(x->cgroup_path, y->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);
+
+        if (IS_ROOT(cgroup_path))
+                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;
+                Iterator j;
+                int width;
+
+                /* Order processes by their PID */
+                pids = newa(pid_t, hashmap_size(cg->pids));
+
+                HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j)
+                        pids[n++] = PTR_TO_PID(pidp);
+
+                assert(n == hashmap_size(cg->pids));
+                qsort_safe(pids, n, sizeof(pid_t), 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 = draw_special_char(more ? DRAW_TREE_BRANCH : DRAW_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);
+                qsort_safe(children, n, sizeof(struct CGroupInfo*), cgroup_info_compare_func);
+
+                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 = draw_special_char(more ? DRAW_TREE_BRANCH : DRAW_TREE_RIGHT);
+
+                        fputs(prefix, stdout);
+                        fputs(special, stdout);
+                        fputs(name, stdout);
+                        fputc('\n', stdout);
+
+                        special = draw_special_char(more ? DRAW_TREE_VERTICAL : DRAW_TREE_SPACE);
+
+                        pp = strappend(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;
+        Iterator i;
+        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, i) {
+                const char *name;
+                void *pidp;
+                Iterator j;
+
+                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, j) {
+                        pids[n++] = PTR_TO_PID(pidp);
+
+                        r = hashmap_put(names, pidp, (void*) name);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        if (n == 0)
+                return 0;
+
+        qsort_safe(pids, n, sizeof(pid_t), 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,
+                        draw_special_char(DRAW_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(&string_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 < 0)
+                        goto finish;
+        }
+
+        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;
+}
diff --git a/src/shared/bus-unit-util.h b/src/shared/bus-unit-util.h
new file mode 100644 (file)
index 0000000..55486f8
--- /dev/null
@@ -0,0 +1,27 @@
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2016 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "sd-bus.h"
+
+#include "output-mode.h"
+#include "install.h"
+
+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);
index f3039b23f741e4638a2ad729d34e260dd6158ccc..65a2c554d51435679e8cdfd410ca5b86ea713630 100644 (file)
 #include "string-util.h"
 #include "terminal-util.h"
 
-static int compare(const void *a, const void *b) {
-        const pid_t *p = a, *q = b;
-
-        if (*p < *q)
-                return -1;
-        if (*p > *q)
-                return 1;
-        return 0;
-}
-
 static void show_pid_array(pid_t pids[], unsigned n_pids, const char *prefix, unsigned n_columns, bool extra, bool more, bool kernel_threads, OutputFlags flags) {
         unsigned i, j, pid_width;
 
         if (n_pids == 0)
                 return;
 
-        qsort(pids, n_pids, sizeof(pid_t), compare);
+        qsort(pids, n_pids, sizeof(pid_t), pid_compare_func);
 
         /* Filter duplicates */
         for (j = 0, i = 1; i < n_pids; i++) {
@@ -86,8 +76,14 @@ static void show_pid_array(pid_t pids[], unsigned n_pids, const char *prefix, un
         }
 }
 
+static int show_cgroup_one_by_path(
+                const char *path,
+                const char *prefix,
+                unsigned n_columns,
+                bool more,
+                bool kernel_threads,
+                OutputFlags flags) {
 
-static int show_cgroup_one_by_path(const char *path, const char *prefix, unsigned n_columns, bool more, bool kernel_threads, OutputFlags flags) {
         char *fn;
         _cleanup_fclose_ FILE *f = NULL;
         size_t n = 0, n_allocated = 0;
@@ -125,7 +121,13 @@ static int show_cgroup_one_by_path(const char *path, const char *prefix, unsigne
         return 0;
 }
 
-int show_cgroup_by_path(const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, OutputFlags flags) {
+int show_cgroup_by_path(
+                const char *path,
+                const char *prefix,
+                unsigned n_columns,
+                bool kernel_threads,
+                OutputFlags flags) {
+
         _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL;
         _cleanup_closedir_ DIR *d = NULL;
         char *gn = NULL;
@@ -137,8 +139,7 @@ int show_cgroup_by_path(const char *path, const char *prefix, unsigned n_columns
         if (n_columns <= 0)
                 n_columns = columns();
 
-        if (!prefix)
-                prefix = "";
+        prefix = strempty(prefix);
 
         r = cg_mangle_path(path, &fn);
         if (r < 0)
@@ -202,7 +203,13 @@ int show_cgroup_by_path(const char *path, const char *prefix, unsigned n_columns
         return 0;
 }
 
-int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, OutputFlags flags) {
+int show_cgroup(const char *controller,
+                const char *path,
+                const char *prefix,
+                unsigned n_columns,
+                bool kernel_threads,
+
+                OutputFlags flags) {
         _cleanup_free_ char *p = NULL;
         int r;
 
@@ -215,7 +222,15 @@ int show_cgroup(const char *controller, const char *path, const char *prefix, un
         return show_cgroup_by_path(p, prefix, n_columns, kernel_threads, flags);
 }
 
-static int show_extra_pids(const char *controller, const char *path, const char *prefix, unsigned n_columns, const pid_t pids[], unsigned n_pids, OutputFlags flags) {
+static int show_extra_pids(
+                const char *controller,
+                const char *path,
+                const char *prefix,
+                unsigned n_columns,
+                const pid_t pids[],
+                unsigned n_pids,
+                OutputFlags flags) {
+
         _cleanup_free_ pid_t *copy = NULL;
         unsigned i, j;
         int r;
@@ -252,7 +267,16 @@ static int show_extra_pids(const char *controller, const char *path, const char
         return 0;
 }
 
-int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags) {
+int show_cgroup_and_extra(
+                const char *controller,
+                const char *path,
+                const char *prefix,
+                unsigned n_columns,
+                bool kernel_threads,
+                const pid_t extra_pids[],
+                unsigned n_extra_pids,
+                OutputFlags flags) {
+
         int r;
 
         assert(path);
@@ -264,7 +288,15 @@ int show_cgroup_and_extra(const char *controller, const char *path, const char *
         return show_extra_pids(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags);
 }
 
-int show_cgroup_and_extra_by_spec(const char *spec, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags) {
+int show_cgroup_and_extra_by_spec(
+                const char *spec,
+                const char *prefix,
+                unsigned n_columns,
+                bool kernel_threads,
+                const pid_t extra_pids[],
+                unsigned n_extra_pids,
+                OutputFlags flags) {
+
         _cleanup_free_ char *controller = NULL, *path = NULL;
         int r;
 
index 04205411dde3d7b40b4b27eaea5924244b8a5ad6..c74fc11ca641672a7d37df0674e3a2c025489e36 100644 (file)
@@ -39,6 +39,7 @@
 #include "bus-common-errors.h"
 #include "bus-error.h"
 #include "bus-message.h"
+#include "bus-unit-util.h"
 #include "bus-util.h"
 #include "cgroup-show.h"
 #include "cgroup-util.h"
@@ -3434,6 +3435,7 @@ typedef struct UnitStatusInfo {
 } UnitStatusInfo;
 
 static void print_status_info(
+                sd_bus *bus,
                 UnitStatusInfo *i,
                 bool *ellipsized) {
 
@@ -3444,6 +3446,7 @@ static void print_status_info(
         char since2[FORMAT_TIMESTAMP_MAX], *s2;
         const char *path;
         char **t, **t2;
+        int r;
 
         assert(i);
 
@@ -3716,25 +3719,26 @@ static void print_status_info(
                 printf("      CPU: %s\n", format_timespan(buf, sizeof(buf), i->cpu_usage_nsec / NSEC_PER_USEC, USEC_PER_MSEC));
         }
 
-        if (i->control_group &&
-            (i->main_pid > 0 || i->control_pid > 0 ||
-             (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE) || cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, i->control_group) == 0))) {
+        if (i->control_group)
+                printf("   CGroup: %s\n", i->control_group);
+
+        {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                static const char prefix[] = "           ";
                 unsigned c;
 
-                printf("   CGroup: %s\n", i->control_group);
+                c = columns();
+                if (c > sizeof(prefix) - 1)
+                        c -= sizeof(prefix) - 1;
+                else
+                        c = 0;
 
-                if (IN_SET(arg_transport,
-                           BUS_TRANSPORT_LOCAL,
-                           BUS_TRANSPORT_MACHINE)) {
+                r = unit_show_processes(bus, i->id, i->control_group, prefix, c, get_output_flags(), &error);
+                if (r == -EBADR) {
                         unsigned k = 0;
                         pid_t extra[2];
-                        static const char prefix[] = "           ";
 
-                        c = columns();
-                        if (c > sizeof(prefix) - 1)
-                                c -= sizeof(prefix) - 1;
-                        else
-                                c = 0;
+                        /* Fallback for older systemd versions where the GetUnitProcesses() call is not yet available */
 
                         if (i->main_pid > 0)
                                 extra[k++] = i->main_pid;
@@ -3743,7 +3747,8 @@ static void print_status_info(
                                 extra[k++] = i->control_pid;
 
                         show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, i->control_group, prefix, c, false, extra, k, get_output_flags());
-                }
+                } else if (r < 0)
+                        log_warning_errno(r, "Failed to dump process list, ignoring: %s", bus_error_message(&error, r));
         }
 
         if (i->id && arg_transport == BUS_TRANSPORT_LOCAL)
@@ -4504,7 +4509,7 @@ static int show_one(
                 if (streq(verb, "help"))
                         show_unit_help(&info);
                 else
-                        print_status_info(&info, ellipsized);
+                        print_status_info(bus, &info, ellipsized);
         }
 
         strv_free(info.documentation);