chiark / gitweb /
unit: record inactive enter/exit timestamps to facilitate syslog lookups
[elogind.git] / unit.c
diff --git a/unit.c b/unit.c
index 900c76ad66a1aa47c468d94aba0d125629b4ff3c..7779753591921d29ba4f6ff7043b9c18288ff04b 100644 (file)
--- a/unit.c
+++ b/unit.c
@@ -37,6 +37,7 @@
 #include "log.h"
 #include "unit-name.h"
 #include "specifier.h"
+#include "dbus-unit.h"
 
 const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = {
         [UNIT_SERVICE] = &service_vtable,
@@ -46,7 +47,8 @@ const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = {
         [UNIT_DEVICE] = &device_vtable,
         [UNIT_MOUNT] = &mount_vtable,
         [UNIT_AUTOMOUNT] = &automount_vtable,
-        [UNIT_SNAPSHOT] = &snapshot_vtable
+        [UNIT_SNAPSHOT] = &snapshot_vtable,
+        [UNIT_SWAP] = &swap_vtable
 };
 
 Unit *unit_new(Manager *m) {
@@ -124,6 +126,11 @@ int unit_add_name(Unit *u, const char *text) {
                 goto fail;
         }
 
+        if (hashmap_size(u->meta.manager->units) >= MANAGER_MAX_NAMES) {
+                r = -E2BIG;
+                goto fail;
+        }
+
         if ((r = set_put(u->meta.names, s)) < 0) {
                 if (r == -EEXIST)
                         r = 0;
@@ -203,6 +210,25 @@ int unit_set_description(Unit *u, const char *description) {
         return 0;
 }
 
+bool unit_check_gc(Unit *u) {
+        assert(u);
+
+        if (UNIT_VTABLE(u)->no_gc)
+                return true;
+
+        if (u->meta.job)
+                return true;
+
+        if (unit_active_state(u) != UNIT_INACTIVE)
+                return true;
+
+        if (UNIT_VTABLE(u)->check_gc)
+                if (UNIT_VTABLE(u)->check_gc(u))
+                        return true;
+
+        return false;
+}
+
 void unit_add_to_load_queue(Unit *u) {
         assert(u);
         assert(u->meta.type != _UNIT_TYPE_INVALID);
@@ -224,6 +250,24 @@ void unit_add_to_cleanup_queue(Unit *u) {
         u->meta.in_cleanup_queue = true;
 }
 
+void unit_add_to_gc_queue(Unit *u) {
+        assert(u);
+
+        if (u->meta.in_gc_queue || u->meta.in_cleanup_queue)
+                return;
+
+        if (unit_check_gc(u))
+                return;
+
+        LIST_PREPEND(Meta, gc_queue, u->meta.manager->gc_queue, &u->meta);
+        u->meta.in_gc_queue = true;
+
+        u->meta.manager->n_in_gc_queue ++;
+
+        if (u->meta.manager->gc_queue_timestamp <= 0)
+                u->meta.manager->gc_queue_timestamp = now(CLOCK_MONOTONIC);
+}
+
 void unit_add_to_dbus_queue(Unit *u) {
         assert(u);
         assert(u->meta.type != _UNIT_TYPE_INVALID);
@@ -249,6 +293,8 @@ static void bidi_set_free(Unit *u, Set *s) {
 
                 for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++)
                         set_remove(other->meta.dependencies[d], u);
+
+                unit_add_to_gc_queue(other);
         }
 
         set_free(s);
@@ -279,6 +325,11 @@ void unit_free(Unit *u) {
         if (u->meta.in_cleanup_queue)
                 LIST_REMOVE(Meta, cleanup_queue, u->meta.manager->cleanup_queue, &u->meta);
 
+        if (u->meta.in_gc_queue) {
+                LIST_REMOVE(Meta, gc_queue, u->meta.manager->gc_queue, &u->meta);
+                u->meta.manager->n_in_gc_queue--;
+        }
+
         /* Free data and next 'smaller' objects */
         if (u->meta.job)
                 job_free(u->meta.job);
@@ -481,11 +532,11 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
         /* If syslog or kernel logging is requested, make sure our own
          * logging daemon is run first. */
 
-        if ((r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_LOGGER_SOCKET, NULL)) < 0)
+        if ((r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_LOGGER_SOCKET, NULL, true)) < 0)
                 return r;
 
         if (u->meta.manager->running_as != MANAGER_SESSION)
-                if ((r = unit_add_dependency_by_name(u, UNIT_REQUIRES, SPECIAL_LOGGER_SOCKET, NULL)) < 0)
+                if ((r = unit_add_dependency_by_name(u, UNIT_REQUIRES, SPECIAL_LOGGER_SOCKET, NULL, true)) < 0)
                         return r;
 
         return 0;
@@ -507,7 +558,11 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
         char *p2;
         const char *prefix2;
         CGroupBonding *b;
-        char timestamp1[FORMAT_TIMESTAMP_MAX], timestamp2[FORMAT_TIMESTAMP_MAX];
+        char
+                timestamp1[FORMAT_TIMESTAMP_MAX],
+                timestamp2[FORMAT_TIMESTAMP_MAX],
+                timestamp3[FORMAT_TIMESTAMP_MAX],
+                timestamp4[FORMAT_TIMESTAMP_MAX];
 
         assert(u);
         assert(u->meta.type >= 0);
@@ -518,20 +573,26 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
         prefix2 = p2 ? p2 : prefix;
 
         fprintf(f,
-                "%s Unit %s:\n"
+                "%s-> Unit %s:\n"
                 "%s\tDescription: %s\n"
                 "%s\tInstance: %s\n"
                 "%s\tUnit Load State: %s\n"
                 "%s\tUnit Active State: %s\n"
+                "%s\tInactive Exit Timestamp: %s\n"
                 "%s\tActive Enter Timestamp: %s\n"
-                "%s\tActive Exit Timestamp: %s\n",
+                "%s\tActive Exit Timestamp: %s\n"
+                "%s\tInactive Enter Timestamp: %s\n"
+                "%s\tGC Check Good: %s\n",
                 prefix, u->meta.id,
                 prefix, unit_description(u),
                 prefix, strna(u->meta.instance),
                 prefix, unit_load_state_to_string(u->meta.load_state),
                 prefix, unit_active_state_to_string(unit_active_state(u)),
-                prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->meta.active_enter_timestamp)),
-                prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->meta.active_exit_timestamp)));
+                prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->meta.inactive_exit_timestamp)),
+                prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->meta.active_enter_timestamp)),
+                prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->meta.active_exit_timestamp)),
+                prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->meta.inactive_enter_timestamp)),
+                prefix, yes_no(unit_check_gc(u)));
 
         SET_FOREACH(t, u->meta.names, i)
                 fprintf(f, "%s\tName: %s\n", prefix, t);
@@ -614,6 +675,16 @@ int unit_load_fragment_and_dropin_optional(Unit *u) {
         return 0;
 }
 
+/* Common implementation for multiple backends */
+int unit_load_nop(Unit *u) {
+        assert(u);
+
+        if (u->meta.load_state == UNIT_STUB)
+                u->meta.load_state = UNIT_LOADED;
+
+        return 0;
+}
+
 int unit_load(Unit *u) {
         int r;
 
@@ -642,6 +713,7 @@ int unit_load(Unit *u) {
         assert((u->meta.load_state != UNIT_MERGED) == !u->meta.merged_into);
 
         unit_add_to_dbus_queue(unit_follow_merge(u));
+        unit_add_to_gc_queue(u);
 
         return 0;
 
@@ -649,7 +721,7 @@ fail:
         u->meta.load_state = UNIT_FAILED;
         unit_add_to_dbus_queue(u);
 
-        log_error("Failed to load configuration for %s: %s", u->meta.id, strerror(-r));
+        log_debug("Failed to load configuration for %s: %s", u->meta.id, strerror(-r));
 
         return r;
 }
@@ -844,6 +916,7 @@ static void retroactively_stop_dependencies(Unit *u) {
 
 void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
         bool unexpected = false;
+        usec_t ts;
 
         assert(u);
         assert(os < _UNIT_ACTIVE_STATE_MAX);
@@ -856,10 +929,17 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
          * this function will be called too and the utmp code below
          * relies on that! */
 
+        ts = now(CLOCK_REALTIME);
+
+        if (os == UNIT_INACTIVE && ns != UNIT_INACTIVE)
+                u->meta.inactive_exit_timestamp = ts;
+        else if (os != UNIT_INACTIVE && ns == UNIT_INACTIVE)
+                u->meta.inactive_enter_timestamp = ts;
+
         if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns))
-                u->meta.active_enter_timestamp = now(CLOCK_REALTIME);
+                u->meta.active_enter_timestamp = ts;
         else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns))
-                u->meta.active_exit_timestamp = now(CLOCK_REALTIME);
+                u->meta.active_exit_timestamp = ts;
 
         if (u->meta.job) {
 
@@ -976,6 +1056,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
         unit_check_uneeded(u);
 
         unit_add_to_dbus_queue(u);
+        unit_add_to_gc_queue(u);
 }
 
 int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w) {
@@ -1108,7 +1189,7 @@ void unit_unwatch_timer(Unit *u, Watch *w) {
         assert(w->type == WATCH_TIMER && w->data.unit == u);
 
         assert_se(epoll_ctl(u->meta.manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0);
-        assert_se(close_nointr(w->fd) == 0);
+        close_nointr_nofail(w->fd);
 
         w->fd = -1;
         w->type = WATCH_INVALID;
@@ -1141,7 +1222,7 @@ bool unit_job_is_applicable(Unit *u, JobType j) {
         }
 }
 
-int unit_add_dependency(Unit *u, UnitDependency d, Unit *other) {
+int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference) {
 
         static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = {
                 [UNIT_REQUIRES] = UNIT_REQUIRED_BY,
@@ -1154,9 +1235,11 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other) {
                 [UNIT_WANTED_BY] = _UNIT_DEPENDENCY_INVALID,
                 [UNIT_CONFLICTS] = UNIT_CONFLICTS,
                 [UNIT_BEFORE] = UNIT_AFTER,
-                [UNIT_AFTER] = UNIT_BEFORE
+                [UNIT_AFTER] = UNIT_BEFORE,
+                [UNIT_REFERENCES] = UNIT_REFERENCED_BY,
+                [UNIT_REFERENCED_BY] = UNIT_REFERENCES
         };
-        int r;
+        int r, q = 0, v = 0, w = 0;
 
         assert(u);
         assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX);
@@ -1176,22 +1259,47 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other) {
                     return -EINVAL;
         }
 
-        if ((r = set_ensure_allocated(&u->meta.dependencies[d], trivial_hash_func, trivial_compare_func)) < 0)
+        if ((r = set_ensure_allocated(&u->meta.dependencies[d], trivial_hash_func, trivial_compare_func)) < 0 ||
+            (r = set_ensure_allocated(&other->meta.dependencies[inverse_table[d]], trivial_hash_func, trivial_compare_func)) < 0)
                 return r;
 
-        if ((r = set_ensure_allocated(&other->meta.dependencies[inverse_table[d]], trivial_hash_func, trivial_compare_func)) < 0)
-                return r;
+        if (add_reference)
+                if ((r = set_ensure_allocated(&u->meta.dependencies[UNIT_REFERENCES], trivial_hash_func, trivial_compare_func)) < 0 ||
+                    (r = set_ensure_allocated(&other->meta.dependencies[UNIT_REFERENCED_BY], trivial_hash_func, trivial_compare_func)) < 0)
+                        return r;
 
-        if ((r = set_put(u->meta.dependencies[d], other)) < 0)
-                return r;
+        if ((q = set_put(u->meta.dependencies[d], other)) < 0)
+                return q;
 
-        if ((r = set_put(other->meta.dependencies[inverse_table[d]], u)) < 0) {
-                set_remove(u->meta.dependencies[d], other);
-                return r;
+        if ((v = set_put(other->meta.dependencies[inverse_table[d]], u)) < 0) {
+                r = v;
+                goto fail;
+        }
+
+        if (add_reference) {
+                if ((w = set_put(u->meta.dependencies[UNIT_REFERENCES], other)) < 0) {
+                        r = w;
+                        goto fail;
+                }
+
+                if ((r = set_put(other->meta.dependencies[UNIT_REFERENCED_BY], u)) < 0)
+                        goto fail;
         }
 
         unit_add_to_dbus_queue(u);
         return 0;
+
+fail:
+        if (q > 0)
+                set_remove(u->meta.dependencies[d], other);
+
+        if (v > 0)
+                set_remove(other->meta.dependencies[inverse_table[d]], u);
+
+        if (w > 0)
+                set_remove(u->meta.dependencies[UNIT_REFERENCES], other);
+
+        return r;
 }
 
 static const char *resolve_template(Unit *u, const char *name, const char*path, char **p) {
@@ -1227,7 +1335,7 @@ static const char *resolve_template(Unit *u, const char *name, const char*path,
         return s;
 }
 
-int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path) {
+int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) {
         Unit *other;
         int r;
         char *s;
@@ -1241,14 +1349,14 @@ int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, con
         if ((r = manager_load_unit(u->meta.manager, name, path, &other)) < 0)
                 goto finish;
 
-        r = unit_add_dependency(u, d, other);
+        r = unit_add_dependency(u, d, other, add_reference);
 
 finish:
         free(s);
         return r;
 }
 
-int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *path) {
+int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) {
         Unit *other;
         int r;
         char *s;
@@ -1262,7 +1370,7 @@ int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *n
         if ((r = manager_load_unit(u->meta.manager, name, path, &other)) < 0)
                 goto finish;
 
-        r = unit_add_dependency(other, d, u);
+        r = unit_add_dependency(other, d, u, add_reference);
 
 finish:
         free(s);
@@ -1494,6 +1602,29 @@ int unit_load_related_unit(Unit *u, const char *type, Unit **_found) {
         return r;
 }
 
+int unit_get_related_unit(Unit *u, const char *type, Unit **_found) {
+        Unit *found;
+        char *t;
+
+        assert(u);
+        assert(type);
+        assert(_found);
+
+        if (!(t = unit_name_change_suffix(u->meta.id, type)))
+                return -ENOMEM;
+
+        assert(!unit_has_name(u, t));
+
+        found = manager_get_unit(u->meta.manager, t);
+        free(t);
+
+        if (!found)
+                return -ENOENT;
+
+        *_found = found;
+        return 0;
+}
+
 static char *specifier_prefix_and_instance(char specifier, void *data, void *userdata) {
         Unit *u = userdata;
         assert(u);
@@ -1626,6 +1757,133 @@ void unit_unwatch_bus_name(Unit *u, const char *name) {
         hashmap_remove_value(u->meta.manager->watch_bus, name, u);
 }
 
+bool unit_can_serialize(Unit *u) {
+        assert(u);
+
+        return UNIT_VTABLE(u)->serialize && UNIT_VTABLE(u)->deserialize_item;
+}
+
+int unit_serialize(Unit *u, FILE *f, FDSet *fds) {
+        int r;
+
+        assert(u);
+        assert(f);
+        assert(fds);
+
+        if (!unit_can_serialize(u))
+                return 0;
+
+        if ((r = UNIT_VTABLE(u)->serialize(u, f, fds)) < 0)
+                return r;
+
+        /* End marker */
+        fputc('\n', f);
+        return 0;
+}
+
+void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *format, ...) {
+        va_list ap;
+
+        assert(u);
+        assert(f);
+        assert(key);
+        assert(format);
+
+        fputs(key, f);
+        fputc('=', f);
+
+        va_start(ap, format);
+        vfprintf(f, format, ap);
+        va_end(ap);
+
+        fputc('\n', f);
+}
+
+void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) {
+        assert(u);
+        assert(f);
+        assert(key);
+        assert(value);
+
+        fprintf(f, "%s=%s\n", key, value);
+}
+
+int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
+        int r;
+
+        assert(u);
+        assert(f);
+        assert(fds);
+
+        if (!unit_can_serialize(u))
+                return 0;
+
+        for (;;) {
+                char line[1024], *l, *v;
+                size_t k;
+
+                if (!fgets(line, sizeof(line), f)) {
+                        if (feof(f))
+                                return 0;
+                        return -errno;
+                }
+
+                l = strstrip(line);
+
+                /* End marker */
+                if (l[0] == 0)
+                        return 0;
+
+                k = strcspn(l, "=");
+
+                if (l[k] == '=') {
+                        l[k] = 0;
+                        v = l+k+1;
+                } else
+                        v = l+k;
+
+                if ((r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds)) < 0)
+                        return r;
+        }
+}
+
+int unit_add_node_link(Unit *u, const char *what, bool wants) {
+        Unit *device;
+        char *e;
+        int r;
+
+        assert(u);
+
+        if (!what)
+                return 0;
+
+        /* Adds in links to the device node that this unit is based on */
+
+        if (!path_startswith(what, "/dev/") && !path_startswith(what, "/sys/"))
+                return 0;
+
+        if (!(e = unit_name_build_escape(what+1, NULL, ".device")))
+                return -ENOMEM;
+
+        r = manager_load_unit(u->meta.manager, e, NULL, &device);
+        free(e);
+
+        if (r < 0)
+                return r;
+
+        if ((r = unit_add_dependency(u, UNIT_AFTER, device, true)) < 0)
+                return r;
+
+        if ((r = unit_add_dependency(u, UNIT_REQUIRES, device, true)) < 0)
+                return r;
+
+        if (wants)
+                if ((r = unit_add_dependency(device, UNIT_WANTS, u, false)) < 0)
+                        return r;
+
+        return 0;
+}
+
 static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
         [UNIT_SERVICE] = "service",
         [UNIT_TIMER] = "timer",
@@ -1634,7 +1892,8 @@ static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
         [UNIT_DEVICE] = "device",
         [UNIT_MOUNT] = "mount",
         [UNIT_AUTOMOUNT] = "automount",
-        [UNIT_SNAPSHOT] = "snapshot"
+        [UNIT_SNAPSHOT] = "snapshot",
+        [UNIT_SWAP] = "swap"
 };
 
 DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
@@ -1669,6 +1928,8 @@ static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
         [UNIT_CONFLICTS] = "Conflicts",
         [UNIT_BEFORE] = "Before",
         [UNIT_AFTER] = "After",
+        [UNIT_REFERENCES] = "References",
+        [UNIT_REFERENCED_BY] = "ReferencedBy"
 };
 
 DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);