about summary refs log tree commit diff
path: root/src/nm-checkpoint-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-checkpoint-manager.c')
-rw-r--r--src/nm-checkpoint-manager.c326
1 files changed, 179 insertions, 147 deletions
diff --git a/src/nm-checkpoint-manager.c b/src/nm-checkpoint-manager.c
index 6da220c4..7e8c8aea 100644
--- a/src/nm-checkpoint-manager.c
+++ b/src/nm-checkpoint-manager.c
@@ -26,16 +26,16 @@
 #include "nm-connection.h"
 #include "nm-core-utils.h"
 #include "devices/nm-device.h"
-#include "nm-exported-object.h"
 #include "nm-manager.h"
 #include "nm-utils.h"
+#include "c-list/src/c-list.h"
 
 /*****************************************************************************/
 
 struct _NMCheckpointManager {
 	NMManager *_manager;
-	GHashTable *checkpoints;
-	guint rollback_timeout_id;
+	GParamSpec *property_spec;
+	CList checkpoints_lst_head;
 };
 
 #define GET_MANAGER(self) \
@@ -56,80 +56,67 @@ struct _NMCheckpointManager {
 
 /*****************************************************************************/
 
-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));
+notify_checkpoints (NMCheckpointManager *self) {
+	g_object_notify_by_pspec ((GObject *) GET_MANAGER (self),
+	                          self->property_spec);
 }
 
-static gboolean
-rollback_timeout_cb (NMCheckpointManager *self)
+static void
+destroy_checkpoint (NMCheckpointManager *self, NMCheckpoint *checkpoint, gboolean log_destroy)
 {
-	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);
-		}
-	}
+	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);
 
-	self->rollback_timeout_id = 0;
-	update_rollback_timeout (self);
+	if (log_destroy)
+		nm_checkpoint_log_destroy (checkpoint);
 
-	return G_SOURCE_REMOVE;
+	notify_checkpoints (self);
+
+	nm_dbus_object_unexport (NM_DBUS_OBJECT (checkpoint));
+	g_object_unref (checkpoint);
 }
 
-static void
-update_rollback_timeout (NMCheckpointManager *self)
+static GVariant *
+rollback_checkpoint (NMCheckpointManager *self, NMCheckpoint *checkpoint)
 {
-	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;
-	}
+	GVariant *result;
+	const CList *iter;
 
-	nm_clear_g_source (&self->rollback_timeout_id);
+	nm_assert (c_list_contains (&self->checkpoints_lst_head, &checkpoint->checkpoints_lst));
 
-	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);
+	/* 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 NMCheckpoint *
-find_checkpoint_for_device (NMCheckpointManager *self, NMDevice *device)
+static void
+rollback_timeout_cb (NMCheckpoint *checkpoint,
+                     gpointer user_data)
 {
-	GHashTableIter iter;
-	NMCheckpoint *checkpoint;
+	NMCheckpointManager *self = user_data;
+	gs_unref_variant GVariant *result = NULL;
 
-	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;
+	result = rollback_checkpoint (self, checkpoint);
 }
 
 NMCheckpoint *
@@ -141,152 +128,200 @@ nm_checkpoint_manager_create (NMCheckpointManager *self,
 {
 	NMManager *manager;
 	NMCheckpoint *checkpoint;
-	const char * const *path;
 	gs_unref_ptrarray GPtrArray *devices = NULL;
 	NMDevice *device;
-	const char *checkpoint_path;
-	gs_free const char **device_paths_free = NULL;
-	guint i;
 
 	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 char *device_path;
-		const GSList *iter;
-		GPtrArray *paths;
-
-		paths = g_ptr_array_new ();
-		for (iter = nm_manager_get_devices (manager);
-		     iter;
-		     iter = g_slist_next (iter)) {
-			device = NM_DEVICE (iter->data);
+		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;
-			device_path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (device));
-			if (device_path)
-				g_ptr_array_add (paths, (gpointer) device_path);
+			nm_assert (nm_dbus_object_get_path (NM_DBUS_OBJECT (device)));
+			g_ptr_array_add (devices, device);
 		}
-		g_ptr_array_add (paths, NULL);
-		device_paths_free = (const char **) g_ptr_array_free (paths, FALSE);
-		device_paths = (const char *const *) device_paths_free;
 	} 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);
+		}
 	}
 
-	devices = g_ptr_array_new ();
-	for (path = device_paths; *path; path++) {
-		device = nm_manager_get_device_by_path (manager, *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 (!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)) {
-		for (i = 0; i < devices->len; i++) {
-			device = devices->pdata[i];
-			checkpoint = find_checkpoint_for_device (self, device);
-			if (checkpoint) {
+	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_exported_object_get_path (NM_EXPORTED_OBJECT (checkpoint)));
+				             nm_dbus_object_get_path (NM_DBUS_OBJECT (checkpoint)));
 				return NULL;
 			}
 		}
 	}
 
-	checkpoint = nm_checkpoint_new (manager, devices, rollback_timeout, flags, 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));
+	checkpoint = nm_checkpoint_new (manager, devices, rollback_timeout, flags);
 
-	if (!nm_g_hash_table_insert (self->checkpoints,
-	                             (gpointer) checkpoint_path,
-	                             checkpoint))
-		g_return_val_if_reached (NULL);
-
-	update_rollback_timeout (self);
+	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;
 }
 
-gboolean
-nm_checkpoint_manager_destroy_all (NMCheckpointManager *self,
-                                   GError **error)
+void
+nm_checkpoint_manager_destroy_all (NMCheckpointManager *self)
 {
-	g_return_val_if_fail (self, FALSE);
+	NMCheckpoint *checkpoint;
 
-	g_hash_table_remove_all (self->checkpoints);
+	g_return_if_fail (self);
 
-	return TRUE;
+	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 *checkpoint_path,
+                               const char *path,
                                GError **error)
 {
-	gboolean ret;
+	NMCheckpoint *checkpoint;
 
 	g_return_val_if_fail (self, FALSE);
-	g_return_val_if_fail (checkpoint_path && checkpoint_path[0] == '/', FALSE);
+	g_return_val_if_fail (path && 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);
+	if (nm_streq (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 *checkpoint_path,
+                                const char *path,
                                 GVariant **results,
                                 GError **error)
 {
-	NMCheckpoint *cp;
+	NMCheckpoint *checkpoint;
 
 	g_return_val_if_fail (self, FALSE);
-	g_return_val_if_fail (checkpoint_path && checkpoint_path[0] == '/', 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);
 
-	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);
+	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 = (NMCheckpoint *) 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;
 	}
 
-	*results = nm_checkpoint_rollback (cp);
-	g_hash_table_remove (self->checkpoints, checkpoint_path);
+	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)
+nm_checkpoint_manager_new (NMManager *manager, GParamSpec *spec)
 {
 	NMCheckpointManager *self;
 
@@ -301,20 +336,17 @@ nm_checkpoint_manager_new (NMManager *manager)
 	 * of NMManager shall surpass the lifetime of the NMCheckpointManager
 	 * instance. */
 	self->_manager = manager;
-	self->checkpoints = g_hash_table_new_full (nm_str_hash, g_str_equal,
-	                                           NULL, checkpoint_destroy);
-
+	self->property_spec = spec;
+	c_list_init (&self->checkpoints_lst_head);
 	return self;
 }
 
 void
-nm_checkpoint_manager_unref (NMCheckpointManager *self)
+nm_checkpoint_manager_free (NMCheckpointManager *self)
 {
 	if (!self)
 		return;
 
-	nm_clear_g_source (&self->rollback_timeout_id);
-	g_hash_table_destroy (self->checkpoints);
-
+	nm_checkpoint_manager_destroy_all (self);
 	g_slice_free (NMCheckpointManager, self);
 }