diff options
Diffstat (limited to 'src/core/nm-checkpoint-manager.c')
| -rw-r--r-- | src/core/nm-checkpoint-manager.c | 349 |
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); +} |