4cad4c
From 5e422a9cea38bd5c7ce54c7bbac612c04418dc41 Mon Sep 17 00:00:00 2001
4cad4c
From: Lennart Poettering <lennart@poettering.net>
4cad4c
Date: Mon, 1 Apr 2019 18:54:59 +0200
4cad4c
Subject: [PATCH] shared: add generic logic for waiting for a unit to enter
4cad4c
 some state
4cad4c
4cad4c
This is a generic implementation of a client-side logic of waiting until
4cad4c
a unit enters or leaves some state.
4cad4c
4cad4c
This is a more generic implementation of the WaitContext logic currently
4cad4c
in systemctl.c, and is supposed to replace it (a later commit does
4cad4c
this). It's similar to bus-wait-for-jobs.c and we probably should fold
4cad4c
that one into it later on.
4cad4c
4cad4c
This code is more powerful and cleaner than the WaitContext logic
4cad4c
however. In addition to waiting for a unit to exit this also allows us
4cad4c
to wait for a unit to leave the "maintainance" state.
4cad4c
4cad4c
This commit only implements the generic logic, and adds no users of it
4cad4c
yet.
4cad4c
4cad4c
(cherry picked from commit 3572d3df8f822d4cf1601428401a837f723771cf)
4cad4c
4cad4c
Related: #1830861
4cad4c
---
4cad4c
 src/shared/bus-wait-for-units.c | 434 ++++++++++++++++++++++++++++++++
4cad4c
 src/shared/bus-wait-for-units.h |  35 +++
4cad4c
 src/shared/meson.build          |   2 +
4cad4c
 3 files changed, 471 insertions(+)
4cad4c
 create mode 100644 src/shared/bus-wait-for-units.c
4cad4c
 create mode 100644 src/shared/bus-wait-for-units.h
4cad4c
4cad4c
diff --git a/src/shared/bus-wait-for-units.c b/src/shared/bus-wait-for-units.c
4cad4c
new file mode 100644
4cad4c
index 0000000000..d07f491e93
4cad4c
--- /dev/null
4cad4c
+++ b/src/shared/bus-wait-for-units.c
4cad4c
@@ -0,0 +1,434 @@
4cad4c
+/* SPDX-License-Identifier: LGPL-2.1+ */
4cad4c
+
4cad4c
+#include "bus-util.h"
4cad4c
+#include "bus-wait-for-units.h"
4cad4c
+#include "hashmap.h"
4cad4c
+#include "string-util.h"
4cad4c
+#include "strv.h"
4cad4c
+#include "unit-def.h"
4cad4c
+
4cad4c
+typedef struct WaitForItem {
4cad4c
+        BusWaitForUnits *parent;
4cad4c
+
4cad4c
+        BusWaitForUnitsFlags flags;
4cad4c
+
4cad4c
+        char *bus_path;
4cad4c
+
4cad4c
+        sd_bus_slot *slot_get_all;
4cad4c
+        sd_bus_slot *slot_properties_changed;
4cad4c
+
4cad4c
+        bus_wait_for_units_unit_callback unit_callback;
4cad4c
+        void *userdata;
4cad4c
+
4cad4c
+        char *active_state;
4cad4c
+        uint32_t job_id;
4cad4c
+        char *clean_result;
4cad4c
+} WaitForItem;
4cad4c
+
4cad4c
+typedef struct BusWaitForUnits {
4cad4c
+        sd_bus *bus;
4cad4c
+        sd_bus_slot *slot_disconnected;
4cad4c
+
4cad4c
+        Hashmap *items;
4cad4c
+
4cad4c
+        bus_wait_for_units_ready_callback ready_callback;
4cad4c
+        void *userdata;
4cad4c
+
4cad4c
+        WaitForItem *current;
4cad4c
+
4cad4c
+        BusWaitForUnitsState state;
4cad4c
+        bool has_failed:1;
4cad4c
+} BusWaitForUnits;
4cad4c
+
4cad4c
+static WaitForItem *wait_for_item_free(WaitForItem *item) {
4cad4c
+        int r;
4cad4c
+
4cad4c
+        if (!item)
4cad4c
+                return NULL;
4cad4c
+
4cad4c
+        if (item->parent) {
4cad4c
+                if (FLAGS_SET(item->flags, BUS_WAIT_REFFED) && item->bus_path && item->parent->bus) {
4cad4c
+                        r = sd_bus_call_method_async(
4cad4c
+                                        item->parent->bus,
4cad4c
+                                        NULL,
4cad4c
+                                        "org.freedesktop.systemd1",
4cad4c
+                                        item->bus_path,
4cad4c
+                                        "org.freedesktop.systemd1.Unit",
4cad4c
+                                        "Unref",
4cad4c
+                                        NULL,
4cad4c
+                                        NULL,
4cad4c
+                                        NULL);
4cad4c
+                        if (r < 0)
4cad4c
+                                log_debug_errno(r, "Failed to drop reference to unit %s, ignoring: %m", item->bus_path);
4cad4c
+                }
4cad4c
+
4cad4c
+                assert_se(hashmap_remove(item->parent->items, item->bus_path) == item);
4cad4c
+
4cad4c
+                if (item->parent->current == item)
4cad4c
+                        item->parent->current = NULL;
4cad4c
+        }
4cad4c
+
4cad4c
+        sd_bus_slot_unref(item->slot_properties_changed);
4cad4c
+        sd_bus_slot_unref(item->slot_get_all);
4cad4c
+
4cad4c
+        free(item->bus_path);
4cad4c
+        free(item->active_state);
4cad4c
+        free(item->clean_result);
4cad4c
+
4cad4c
+        return mfree(item);
4cad4c
+}
4cad4c
+
4cad4c
+DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free);
4cad4c
+
4cad4c
+static void bus_wait_for_units_clear(BusWaitForUnits *d) {
4cad4c
+        WaitForItem *item;
4cad4c
+
4cad4c
+        assert(d);
4cad4c
+
4cad4c
+        d->slot_disconnected = sd_bus_slot_unref(d->slot_disconnected);
4cad4c
+        d->bus = sd_bus_unref(d->bus);
4cad4c
+
4cad4c
+        while ((item = hashmap_first(d->items))) {
4cad4c
+                d->current = item;
4cad4c
+
4cad4c
+                item->unit_callback(d, item->bus_path, false, item->userdata);
4cad4c
+                wait_for_item_free(item);
4cad4c
+        }
4cad4c
+
4cad4c
+        d->items = hashmap_free(d->items);
4cad4c
+}
4cad4c
+
4cad4c
+static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
4cad4c
+        BusWaitForUnits *d = userdata;
4cad4c
+
4cad4c
+        assert(m);
4cad4c
+        assert(d);
4cad4c
+
4cad4c
+        log_error("Warning! D-Bus connection terminated.");
4cad4c
+
4cad4c
+        bus_wait_for_units_clear(d);
4cad4c
+
4cad4c
+        if (d->ready_callback)
4cad4c
+                d->ready_callback(d, false, d->userdata);
4cad4c
+        else /* If no ready callback is specified close the connection so that the event loop exits */
4cad4c
+                sd_bus_close(sd_bus_message_get_bus(m));
4cad4c
+
4cad4c
+        return 0;
4cad4c
+}
4cad4c
+
4cad4c
+int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret) {
4cad4c
+        _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *d = NULL;
4cad4c
+        int r;
4cad4c
+
4cad4c
+        assert(bus);
4cad4c
+        assert(ret);
4cad4c
+
4cad4c
+        d = new(BusWaitForUnits, 1);
4cad4c
+        if (!d)
4cad4c
+                return -ENOMEM;
4cad4c
+
4cad4c
+        *d = (BusWaitForUnits) {
4cad4c
+                .state = BUS_WAIT_SUCCESS,
4cad4c
+                .bus = sd_bus_ref(bus),
4cad4c
+        };
4cad4c
+
4cad4c
+        r = sd_bus_match_signal_async(
4cad4c
+                        bus,
4cad4c
+                        &d->slot_disconnected,
4cad4c
+                        "org.freedesktop.DBus.Local",
4cad4c
+                        NULL,
4cad4c
+                        "org.freedesktop.DBus.Local",
4cad4c
+                        "Disconnected",
4cad4c
+                        match_disconnected, NULL, d);
4cad4c
+        if (r < 0)
4cad4c
+                return r;
4cad4c
+
4cad4c
+        *ret = TAKE_PTR(d);
4cad4c
+        return 0;
4cad4c
+}
4cad4c
+
4cad4c
+BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d) {
4cad4c
+        if (!d)
4cad4c
+                return NULL;
4cad4c
+
4cad4c
+        bus_wait_for_units_clear(d);
4cad4c
+        sd_bus_slot_unref(d->slot_disconnected);
4cad4c
+        sd_bus_unref(d->bus);
4cad4c
+
4cad4c
+        return mfree(d);
4cad4c
+}
4cad4c
+
4cad4c
+static bool bus_wait_for_units_is_ready(BusWaitForUnits *d) {
4cad4c
+        assert(d);
4cad4c
+
4cad4c
+        if (!d->bus) /* Disconnected? */
4cad4c
+                return true;
4cad4c
+
4cad4c
+        return hashmap_isempty(d->items);
4cad4c
+}
4cad4c
+
4cad4c
+void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata) {
4cad4c
+        assert(d);
4cad4c
+
4cad4c
+        d->ready_callback = callback;
4cad4c
+        d->userdata = userdata;
4cad4c
+}
4cad4c
+
4cad4c
+static void bus_wait_for_units_check_ready(BusWaitForUnits *d) {
4cad4c
+        assert(d);
4cad4c
+
4cad4c
+        if (!bus_wait_for_units_is_ready(d))
4cad4c
+                return;
4cad4c
+
4cad4c
+        d->state = d->has_failed ? BUS_WAIT_FAILURE : BUS_WAIT_SUCCESS;
4cad4c
+
4cad4c
+        if (d->ready_callback)
4cad4c
+                d->ready_callback(d, d->state, d->userdata);
4cad4c
+}
4cad4c
+
4cad4c
+static void wait_for_item_check_ready(WaitForItem *item) {
4cad4c
+        BusWaitForUnits *d;
4cad4c
+
4cad4c
+        assert(item);
4cad4c
+        assert(d = item->parent);
4cad4c
+
4cad4c
+        if (FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END)) {
4cad4c
+
4cad4c
+                if (item->clean_result && !streq(item->clean_result, "success"))
4cad4c
+                        d->has_failed = true;
4cad4c
+
4cad4c
+                if (!item->active_state || streq(item->active_state, "maintenance"))
4cad4c
+                        return;
4cad4c
+        }
4cad4c
+
4cad4c
+        if (FLAGS_SET(item->flags, BUS_WAIT_NO_JOB) && item->job_id != 0)
4cad4c
+                return;
4cad4c
+
4cad4c
+        if (FLAGS_SET(item->flags, BUS_WAIT_FOR_INACTIVE)) {
4cad4c
+
4cad4c
+                if (streq_ptr(item->active_state, "failed"))
4cad4c
+                        d->has_failed = true;
4cad4c
+                else if (!streq_ptr(item->active_state, "inactive"))
4cad4c
+                        return;
4cad4c
+        }
4cad4c
+
4cad4c
+        if (item->unit_callback) {
4cad4c
+                d->current = item;
4cad4c
+                item->unit_callback(d, item->bus_path, true, item->userdata);
4cad4c
+        }
4cad4c
+
4cad4c
+        wait_for_item_free(item);
4cad4c
+
4cad4c
+        bus_wait_for_units_check_ready(d);
4cad4c
+}
4cad4c
+
4cad4c
+static int property_map_job(
4cad4c
+                sd_bus *bus,
4cad4c
+                const char *member,
4cad4c
+                sd_bus_message *m,
4cad4c
+                sd_bus_error *error,
4cad4c
+                void *userdata) {
4cad4c
+
4cad4c
+        WaitForItem *item = userdata;
4cad4c
+        const char *path;
4cad4c
+        uint32_t id;
4cad4c
+        int r;
4cad4c
+
4cad4c
+        assert(item);
4cad4c
+
4cad4c
+        r = sd_bus_message_read(m, "(uo)", &id, &path);
4cad4c
+        if (r < 0)
4cad4c
+                return r;
4cad4c
+
4cad4c
+        item->job_id = id;
4cad4c
+        return 0;
4cad4c
+}
4cad4c
+
4cad4c
+static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) {
4cad4c
+
4cad4c
+        static const struct bus_properties_map map[] = {
4cad4c
+                { "ActiveState", "s",    NULL,             offsetof(WaitForItem, active_state) },
4cad4c
+                { "Job",         "(uo)", property_map_job, 0                                   },
4cad4c
+                { "CleanResult", "s",    NULL,             offsetof(WaitForItem, clean_result) },
4cad4c
+                {}
4cad4c
+        };
4cad4c
+
4cad4c
+        int r;
4cad4c
+
4cad4c
+        assert(item);
4cad4c
+        assert(m);
4cad4c
+
4cad4c
+        r = bus_message_map_all_properties(m, map, BUS_MAP_STRDUP, NULL, item);
4cad4c
+        if (r < 0)
4cad4c
+                return r;
4cad4c
+
4cad4c
+        wait_for_item_check_ready(item);
4cad4c
+        return 0;
4cad4c
+}
4cad4c
+
4cad4c
+static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
4cad4c
+        WaitForItem *item = userdata;
4cad4c
+        const char *interface;
4cad4c
+        int r;
4cad4c
+
4cad4c
+        assert(item);
4cad4c
+
4cad4c
+        r = sd_bus_message_read(m, "s", &interface);
4cad4c
+        if (r < 0) {
4cad4c
+                log_debug_errno(r, "Failed to parse PropertiesChanged signal: %m");
4cad4c
+                return 0;
4cad4c
+        }
4cad4c
+
4cad4c
+        if (!streq(interface, "org.freedesktop.systemd1.Unit"))
4cad4c
+                return 0;
4cad4c
+
4cad4c
+        r = wait_for_item_parse_properties(item, m);
4cad4c
+        if (r < 0)
4cad4c
+                log_debug_errno(r, "Failed to process PropertiesChanged signal: %m");
4cad4c
+
4cad4c
+        return 0;
4cad4c
+}
4cad4c
+
4cad4c
+static int on_get_all_properties(sd_bus_message *m, void *userdata, sd_bus_error *error) {
4cad4c
+        WaitForItem *item = userdata;
4cad4c
+        int r;
4cad4c
+
4cad4c
+        assert(item);
4cad4c
+
4cad4c
+        if (sd_bus_error_is_set(error)) {
4cad4c
+                BusWaitForUnits *d = item->parent;
4cad4c
+
4cad4c
+                d->has_failed = true;
4cad4c
+
4cad4c
+                log_debug_errno(sd_bus_error_get_errno(error), "GetAll() failed for %s: %s",
4cad4c
+                                item->bus_path, error->message);
4cad4c
+
4cad4c
+                d->current = item;
4cad4c
+                item->unit_callback(d, item->bus_path, false, item->userdata);
4cad4c
+                wait_for_item_free(item);
4cad4c
+
4cad4c
+                bus_wait_for_units_check_ready(d);
4cad4c
+                return 0;
4cad4c
+        }
4cad4c
+
4cad4c
+        r = wait_for_item_parse_properties(item, m);
4cad4c
+        if (r < 0)
4cad4c
+                log_debug_errno(r, "Failed to process GetAll method reply: %m");
4cad4c
+
4cad4c
+        return 0;
4cad4c
+}
4cad4c
+
4cad4c
+int bus_wait_for_units_add_unit(
4cad4c
+                BusWaitForUnits *d,
4cad4c
+                const char *unit,
4cad4c
+                BusWaitForUnitsFlags flags,
4cad4c
+                bus_wait_for_units_unit_callback callback,
4cad4c
+                void *userdata) {
4cad4c
+
4cad4c
+        _cleanup_(wait_for_item_freep) WaitForItem *item = NULL;
4cad4c
+        int r;
4cad4c
+
4cad4c
+        assert(d);
4cad4c
+        assert(unit);
4cad4c
+
4cad4c
+        assert(flags != 0);
4cad4c
+
4cad4c
+        r = hashmap_ensure_allocated(&d->items, &string_hash_ops);
4cad4c
+        if (r < 0)
4cad4c
+                return r;
4cad4c
+
4cad4c
+        item = new(WaitForItem, 1);
4cad4c
+        if (!item)
4cad4c
+                return -ENOMEM;
4cad4c
+
4cad4c
+        *item = (WaitForItem) {
4cad4c
+                .flags = flags,
4cad4c
+                .bus_path = unit_dbus_path_from_name(unit),
4cad4c
+                .unit_callback = callback,
4cad4c
+                .userdata = userdata,
4cad4c
+                .job_id = UINT32_MAX,
4cad4c
+        };
4cad4c
+
4cad4c
+        if (!item->bus_path)
4cad4c
+                return -ENOMEM;
4cad4c
+
4cad4c
+        if (!FLAGS_SET(item->flags, BUS_WAIT_REFFED)) {
4cad4c
+                r = sd_bus_call_method_async(
4cad4c
+                                d->bus,
4cad4c
+                                NULL,
4cad4c
+                                "org.freedesktop.systemd1",
4cad4c
+                                item->bus_path,
4cad4c
+                                "org.freedesktop.systemd1.Unit",
4cad4c
+                                "Ref",
4cad4c
+                                NULL,
4cad4c
+                                NULL,
4cad4c
+                                NULL);
4cad4c
+                if (r < 0)
4cad4c
+                        return log_debug_errno(r, "Failed to add reference to unit %s: %m", unit);
4cad4c
+
4cad4c
+
4cad4c
+                item->flags |= BUS_WAIT_REFFED;
4cad4c
+        }
4cad4c
+
4cad4c
+        r = sd_bus_match_signal_async(
4cad4c
+                        d->bus,
4cad4c
+                        &item->slot_properties_changed,
4cad4c
+                        "org.freedesktop.systemd1",
4cad4c
+                        item->bus_path,
4cad4c
+                        "org.freedesktop.DBus.Properties",
4cad4c
+                        "PropertiesChanged",
4cad4c
+                        on_properties_changed,
4cad4c
+                        NULL,
4cad4c
+                        item);
4cad4c
+        if (r < 0)
4cad4c
+                return log_debug_errno(r, "Failed to request match for PropertiesChanged signal: %m");
4cad4c
+
4cad4c
+        r = sd_bus_call_method_async(
4cad4c
+                        d->bus,
4cad4c
+                        &item->slot_get_all,
4cad4c
+                        "org.freedesktop.systemd1",
4cad4c
+                        item->bus_path,
4cad4c
+                        "org.freedesktop.DBus.Properties",
4cad4c
+                        "GetAll",
4cad4c
+                        on_get_all_properties,
4cad4c
+                        item,
4cad4c
+                        "s", FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END) ? NULL : "org.freedesktop.systemd1.Unit");
4cad4c
+        if (r < 0)
4cad4c
+                return log_debug_errno(r, "Failed to request properties of unit %s: %m", unit);
4cad4c
+
4cad4c
+        r = hashmap_put(d->items, item->bus_path, item);
4cad4c
+        if (r < 0)
4cad4c
+                return r;
4cad4c
+
4cad4c
+        d->state = BUS_WAIT_RUNNING;
4cad4c
+        item->parent = d;
4cad4c
+        TAKE_PTR(item);
4cad4c
+        return 0;
4cad4c
+}
4cad4c
+
4cad4c
+int bus_wait_for_units_run(BusWaitForUnits *d) {
4cad4c
+        int r;
4cad4c
+
4cad4c
+        assert(d);
4cad4c
+
4cad4c
+        while (d->state == BUS_WAIT_RUNNING) {
4cad4c
+
4cad4c
+                r = sd_bus_process(d->bus, NULL);
4cad4c
+                if (r < 0)
4cad4c
+                        return r;
4cad4c
+                if (r > 0)
4cad4c
+                        continue;
4cad4c
+
4cad4c
+                r = sd_bus_wait(d->bus, (uint64_t) -1);
4cad4c
+                if (r < 0)
4cad4c
+                        return r;
4cad4c
+        }
4cad4c
+
4cad4c
+        return d->state;
4cad4c
+}
4cad4c
+
4cad4c
+BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d) {
4cad4c
+        assert(d);
4cad4c
+
4cad4c
+        return d->state;
4cad4c
+}
4cad4c
diff --git a/src/shared/bus-wait-for-units.h b/src/shared/bus-wait-for-units.h
4cad4c
new file mode 100644
4cad4c
index 0000000000..a20f3d8fd7
4cad4c
--- /dev/null
4cad4c
+++ b/src/shared/bus-wait-for-units.h
4cad4c
@@ -0,0 +1,35 @@
4cad4c
+/* SPDX-License-Identifier: LGPL-2.1+ */
4cad4c
+#pragma once
4cad4c
+
4cad4c
+#include "macro.h"
4cad4c
+#include "sd-bus.h"
4cad4c
+
4cad4c
+typedef struct BusWaitForUnits BusWaitForUnits;
4cad4c
+
4cad4c
+typedef enum BusWaitForUnitsState {
4cad4c
+        BUS_WAIT_SUCCESS,    /* Nothing to wait for anymore and nothing failed */
4cad4c
+        BUS_WAIT_FAILURE,    /* dito, but something failed */
4cad4c
+        BUS_WAIT_RUNNING,    /* Still something to wait for */
4cad4c
+        _BUS_WAIT_FOR_UNITS_STATE_MAX,
4cad4c
+        _BUS_WAIT_FOR_UNITS_STATE_INVALID = -1,
4cad4c
+} BusWaitForUnitsState;
4cad4c
+
4cad4c
+typedef enum BusWaitForUnitsFlags {
4cad4c
+        BUS_WAIT_FOR_MAINTENANCE_END = 1 << 0, /* Wait until the unit is no longer in maintenance state */
4cad4c
+        BUS_WAIT_FOR_INACTIVE        = 1 << 1, /* Wait until the unit is back in inactive or dead state */
4cad4c
+        BUS_WAIT_NO_JOB              = 1 << 2, /* Wait until there's no more job pending */
4cad4c
+        BUS_WAIT_REFFED              = 1 << 3, /* The unit is already reffed with RefUnit() */
4cad4c
+} BusWaitForUnitsFlags;
4cad4c
+
4cad4c
+typedef void (*bus_wait_for_units_ready_callback)(BusWaitForUnits *d, BusWaitForUnitsState state, void *userdata);
4cad4c
+typedef void (*bus_wait_for_units_unit_callback)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata);
4cad4c
+
4cad4c
+int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret);
4cad4c
+BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d);
4cad4c
+
4cad4c
+BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d);
4cad4c
+void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata);
4cad4c
+int bus_wait_for_units_add_unit(BusWaitForUnits *d, const char *unit, BusWaitForUnitsFlags flags, bus_wait_for_units_unit_callback callback, void *userdata);
4cad4c
+int bus_wait_for_units_run(BusWaitForUnits *d);
4cad4c
+
4cad4c
+DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free);
4cad4c
diff --git a/src/shared/meson.build b/src/shared/meson.build
4cad4c
index 54e77e9af6..d0a1bba4c6 100644
4cad4c
--- a/src/shared/meson.build
4cad4c
+++ b/src/shared/meson.build
4cad4c
@@ -18,6 +18,8 @@ shared_sources = files('''
4cad4c
         bus-unit-util.h
4cad4c
         bus-util.c
4cad4c
         bus-util.h
4cad4c
+        bus-wait-for-units.c
4cad4c
+        bus-wait-for-units.h
4cad4c
         cgroup-show.c
4cad4c
         cgroup-show.h
4cad4c
         clean-ipc.c