bus-unit-util: Add utility to freeze/thaw units
authorAdrian Vovk <adrianvovk@gmail.com>
Sat, 23 Dec 2023 21:57:47 +0000 (16:57 -0500)
committerAdrian Vovk <adrianvovk@gmail.com>
Tue, 5 Mar 2024 17:12:34 +0000 (12:12 -0500)
This utility lets us freeze units, and then automatically thaw them
when via a _cleanup_ handler. For example, you can now write something
like:

```
_cleanup_(unit_freezer_thaw) UnitFreezer freezer = UNIT_FREEZER_NULL;
r = unit_freezer_freeze("myunit.service", &freezer);
if (r < 0)
    return r;
// Freeze is thawed once this scope ends
```

Aside from the basic _freeze and _thaw methods, there's also
_cancel and _restore. Cancel destroys the UnitFreezer without
thawing the unit. Restore creates a UnitFreezer without freezing it.
The idea of these two methods is that it allows the freeze/thaw to
be separated from each other (i.e. done in response to two separate
DBus method calls). For example:

```
_cleanup_(unit_freezer_thaw) UnitFreezer freezer = UNIT_FREEZER_NULL;
r = unit_freezer_freeze("myunit.service", &freezer);
if (r < 0)
    return r;
// Freeze is thawed once this scope ends

r = do_something()
if (r < 0)
    return r; // Freeze is thawed

unit_freezer_cancel(&freezer); // Thaw is canceled.
```

Then in another scope:
```
// Bring back a UnitFreezer object for the already-frozen service
_cleanup_(unit_freezer_thaw) UnitFreezer freezer = UNIT_FREEZER_NULL;
r = unit_freezer_restore("myunit.service", &freezer);
if (r < 0)
    return r;
// Freeze is thawed once this scope ends
```

src/shared/bus-unit-util.c
src/shared/bus-unit-util.h

index 783ce31b3d5a98dadbcd30bbc53b423bf7646c5c..2f485f810dbdbb235b38598551a31369c56fdd14 100644 (file)
@@ -10,6 +10,7 @@
 #include "cgroup-setup.h"
 #include "cgroup-util.h"
 #include "condition.h"
+#include "constants.h"
 #include "coredump-util.h"
 #include "cpu-set-util.h"
 #include "dissect-image.h"
@@ -2937,3 +2938,89 @@ int bus_service_manager_reload(sd_bus *bus) {
 
         return 0;
 }
+
+int unit_freezer_new(const char *name, UnitFreezer *ret) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_free_ char *namedup = NULL;
+        int r;
+
+        assert(name);
+        assert(ret);
+
+        namedup = strdup(name);
+        if (!namedup)
+                return log_oom_debug();
+
+        r = bus_connect_system_systemd(&bus);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to open connection to systemd: %m");
+
+        (void) sd_bus_set_method_call_timeout(bus, FREEZE_TIMEOUT);
+
+        *ret = (UnitFreezer) {
+                .name = TAKE_PTR(namedup),
+                .bus = TAKE_PTR(bus),
+        };
+        return 0;
+}
+
+static int unit_freezer_action(bool freeze, UnitFreezer *f) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(f);
+        assert(f->bus);
+        assert(f->name);
+
+        r = bus_call_method(f->bus, bus_systemd_mgr, freeze ? "FreezeUnit" : "ThawUnit",
+                            &error, NULL, "s", f->name);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to %s unit %s: %s", freeze ? "freeze" : "thaw",
+                                       f->name, bus_error_message(&error, r));
+
+        return 0;
+}
+
+int unit_freezer_freeze(UnitFreezer *f) {
+        return unit_freezer_action(true, f);
+}
+
+int unit_freezer_thaw(UnitFreezer *f) {
+        return unit_freezer_action(false, f);
+}
+
+void unit_freezer_done(UnitFreezer *f) {
+        assert(f);
+
+        f->name = mfree(f->name);
+        f->bus = sd_bus_flush_close_unref(f->bus);
+}
+
+int unit_freezer_new_freeze(const char *name, UnitFreezer *ret) {
+        _cleanup_(unit_freezer_done) UnitFreezer f = {};
+        int r;
+
+        assert(name);
+        assert(ret);
+
+        r = unit_freezer_new(name, &f);
+        if (r < 0)
+                return r;
+
+        r = unit_freezer_freeze(&f);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_STRUCT(f);
+        return 0;
+}
+
+void unit_freezer_done_thaw(UnitFreezer *f) {
+        assert(f);
+
+        if (!f->name)
+                return;
+
+        (void) unit_freezer_thaw(f);
+        unit_freezer_done(f);
+}
index d52c8475caf75d049a11f56cc77e9376470e64cd..3c6a00106967c74cce62d170e79be276040c28b4 100644 (file)
@@ -35,3 +35,20 @@ int unit_load_state(sd_bus *bus, const char *name, char **load_state);
 int unit_info_compare(const UnitInfo *a, const UnitInfo *b);
 
 int bus_service_manager_reload(sd_bus *bus);
+
+typedef struct UnitFreezer {
+        char *name;
+        sd_bus *bus;
+} UnitFreezer;
+
+int unit_freezer_new(const char *name, UnitFreezer *ret);
+
+int unit_freezer_freeze(UnitFreezer *freezer);
+
+int unit_freezer_thaw(UnitFreezer *freezer);
+
+void unit_freezer_done(UnitFreezer *freezer);
+
+int unit_freezer_new_freeze(const char *name, UnitFreezer *ret);
+
+void unit_freezer_done_thaw(UnitFreezer *freezer);