diff options
author | Beniamino Galvani <bgalvani@redhat.com> | 2016-07-01 12:11:01 +0200 |
---|---|---|
committer | Beniamino Galvani <bgalvani@redhat.com> | 2016-08-17 14:55:34 +0200 |
commit | 3e09aed2a09fab11f66b8228e48dc8f732c65cce (patch) | |
tree | 5d9fbc37fe5025b36e0cdf81cba44c15c0e79bdb | |
parent | b9e89c918f13374772a72cefbe0cda6bb6bc88e4 (diff) | |
download | NetworkManager-3e09aed2a09fab11f66b8228e48dc8f732c65cce.tar.gz |
checkpoint: add create, rollback and destroy D-Bus API
Co-authored-by: Thomas Haller <thaller@redhat.com>
-rw-r--r-- | introspection/Makefile.am | 4 | ||||
-rw-r--r-- | introspection/nm-checkpoint.xml | 42 | ||||
-rw-r--r-- | introspection/nm-manager.xml | 41 | ||||
-rw-r--r-- | libnm-core/nm-dbus-interface.h | 34 | ||||
-rw-r--r-- | src/Makefile.am | 6 | ||||
-rw-r--r-- | src/nm-checkpoint-manager.c | 298 | ||||
-rw-r--r-- | src/nm-checkpoint-manager.h | 50 | ||||
-rw-r--r-- | src/nm-checkpoint.c | 433 | ||||
-rw-r--r-- | src/nm-checkpoint.h | 49 | ||||
-rw-r--r-- | src/nm-manager.c | 125 | ||||
-rw-r--r-- | src/nm-manager.h | 2 |
11 files changed, 1082 insertions, 2 deletions
diff --git a/introspection/Makefile.am b/introspection/Makefile.am index 3a627930b9..d4762637a0 100644 --- a/introspection/Makefile.am +++ b/introspection/Makefile.am @@ -17,6 +17,8 @@ nodist_libnmdbus_la_SOURCES = \ nmdbus-active-connection.h \ nmdbus-agent-manager.c \ nmdbus-agent-manager.h \ + nmdbus-checkpoint.c \ + nmdbus-checkpoint.h \ nmdbus-device-adsl.c \ nmdbus-device-adsl.h \ nmdbus-device-bond.c \ @@ -81,6 +83,7 @@ nodist_libnmdbus_la_SOURCES = \ DBUS_INTERFACE_DOCS = \ nmdbus-access-point-org.freedesktop.NetworkManager.AccessPoint.xml \ nmdbus-active-connection-org.freedesktop.NetworkManager.Connection.Active.xml \ + nmdbus-checkpoint-org.freedesktop.NetworkManager.Checkpoint.xml \ nmdbus-device-team-org.freedesktop.NetworkManager.Device.Team.xml \ nmdbus-dhcp6-config-org.freedesktop.NetworkManager.DHCP6Config.xml \ nmdbus-device-wifi-org.freedesktop.NetworkManager.Device.Wireless.xml \ @@ -139,6 +142,7 @@ EXTRA_DIST = \ nm-access-point.xml \ nm-active-connection.xml \ nm-agent-manager.xml \ + nm-checkpoint.xml \ nm-device-adsl.xml \ nm-device-bond.xml \ nm-device-bridge.xml \ diff --git a/introspection/nm-checkpoint.xml b/introspection/nm-checkpoint.xml new file mode 100644 index 0000000000..d0fbda8dec --- /dev/null +++ b/introspection/nm-checkpoint.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/"> + + <!-- + org.freedesktop.NetworkManager.Checkpoint: + + A snapshot of NetworkManager state for a given device list + --> + <interface name="org.freedesktop.NetworkManager.Checkpoint"> + <annotation name="org.gtk.GDBus.C.Name" value="Checkpoint"/> + + <!-- + Devices: + + Array of object paths for devices which are part of this + checkpoint. + --> + <property name="Devices" type="ao" access="read"/> + + <!-- + Created: + + The timestamp (in CLOCK_BOOTTIME milliseconds) of checkpoint creation. + --> + <property name="Created" type="x" access="read"/> + + <!-- + RollbackTimeout: + + Timeout in seconds for automatic rollback, or zero. + --> + <property name="RollbackTimeout" type="u" access="read"/> + + <!-- + PropertiesChanged: + @properties: A dictionary mapping property names to variant boxed values + --> + <signal name="PropertiesChanged"> + <arg name="properties" type="a{sv}"/> + </signal> + </interface> +</node> diff --git a/introspection/nm-manager.xml b/introspection/nm-manager.xml index ea368ba331..95cc16cabe 100644 --- a/introspection/nm-manager.xml +++ b/introspection/nm-manager.xml @@ -206,6 +206,47 @@ </method> <!-- + CheckpointCreate: + + @devices: a list of device paths for which a checkpoint should be created. An empty list means all managed devices. + @rollback_timeout: the time in seconds until NetworkManager will automatically rollback to the checkpoint. Set to zero for infinite. + @flags: optional flags that influence the creation. + @checkpoint: on success, returns the path of the checkpoint. + + Create a checkpoint of the current networking configuration + for given interfaces. If @rollback_timeout is not zero, a + rollback is automatically performed after the given timeout. + --> + <method name="CheckpointCreate"> + <arg name="devices" type="ao" direction="in"/> + <arg name="rollback_timeout" type="u" direction="in"/> + <arg name="flags" type="u" direction="in"/> + <arg name="checkpoint" type="o" direction="out"/> + </method> + + <!-- + CheckpointDestroy: + @checkpoint: the checkpoint to be destroyed. Set to empty to cancel all pending checkpoints. + + Destroy a previously created checkpoint. + --> + <method name="CheckpointDestroy"> + <arg name="checkpoint" type="o" direction="in"/> + </method> + + <!-- + CheckpointRollback: + @checkpoint: the checkpoint to be rolled back. + @result: on return, a dictionary of devices and results. Devices are represented by their original D-Bus path; each result is a <link linkend="NMRollbackResult">RollbackResult</link>. + + Rollback a checkpoint before the timeout is reached. + --> + <method name="CheckpointRollback"> + <arg name="checkpoint" type="o" direction="in"/> + <arg name="result" type="a{su}" direction="out" /> + </method> + + <!-- Devices: The list of realized network devices. Realized devices are those which diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h index 1e0fbe68b6..c7ce110029 100644 --- a/libnm-core/nm-dbus-interface.h +++ b/libnm-core/nm-dbus-interface.h @@ -689,4 +689,38 @@ typedef enum { NM_IP_TUNNEL_MODE_VTI6 = 9, } NMIPTunnelMode; + +/** + * NMCheckpointCreateFlags: + * @NM_CHECKPOINT_CREATE_FLAG_NONE: no flags + * @NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL: when creating + * a new checkpoint, destroy all existing ones. + * + * The flags for CheckpointCreate call + * + * Since: 1.4 + */ +typedef enum { /*< skip >*/ + NM_CHECKPOINT_CREATE_FLAG_NONE = 0, + NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL = 0x01, +} NMCheckpointCreateFlags; + +/** + * NMRollbackResult: + * @NM_ROLLBACK_RESULT_OK: the rollback succeeded. + * @NM_ROLLBACK_RESULT_ERR_NO_DEVICE: the device no longer exists. + * @NM_ROLLBACK_RESULT_ERR_DEVICE_UNMANAGED: the device is now unmanaged. + * @NM_ROLLBACK_RESULT_ERR_FAILED: other errors during rollback. + * + * The result of a checkpoint Rollback() operation for a specific device. + * + * Since: 1.4 + **/ +typedef enum { /*< skip >*/ + NM_ROLLBACK_RESULT_OK = 0, + NM_ROLLBACK_RESULT_ERR_NO_DEVICE = 1, + NM_ROLLBACK_RESULT_ERR_DEVICE_UNMANAGED = 2, + NM_ROLLBACK_RESULT_ERR_FAILED = 3, +} NMRollbackResult; + #endif /* __NM_DBUS_INTERFACE_H__ */ diff --git a/src/Makefile.am b/src/Makefile.am index 41c3169089..c460caff67 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -311,6 +311,12 @@ libNetworkManager_la_SOURCES = \ \ dhcp-manager/nm-dhcp-dhclient-utils.c \ dhcp-manager/nm-dhcp-dhclient-utils.h \ + \ + nm-checkpoint-manager.c \ + nm-checkpoint-manager.h \ + nm-checkpoint.c \ + nm-checkpoint.h \ + \ devices/nm-device.c \ devices/nm-device.h \ devices/nm-lldp-listener.c \ diff --git a/src/nm-checkpoint-manager.c b/src/nm-checkpoint-manager.c new file mode 100644 index 0000000000..254dc31e3d --- /dev/null +++ b/src/nm-checkpoint-manager.c @@ -0,0 +1,298 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-checkpoint-manager.h" + +#include "nm-checkpoint.h" +#include "nm-connection.h" +#include "nm-core-utils.h" +#include "nm-device.h" +#include "nm-exported-object.h" +#include "nm-manager.h" +#include "nm-utils.h" + +/*****************************************************************************/ + +struct _NMCheckpointManager { + NMManager *_manager; + GHashTable *checkpoints; + guint rollback_timeout_id; +}; + +#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_PREFIX_NAME "checkpoint" +#define _NMLOG_DOMAIN LOGD_CORE + +#define _NMLOG(level, ...) \ + nm_log (level, _NMLOG_DOMAIN, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static void update_rollback_timeout (NMCheckpointManager *self); + +static void +checkpoint_destroy (gpointer checkpoint) +{ + nm_exported_object_unexport (NM_EXPORTED_OBJECT (checkpoint)); + g_object_unref (G_OBJECT (checkpoint)); +} + +static gboolean +rollback_timeout_cb (NMCheckpointManager *self) +{ + NMCheckpoint *checkpoint; + GHashTableIter iter; + GVariant *result; + gint64 ts, now; + + now = nm_utils_get_monotonic_timestamp_ms (); + + g_hash_table_iter_init (&iter, self->checkpoints); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &checkpoint)) { + ts = nm_checkpoint_get_rollback_ts (checkpoint); + if (ts && ts <= now) { + result = nm_checkpoint_rollback (checkpoint); + if (result) + g_variant_unref (result); + g_hash_table_iter_remove (&iter); + } + } + + self->rollback_timeout_id = 0; + update_rollback_timeout (self); + + return G_SOURCE_REMOVE; +} + +static void +update_rollback_timeout (NMCheckpointManager *self) +{ + NMCheckpoint *checkpoint; + GHashTableIter iter; + gint64 ts, delta, next = G_MAXINT64; + + g_hash_table_iter_init (&iter, self->checkpoints); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &checkpoint)) { + ts = nm_checkpoint_get_rollback_ts (checkpoint); + if (ts && ts < next) + next = ts; + } + + nm_clear_g_source (&self->rollback_timeout_id); + + if (next != G_MAXINT64) { + delta = MAX (next - nm_utils_get_monotonic_timestamp_ms (), 0); + self->rollback_timeout_id = g_timeout_add (delta, + (GSourceFunc) rollback_timeout_cb, + self); + _LOGT ("update timeout: next check in %" G_GINT64_FORMAT " ms", delta); + } +} + +static NMCheckpoint * +find_checkpoint_for_device (NMCheckpointManager *self, NMDevice *device) +{ + GHashTableIter iter; + NMCheckpoint *checkpoint; + + g_hash_table_iter_init (&iter, self->checkpoints); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &checkpoint)) { + if (nm_checkpoint_includes_device (checkpoint, device)) + return checkpoint; + } + + return NULL; +} + +NMCheckpoint * +nm_checkpoint_manager_create (NMCheckpointManager *self, + const char *const *device_paths, + guint32 rollback_timeout, + NMCheckpointCreateFlags flags, + GError **error) +{ + NMCheckpoint *checkpoint; + const char * const *path; + gs_unref_ptrarray GPtrArray *devices = NULL; + NMDevice *device; + const char *checkpoint_path; + guint i; + + g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + devices = g_ptr_array_new (); + for (path = device_paths; *path; path++) { + device = nm_manager_get_device_by_path (GET_MANAGER (self), *path); + if (!device) { + g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "device %s does not exist", *path); + return NULL; + } + g_ptr_array_add (devices, device); + } + + if (!NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL)) { + for (i = 0; i < devices->len; i++) { + device = devices->pdata[i]; + if (find_checkpoint_for_device (self, device)) { + g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "a checkpoint for device '%s' already exists", + nm_device_get_iface (device)); + return NULL; + } + } + } + + checkpoint = nm_checkpoint_new (GET_MANAGER (self), devices, + rollback_timeout, error); + if (!checkpoint) + return NULL; + + if (NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL)) + g_hash_table_remove_all (self->checkpoints); + + nm_exported_object_export (NM_EXPORTED_OBJECT (checkpoint)); + checkpoint_path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (checkpoint)); + + if (!nm_g_hash_table_insert (self->checkpoints, + (gpointer) checkpoint_path, + checkpoint)) + g_return_val_if_reached (NULL); + + update_rollback_timeout (self); + + return checkpoint; +} + +gboolean +nm_checkpoint_manager_destroy_all (NMCheckpointManager *self, + GError **error) +{ + g_return_val_if_fail (self, FALSE); + + g_hash_table_remove_all (self->checkpoints); + + return TRUE; +} + +gboolean +nm_checkpoint_manager_destroy (NMCheckpointManager *self, + const char *checkpoint_path, + GError **error) +{ + gboolean ret; + + g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (checkpoint_path && checkpoint_path[0] == '/', FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + if (!nm_streq (checkpoint_path, "/")) { + ret = g_hash_table_remove (self->checkpoints, checkpoint_path); + if (!ret) { + g_set_error (error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "checkpoint %s does not exist", checkpoint_path); + } + return ret; + } else + return nm_checkpoint_manager_destroy_all (self, error); +} + +gboolean +nm_checkpoint_manager_rollback (NMCheckpointManager *self, + const char *checkpoint_path, + GVariant **results, + GError **error) +{ + NMCheckpoint *cp; + + g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (checkpoint_path && checkpoint_path[0] == '/', FALSE); + g_return_val_if_fail (results, FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + cp = g_hash_table_lookup (self->checkpoints, checkpoint_path); + if (!cp) { + g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, + "checkpoint %s does not exist", checkpoint_path); + return FALSE; + } + + *results = nm_checkpoint_rollback (cp); + g_hash_table_remove (self->checkpoints, checkpoint_path); + + return TRUE; +} + +/*****************************************************************************/ + +NMCheckpointManager * +nm_checkpoint_manager_new (NMManager *manager) +{ + 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->checkpoints = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, checkpoint_destroy); + + return self; +} + +void +nm_checkpoint_manager_unref (NMCheckpointManager *self) +{ + if (!self) + return; + + nm_clear_g_source (&self->rollback_timeout_id); + g_hash_table_destroy (self->checkpoints); + + g_slice_free (NMCheckpointManager, self); +} + diff --git a/src/nm-checkpoint-manager.h b/src/nm-checkpoint-manager.h new file mode 100644 index 0000000000..022b1a80e7 --- /dev/null +++ b/src/nm-checkpoint-manager.h @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NM_CHECKPOINT_MANAGER_H__ +#define __NM_CHECKPOINT_MANAGER_H__ + +#include "nm-dbus-interface.h" +#include "nm-checkpoint.h" + +typedef struct _NMCheckpointManager NMCheckpointManager; + +NMCheckpointManager *nm_checkpoint_manager_new (NMManager *manager); +void nm_checkpoint_manager_unref (NMCheckpointManager *self); + +NMCheckpoint *nm_checkpoint_manager_create (NMCheckpointManager *self, + const char *const*device_names, + guint32 rollback_timeout, + NMCheckpointCreateFlags flags, + GError **error); + +gboolean nm_checkpoint_manager_destroy_all (NMCheckpointManager *self, + GError **error); + +gboolean nm_checkpoint_manager_destroy (NMCheckpointManager *self, + const char *checkpoint_path, + GError **error); +gboolean nm_checkpoint_manager_rollback (NMCheckpointManager *self, + const char *checkpoint_path, + GVariant **results, + GError **error); + +#endif /* __NM_CHECKPOINT_MANAGER_H__ */ + diff --git a/src/nm-checkpoint.c b/src/nm-checkpoint.c new file mode 100644 index 0000000000..cb1adc3973 --- /dev/null +++ b/src/nm-checkpoint.c @@ -0,0 +1,433 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "nm-default.h" +#include "nm-checkpoint.h" + +#include <string.h> + +#include "nm-auth-subject.h" +#include "nm-core-utils.h" +#include "nm-dbus-interface.h" +#include "nm-device.h" +#include "nm-manager.h" +#include "nm-settings.h" +#include "nm-settings-connection.h" +#include "nm-simple-connection.h" +#include "nm-utils.h" +#include "nmdbus-checkpoint.h" + +#define _NMLOG_PREFIX_NAME "checkpoint" +#define _NMLOG_DOMAIN LOGD_CORE + +#define _NMLOG(level, ...) \ + G_STMT_START { \ + if (nm_logging_enabled (level, _NMLOG_DOMAIN)) { \ + char __prefix[32]; \ + \ + if (self) \ + g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", ""_NMLOG_PREFIX_NAME"", (self)); \ + else \ + g_strlcpy (__prefix, _NMLOG_PREFIX_NAME, sizeof (__prefix)); \ + _nm_log ((level), (_NMLOG_DOMAIN), 0, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } G_STMT_END + +typedef struct { + char *original_dev_path; + NMDevice *device; + NMConnection *connection; +} DeviceCheckpoint; + +typedef struct { + /* properties */ + GHashTable *devices; + gint64 created; + guint32 rollback_timeout; + /* private members */ + NMManager *manager; + gint64 rollback_ts; +} NMCheckpointPrivate; + +struct _NMCheckpoint { + NMExportedObject parent; + NMCheckpointPrivate priv; +}; + +typedef struct { + NMExportedObjectClass parent; +} NMCheckpointClass; + +G_DEFINE_TYPE (NMCheckpoint, nm_checkpoint, NM_TYPE_EXPORTED_OBJECT) + +#define NM_CHECKPOINT_GET_PRIVATE(self) \ + ({ \ + /* preserve the const-ness of self. Unfortunately, that + * way, @self cannot be a void pointer */ \ + typeof (self) _self = (self); \ + \ + /* Get compiler error if variable is of wrong type */ \ + _nm_unused const NMCheckpoint *_self2 = (_self); \ + \ + nm_assert (NM_IS_CHECKPOINT (_self)); \ + &_self->priv; \ + }) + +NM_GOBJECT_PROPERTIES_DEFINE_BASE ( + PROP_DEVICES, + PROP_CREATED, + PROP_ROLLBACK_TIMEOUT, +); + +guint64 +nm_checkpoint_get_rollback_ts (NMCheckpoint *self) +{ + g_return_val_if_fail (NM_IS_CHECKPOINT (self), 0); + + return NM_CHECKPOINT_GET_PRIVATE (self)->rollback_ts; +} + +gboolean +nm_checkpoint_includes_device (NMCheckpoint *self, NMDevice *device) +{ + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self); + + return g_hash_table_contains (priv->devices, device); +} + +GVariant * +nm_checkpoint_rollback (NMCheckpoint *self) +{ + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self); + DeviceCheckpoint *dev_checkpoint; + GHashTableIter iter; + NMSettingsConnection *connection; + NMDevice *device; + GError *local_error = NULL; + GVariantBuilder builder; + + _LOGI ("rollback of %s", nm_exported_object_get_path ((NMExportedObject *) self)); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{su}")); + + /* Start rolling-back each device */ + g_hash_table_iter_init (&iter, priv->devices); + while (g_hash_table_iter_next (&iter, (gpointer *) &device, (gpointer *) &dev_checkpoint)) { + gs_unref_object NMAuthSubject *subject = NULL; + guint32 result = NM_ROLLBACK_RESULT_OK; + const char *con_path; + + _LOGD ("rollback: restoring state of device %s", nm_device_get_iface (device)); + + if (!nm_device_is_real (device)) { + result = NM_ROLLBACK_RESULT_ERR_NO_DEVICE; + _LOGD ("rollback: device is not realized"); + goto next_dev; + } + + if (nm_device_get_state (device) <= NM_DEVICE_STATE_UNMANAGED) { + result = NM_ROLLBACK_RESULT_ERR_DEVICE_UNMANAGED; + _LOGD ("rollback: device is unmanaged"); + goto next_dev; + } + + if (dev_checkpoint->connection) { + /* The device had an active connection, check if the + * connection still exists + * */ + con_path = nm_connection_get_path (dev_checkpoint->connection); + connection = nm_settings_get_connection_by_path (nm_settings_get(), con_path); + + if (connection) { + /* If the connection is still there, restore its content + * and save it + * */ + _LOGD ("rollback: connection %s still exists", con_path); + + nm_connection_replace_settings_from_connection (NM_CONNECTION (connection), + dev_checkpoint->connection); + nm_settings_connection_commit_changes (connection, + NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE, + NULL, + NULL); + } else { + /* The connection was deleted, recreate it */ + _LOGD ("rollback: adding connection %s again", con_path); + + connection = nm_settings_add_connection (nm_settings_get (), + dev_checkpoint->connection, + TRUE, + &local_error); + if (!connection) { + _LOGD ("rollback: connection add failure: %s", local_error->message); + g_clear_error (&local_error); + result = NM_ROLLBACK_RESULT_ERR_FAILED; + goto next_dev; + } + } + + /* Now re-activate the connection */ + subject = nm_auth_subject_new_internal (); + if (!nm_manager_activate_connection (priv->manager, + connection, + NULL, + device, + subject, + &local_error)) { + _LOGW ("rollback: reactivation of connection %s/%s failed: %s", + nm_connection_get_id ((NMConnection *) connection), + nm_connection_get_uuid ((NMConnection * ) connection), + local_error->message); + g_clear_error (&local_error); + result = NM_ROLLBACK_RESULT_ERR_FAILED; + goto next_dev; + } + } else { + /* The device was initially disconnected, deactivate any existing connection */ + _LOGD ("rollback: disconnecting device"); + + if ( nm_device_get_state (device) > NM_DEVICE_STATE_DISCONNECTED + && nm_device_get_state (device) < NM_DEVICE_STATE_DEACTIVATING) { + nm_device_state_changed (device, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + } + } + +next_dev: + g_variant_builder_add (&builder, "{su}", dev_checkpoint->original_dev_path, result); + } + + return g_variant_new ("(a{su})", &builder); +} + +static DeviceCheckpoint * +device_checkpoint_create (NMDevice *device, + GError **error) +{ + DeviceCheckpoint *dev_checkpoint; + NMConnection *connection; + const char *path; + + if (!nm_device_is_real (device)) { + g_set_error (error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "device '%s' is not realized", + nm_device_get_iface (device)); + return NULL; + } + + if (nm_device_get_state (device) <= NM_DEVICE_STATE_UNMANAGED) { + g_set_error (error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "device '%s' is unmanaged", + nm_device_get_iface (device)); + return NULL; + } + + path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (device)); + + dev_checkpoint = g_slice_new0 (DeviceCheckpoint); + dev_checkpoint->device = g_object_ref (device); + dev_checkpoint->original_dev_path = g_strdup (path); + + connection = nm_device_get_applied_connection (device); + if (connection) + dev_checkpoint->connection = nm_simple_connection_new_clone (connection); + + return dev_checkpoint; +} + +static void +device_checkpoint_destroy (gpointer data) +{ + DeviceCheckpoint *dev_checkpoint = data; + + g_clear_object (&dev_checkpoint->connection); + g_clear_object (&dev_checkpoint->device); + g_free (dev_checkpoint->original_dev_path); + + g_slice_free (DeviceCheckpoint, dev_checkpoint); +} + +static void +nm_checkpoint_init (NMCheckpoint *self) +{ + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self); + + priv->devices = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, device_checkpoint_destroy); +} + +static void +get_all_devices (NMManager *manager, GPtrArray *devices) +{ + const GSList *list, *iter; + NMDevice *dev; + + list = nm_manager_get_devices (manager); + + for (iter = list; iter; iter = g_slist_next (iter)) { + dev = iter->data; + + if (!nm_device_is_real (dev)) + continue; + if (nm_device_get_state (dev) <= NM_DEVICE_STATE_UNMANAGED) + continue; + /* We never touch assumed connections, unless told explicitly */ + if (nm_device_uses_assumed_connection (dev)) + continue; + + g_ptr_array_add (devices, dev); + } +} + +NMCheckpoint * +nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_timeout, + GError **error) +{ + NMCheckpoint *self; + NMCheckpointPrivate *priv; + DeviceCheckpoint *dev_checkpoint; + NMDevice *device; + guint i; + + g_return_val_if_fail (manager, NULL); + g_return_val_if_fail (devices, NULL); + g_return_val_if_fail (!error || !*error, NULL); + + if (!devices->len) + get_all_devices (manager, devices); + + if (!devices->len) { + g_set_error_literal (error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "no device available"); + return NULL; + } + + self = g_object_new (NM_TYPE_CHECKPOINT, NULL); + + priv = NM_CHECKPOINT_GET_PRIVATE (self); + priv->manager = manager; + priv->created = nm_utils_monotonic_timestamp_as_boottime (nm_utils_get_monotonic_timestamp_ms (), + NM_UTILS_NS_PER_MSEC); + priv->rollback_timeout = rollback_timeout; + priv->rollback_ts = rollback_timeout ? + (nm_utils_get_monotonic_timestamp_ms () + ((gint64) rollback_timeout * 1000)) : + 0; + + for (i = 0; i < devices->len; i++) { + device = (NMDevice *) devices->pdata[i]; + dev_checkpoint = device_checkpoint_create (device, error); + if (!dev_checkpoint) { + g_object_unref (self); + return NULL; + } + g_hash_table_insert (priv->devices, device, dev_checkpoint); + } + + return self; +} + +static void +dispose (GObject *object) +{ + NMCheckpoint *self = NM_CHECKPOINT (object); + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self); + + g_clear_pointer (&priv->devices, g_hash_table_unref); + + G_OBJECT_CLASS (nm_checkpoint_parent_class)->dispose (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMCheckpoint *self = NM_CHECKPOINT (object); + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self); + gs_free_slist GSList *devices = NULL; + GHashTableIter iter; + NMDevice *device; + + switch (prop_id) { + case PROP_DEVICES: + g_hash_table_iter_init (&iter, priv->devices); + while (g_hash_table_iter_next (&iter, (gpointer *) &device, NULL)) + devices = g_slist_append (devices, device); + nm_utils_g_value_set_object_path_array (value, devices, NULL, NULL); + break; + case PROP_CREATED: + g_value_set_int64 (value, priv->created); + break; + case PROP_ROLLBACK_TIMEOUT: + g_value_set_uint (value, priv->rollback_timeout); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_checkpoint_class_init (NMCheckpointClass *checkpoint_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (checkpoint_class); + NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (checkpoint_class); + + g_type_class_add_private (checkpoint_class, sizeof (NMCheckpointPrivate)); + + exported_object_class->export_path = NM_DBUS_PATH "/Checkpoint/%u"; + exported_object_class->export_on_construction = FALSE; + + /* virtual methods */ + object_class->dispose = dispose; + object_class->get_property = get_property; + + /* properties */ + obj_properties[PROP_DEVICES] = + g_param_spec_boxed (NM_CHECKPOINT_DEVICES, "", "", + G_TYPE_STRV, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CREATED] = + g_param_spec_int64 (NM_CHECKPOINT_CREATED, "", "", + G_MININT64, G_MAXINT64, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ROLLBACK_TIMEOUT] = + g_param_spec_uint (NM_CHECKPOINT_ROLLBACK_TIMEOUT, "", "", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (checkpoint_class), + NMDBUS_TYPE_CHECKPOINT_SKELETON, + NULL); +} diff --git a/src/nm-checkpoint.h b/src/nm-checkpoint.h new file mode 100644 index 0000000000..c7d2d42f35 --- /dev/null +++ b/src/nm-checkpoint.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_CHECKPOINT_H__ +#define __NETWORKMANAGER_CHECKPOINT_H__ + +#include "nm-exported-object.h" +#include "nm-dbus-interface.h" + +#define NM_TYPE_CHECKPOINT (nm_checkpoint_get_type ()) +#define NM_CHECKPOINT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_CHECKPOINT, NMCheckpoint)) +#define NM_CHECKPOINT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_CHECKPOINT, NMCheckpointClass)) +#define NM_IS_CHECKPOINT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_CHECKPOINT)) +#define NM_IS_CHECKPOINT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_CHECKPOINT)) +#define NM_CHECKPOINT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_CHECKPOINT, NMCheckpointClass)) + +typedef struct _NMCheckpoint NMCheckpoint; + +#define NM_CHECKPOINT_DEVICES "devices" +#define NM_CHECKPOINT_CREATED "created" +#define NM_CHECKPOINT_ROLLBACK_TIMEOUT "rollback-timeout" + +GType nm_checkpoint_get_type (void); + +NMCheckpoint *nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_timeout, + GError **error); + +guint64 nm_checkpoint_get_rollback_ts (NMCheckpoint *checkpoint); +gboolean nm_checkpoint_includes_device (NMCheckpoint *checkpoint, NMDevice *device); +GVariant *nm_checkpoint_rollback (NMCheckpoint *self); + +#endif /* __NETWORKMANAGER_CHECKPOINT_H__ */ diff --git a/src/nm-manager.c b/src/nm-manager.c index b3576b02fd..9aa5c1360d 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -53,6 +53,8 @@ #include "nm-config.h" #include "nm-audit-manager.h" #include "nm-dbus-compat.h" +#include "nm-checkpoint.h" +#include "nm-checkpoint-manager.h" #include "NetworkManagerUtils.h" #include "nmdbus-manager.h" @@ -119,6 +121,8 @@ typedef struct { } prop_filter; NMRfkillManager *rfkill_mgr; + NMCheckpointManager *checkpoint_mgr; + NMSettings *settings; char *hostname; @@ -578,7 +582,7 @@ impl_manager_reload (NMManager *self, /************************************************************************/ -static NMDevice * +NMDevice * nm_manager_get_device_by_path (NMManager *manager, const char *path) { GSList *iter; @@ -5122,6 +5126,116 @@ _set_prop_filter (NMManager *self, GDBusConnection *connection) /******************************************************************************/ +static NMCheckpointManager * +_checkpoint_mgr_get (NMManager *self, gboolean create_as_needed) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); + + if (G_UNLIKELY (!priv->checkpoint_mgr) && create_as_needed) + priv->checkpoint_mgr = nm_checkpoint_manager_new (self); + return priv->checkpoint_mgr; +} + +static void +impl_manager_checkpoint_create (NMManager *self, + GDBusMethodInvocation *context, + const char *const *devices, + guint32 rollback_timeout, + guint32 flags) +{ + NMManagerPrivate *priv; + NMCheckpoint *checkpoint; + GError *error = NULL; + const char *path; + + G_STATIC_ASSERT_EXPR (sizeof (flags) <= sizeof (NMCheckpointCreateFlags)); + g_return_if_fail (NM_IS_MANAGER (self)); + priv = NM_MANAGER_GET_PRIVATE (self); + + if (!nm_bus_manager_ensure_root (priv->dbus_mgr, + context, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED)) + return; + + checkpoint = nm_checkpoint_manager_create (_checkpoint_mgr_get (self, TRUE), + (const char *const *) devices, + rollback_timeout, + (NMCheckpointCreateFlags) flags, + &error); + + if (!checkpoint) { + g_dbus_method_invocation_take_error (context, error); + return; + } + + path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (checkpoint)); + g_dbus_method_invocation_return_value (context, g_variant_new ("(o)", path)); +} + +static void +impl_manager_checkpoint_destroy (NMManager *self, + GDBusMethodInvocation *context, + const char *checkpoint_path) +{ + NMManagerPrivate *priv; + GError *error = NULL; + gboolean r; + + g_return_if_fail (NM_IS_MANAGER (self)); + priv = NM_MANAGER_GET_PRIVATE (self); + + if (!nm_bus_manager_ensure_root (priv->dbus_mgr, + context, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED)) + return; + + r = nm_checkpoint_manager_destroy (_checkpoint_mgr_get (self, TRUE), + checkpoint_path, &error); + + if (!r) { + g_dbus_method_invocation_take_error (context, error); + return; + } + + g_dbus_method_invocation_return_value (context, NULL); +} + +static void +impl_manager_checkpoint_rollback (NMManager *self, + GDBusMethodInvocation *context, + const char *checkpoint_path) +{ + NMManagerPrivate *priv; + GError *error = NULL; + GVariant *results; + gboolean r; + + g_return_if_fail (NM_IS_MANAGER (self)); + priv = NM_MANAGER_GET_PRIVATE (self); + + if (!nm_bus_manager_ensure_root (priv->dbus_mgr, + context, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED)) + return; + + r = nm_checkpoint_manager_rollback (_checkpoint_mgr_get (self, TRUE), + checkpoint_path, + &results, + &error); + + if (!r) { + g_dbus_method_invocation_take_error (context, error); + return; + } + + g_dbus_method_invocation_return_value (context, results); +} + +/******************************************************************************/ + static void auth_mgr_changed (NMAuthManager *auth_manager, gpointer user_data) { @@ -5424,7 +5538,6 @@ nm_manager_init (NMManager *self) G_CALLBACK (auth_mgr_changed), self); - /* Monitor the firmware directory */ if (strlen (KERNEL_FIRMWARE_DIR)) { file = g_file_new_for_path (KERNEL_FIRMWARE_DIR "/"); @@ -5603,6 +5716,11 @@ dispose (GObject *object) g_slist_free_full (priv->auth_chains, (GDestroyNotify) nm_auth_chain_unref); priv->auth_chains = NULL; + if (priv->checkpoint_mgr) { + nm_checkpoint_manager_destroy_all (priv->checkpoint_mgr, NULL); + g_clear_pointer (&priv->checkpoint_mgr, nm_checkpoint_manager_unref); + } + if (priv->auth_mgr) { g_signal_handlers_disconnect_by_func (priv->auth_mgr, G_CALLBACK (auth_mgr_changed), @@ -5936,6 +6054,9 @@ nm_manager_class_init (NMManagerClass *manager_class) "GetLogging", impl_manager_get_logging, "CheckConnectivity", impl_manager_check_connectivity, "state", impl_manager_get_state, + "CheckpointCreate", impl_manager_checkpoint_create, + "CheckpointDestroy", impl_manager_checkpoint_destroy, + "CheckpointRollback", impl_manager_checkpoint_rollback, NULL); } diff --git a/src/nm-manager.h b/src/nm-manager.h index d436f0516b..d7aabff16b 100644 --- a/src/nm-manager.h +++ b/src/nm-manager.h @@ -91,6 +91,8 @@ const GSList * nm_manager_get_devices (NMManager *manager); NMDevice * nm_manager_get_device_by_ifindex (NMManager *manager, int ifindex); +NMDevice * nm_manager_get_device_by_path (NMManager *manager, + const char *path); char * nm_manager_get_connection_iface (NMManager *self, NMConnection *connection, |