summary refs log tree commit diff
path: root/src/core/nm-checkpoint-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/nm-checkpoint-manager.c')
-rw-r--r--src/core/nm-checkpoint-manager.c349
1 files changed, 349 insertions, 0 deletions
diff --git a/src/core/nm-checkpoint-manager.c b/src/core/nm-checkpoint-manager.c
new file mode 100644
index 00000000..b8c9101a
--- /dev/null
+++ b/src/core/nm-checkpoint-manager.c
@@ -0,0 +1,349 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2016 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-checkpoint-manager.h"
+
+#include "nm-checkpoint.h"
+#include "nm-connection.h"
+#include "nm-core-utils.h"
+#include "devices/nm-device.h"
+#include "nm-manager.h"
+#include "nm-utils.h"
+#include "c-list/src/c-list.h"
+
+/*****************************************************************************/
+
+struct _NMCheckpointManager {
+    NMManager * _manager;
+    GParamSpec *property_spec;
+    CList       checkpoints_lst_head;
+};
+
+#define GET_MANAGER(self)                               \
+    ({                                                  \
+        typeof(self) _self = (self);                    \
+                                                        \
+        _nm_unused NMCheckpointManager *_self2 = _self; \
+                                                        \
+        nm_assert(_self);                               \
+        nm_assert(NM_IS_MANAGER(_self->_manager));      \
+        _self->_manager;                                \
+    })
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_CORE
+#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "checkpoint", __VA_ARGS__)
+
+/*****************************************************************************/
+
+static void
+notify_checkpoints(NMCheckpointManager *self)
+{
+    g_object_notify_by_pspec((GObject *) GET_MANAGER(self), self->property_spec);
+}
+
+static void
+destroy_checkpoint(NMCheckpointManager *self, NMCheckpoint *checkpoint, gboolean log_destroy)
+{
+    nm_assert(NM_IS_CHECKPOINT(checkpoint));
+    nm_assert(nm_dbus_object_is_exported(NM_DBUS_OBJECT(checkpoint)));
+    nm_assert(c_list_contains(&self->checkpoints_lst_head, &checkpoint->checkpoints_lst));
+
+    nm_checkpoint_set_timeout_callback(checkpoint, NULL, NULL);
+
+    c_list_unlink(&checkpoint->checkpoints_lst);
+
+    if (log_destroy)
+        nm_checkpoint_log_destroy(checkpoint);
+
+    notify_checkpoints(self);
+
+    nm_dbus_object_unexport(NM_DBUS_OBJECT(checkpoint));
+    g_object_unref(checkpoint);
+}
+
+static GVariant *
+rollback_checkpoint(NMCheckpointManager *self, NMCheckpoint *checkpoint)
+{
+    GVariant *   result;
+    const CList *iter;
+
+    nm_assert(c_list_contains(&self->checkpoints_lst_head, &checkpoint->checkpoints_lst));
+
+    /* we destroy first all overlapping checkpoints that are younger/newer. */
+    for (iter = checkpoint->checkpoints_lst.next; iter != &self->checkpoints_lst_head;) {
+        NMCheckpoint *cp = c_list_entry(iter, NMCheckpoint, checkpoints_lst);
+
+        iter = iter->next;
+        if (nm_checkpoint_includes_devices_of(cp, checkpoint)) {
+            /* the younger checkpoint has overlapping devices and gets obsoleted.
+             * Destroy it. */
+            destroy_checkpoint(self, cp, TRUE);
+        }
+    }
+
+    result = nm_checkpoint_rollback(checkpoint);
+    destroy_checkpoint(self, checkpoint, FALSE);
+    return result;
+}
+
+static void
+rollback_timeout_cb(NMCheckpoint *checkpoint, gpointer user_data)
+{
+    NMCheckpointManager *self         = user_data;
+    gs_unref_variant GVariant *result = NULL;
+
+    result = rollback_checkpoint(self, checkpoint);
+}
+
+NMCheckpoint *
+nm_checkpoint_manager_create(NMCheckpointManager *   self,
+                             const char *const *     device_paths,
+                             guint32                 rollback_timeout,
+                             NMCheckpointCreateFlags flags,
+                             GError **               error)
+{
+    NMManager *       manager;
+    NMCheckpoint *    checkpoint;
+    gs_unref_ptrarray GPtrArray *devices = NULL;
+    NMDevice *                   device;
+
+    g_return_val_if_fail(self, FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+    manager = GET_MANAGER(self);
+
+    devices = g_ptr_array_new();
+
+    if (!device_paths || !device_paths[0]) {
+        const CList *tmp_lst;
+
+        nm_manager_for_each_device (manager, device, tmp_lst) {
+            /* FIXME: there is no strong reason to skip over unrealized devices.
+             *        Also, NMCheckpoint anticipates to handle them (in parts). */
+            if (!nm_device_is_real(device))
+                continue;
+            nm_assert(nm_dbus_object_get_path(NM_DBUS_OBJECT(device)));
+            g_ptr_array_add(devices, device);
+        }
+    } else if (NM_FLAGS_HAS(flags, NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES)) {
+        g_set_error_literal(
+            error,
+            NM_MANAGER_ERROR,
+            NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+            "the DISCONNECT_NEW_DEVICES flag can only be used with an empty device list");
+        return NULL;
+    } else {
+        for (; *device_paths; device_paths++) {
+            device = nm_manager_get_device_by_path(manager, *device_paths);
+            if (!device) {
+                g_set_error(error,
+                            NM_MANAGER_ERROR,
+                            NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+                            "device %s does not exist",
+                            *device_paths);
+                return NULL;
+            }
+            if (!nm_device_is_real(device)) {
+                g_set_error(error,
+                            NM_MANAGER_ERROR,
+                            NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+                            "device %s is not realized",
+                            *device_paths);
+                return NULL;
+            }
+            g_ptr_array_add(devices, device);
+        }
+    }
+
+    if (!devices->len) {
+        g_set_error_literal(error,
+                            NM_MANAGER_ERROR,
+                            NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+                            "no device available");
+        return NULL;
+    }
+
+    if (NM_FLAGS_HAS(flags, NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL))
+        nm_checkpoint_manager_destroy_all(self);
+    else if (!NM_FLAGS_HAS(flags, NM_CHECKPOINT_CREATE_FLAG_ALLOW_OVERLAPPING)) {
+        c_list_for_each_entry (checkpoint, &self->checkpoints_lst_head, checkpoints_lst) {
+            device = nm_checkpoint_includes_devices(checkpoint,
+                                                    (NMDevice *const *) devices->pdata,
+                                                    devices->len);
+            if (device) {
+                g_set_error(error,
+                            NM_MANAGER_ERROR,
+                            NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+                            "device '%s' is already included in checkpoint %s",
+                            nm_device_get_iface(device),
+                            nm_dbus_object_get_path(NM_DBUS_OBJECT(checkpoint)));
+                return NULL;
+            }
+        }
+    }
+
+    checkpoint = nm_checkpoint_new(manager, devices, rollback_timeout, flags);
+
+    nm_dbus_object_export(NM_DBUS_OBJECT(checkpoint));
+
+    nm_checkpoint_set_timeout_callback(checkpoint, rollback_timeout_cb, self);
+    c_list_link_tail(&self->checkpoints_lst_head, &checkpoint->checkpoints_lst);
+    notify_checkpoints(self);
+    return checkpoint;
+}
+
+void
+nm_checkpoint_manager_destroy_all(NMCheckpointManager *self)
+{
+    NMCheckpoint *checkpoint;
+
+    g_return_if_fail(self);
+
+    while ((checkpoint =
+                c_list_first_entry(&self->checkpoints_lst_head, NMCheckpoint, checkpoints_lst)))
+        destroy_checkpoint(self, checkpoint, TRUE);
+}
+
+gboolean
+nm_checkpoint_manager_destroy(NMCheckpointManager *self, const char *path, GError **error)
+{
+    NMCheckpoint *checkpoint;
+
+    g_return_val_if_fail(self, FALSE);
+    g_return_val_if_fail(path && path[0] == '/', FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    if (!nm_dbus_path_not_empty(path)) {
+        nm_checkpoint_manager_destroy_all(self);
+        return TRUE;
+    }
+
+    checkpoint = nm_checkpoint_manager_lookup_by_path(self, path, error);
+    if (!checkpoint)
+        return FALSE;
+
+    destroy_checkpoint(self, checkpoint, TRUE);
+    return TRUE;
+}
+
+gboolean
+nm_checkpoint_manager_rollback(NMCheckpointManager *self,
+                               const char *         path,
+                               GVariant **          results,
+                               GError **            error)
+{
+    NMCheckpoint *checkpoint;
+
+    g_return_val_if_fail(self, FALSE);
+    g_return_val_if_fail(path && path[0] == '/', FALSE);
+    g_return_val_if_fail(results, FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    checkpoint = nm_checkpoint_manager_lookup_by_path(self, path, error);
+    if (!checkpoint)
+        return FALSE;
+
+    *results = rollback_checkpoint(self, checkpoint);
+    return TRUE;
+}
+
+NMCheckpoint *
+nm_checkpoint_manager_lookup_by_path(NMCheckpointManager *self, const char *path, GError **error)
+{
+    NMCheckpoint *checkpoint;
+
+    g_return_val_if_fail(self, NULL);
+
+    checkpoint =
+        nm_dbus_manager_lookup_object(nm_dbus_object_get_manager(NM_DBUS_OBJECT(GET_MANAGER(self))),
+                                      path);
+    if (!checkpoint || !NM_IS_CHECKPOINT(checkpoint)) {
+        g_set_error(error,
+                    NM_MANAGER_ERROR,
+                    NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+                    "checkpoint %s does not exist",
+                    path);
+        return NULL;
+    }
+
+    nm_assert(c_list_contains(&self->checkpoints_lst_head, &checkpoint->checkpoints_lst));
+    return checkpoint;
+}
+
+const char **
+nm_checkpoint_manager_get_checkpoint_paths(NMCheckpointManager *self, guint *out_length)
+{
+    NMCheckpoint *checkpoint;
+    const char ** strv;
+    guint         num, i = 0;
+
+    num = c_list_length(&self->checkpoints_lst_head);
+    NM_SET_OUT(out_length, num);
+    if (!num)
+        return NULL;
+
+    strv = g_new(const char *, num + 1);
+    c_list_for_each_entry (checkpoint, &self->checkpoints_lst_head, checkpoints_lst)
+        strv[i++] = nm_dbus_object_get_path(NM_DBUS_OBJECT(checkpoint));
+    nm_assert(i == num);
+    strv[i] = NULL;
+    return strv;
+}
+
+gboolean
+nm_checkpoint_manager_adjust_rollback_timeout(NMCheckpointManager *self,
+                                              const char *         path,
+                                              guint32              add_timeout,
+                                              GError **            error)
+{
+    NMCheckpoint *checkpoint;
+
+    g_return_val_if_fail(self, FALSE);
+    g_return_val_if_fail(path && path[0] == '/', FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    checkpoint = nm_checkpoint_manager_lookup_by_path(self, path, error);
+    if (!checkpoint)
+        return FALSE;
+
+    nm_checkpoint_adjust_rollback_timeout(checkpoint, add_timeout);
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+NMCheckpointManager *
+nm_checkpoint_manager_new(NMManager *manager, GParamSpec *spec)
+{
+    NMCheckpointManager *self;
+
+    g_return_val_if_fail(NM_IS_MANAGER(manager), FALSE);
+
+    self = g_slice_new0(NMCheckpointManager);
+
+    /* the NMCheckpointManager instance is actually owned by NMManager.
+     * Thus, we cannot take a reference to it, and we also don't bother
+     * taking a weak-reference. Instead let GET_MANAGER() assert that
+     * self->_manager is alive -- which we always expect as the lifetime
+     * of NMManager shall surpass the lifetime of the NMCheckpointManager
+     * instance. */
+    self->_manager      = manager;
+    self->property_spec = spec;
+    c_list_init(&self->checkpoints_lst_head);
+    return self;
+}
+
+void
+nm_checkpoint_manager_free(NMCheckpointManager *self)
+{
+    if (!self)
+        return;
+
+    nm_checkpoint_manager_destroy_all(self);
+    g_slice_free(NMCheckpointManager, self);
+}