nm-checkpoint-manager.c 10.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/* -*- 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"
28
#include "devices/nm-device.h"
29 30
#include "nm-manager.h"
#include "nm-utils.h"
31
#include "nm-utils/c-list.h"
32 33 34 35 36

/*****************************************************************************/

struct _NMCheckpointManager {
	NMManager *_manager;
37
	GParamSpec *property_spec;
38
	CList checkpoints_lst_head;
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
};

#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; \
	})

/*****************************************************************************/

54 55
#define _NMLOG_DOMAIN      LOGD_CORE
#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "checkpoint", __VA_ARGS__)
56 57 58

/*****************************************************************************/

59 60 61 62 63 64
static void
notify_checkpoints (NMCheckpointManager *self) {
	g_object_notify_by_pspec ((GObject *) GET_MANAGER (self),
	                          self->property_spec);
}

65
static void
66
destroy_checkpoint (NMCheckpointManager *self, NMCheckpoint *checkpoint, gboolean log_destroy)
67
{
68 69 70
	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));
71

72 73
	nm_checkpoint_set_timeout_callback (checkpoint, NULL, NULL);

74
	c_list_unlink (&checkpoint->checkpoints_lst);
75

76 77 78
	if (log_destroy)
		nm_checkpoint_log_destroy (checkpoint);

79 80
	notify_checkpoints (self);

81 82
	nm_dbus_object_unexport (NM_DBUS_OBJECT (checkpoint));
	g_object_unref (checkpoint);
83 84
}

85 86 87 88
static GVariant *
rollback_checkpoint (NMCheckpointManager *self, NMCheckpoint *checkpoint)
{
	GVariant *result;
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
	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);
		}
	}
106 107

	result = nm_checkpoint_rollback (checkpoint);
108
	destroy_checkpoint (self, checkpoint, FALSE);
109 110 111
	return result;
}

112
static void
113 114
rollback_timeout_cb (NMCheckpoint *checkpoint,
                     gpointer user_data)
115
{
116 117
	NMCheckpointManager *self = user_data;
	gs_unref_variant GVariant *result = NULL;
118

119
	result = rollback_checkpoint (self, checkpoint);
120 121 122 123 124 125 126 127 128
}

NMCheckpoint *
nm_checkpoint_manager_create (NMCheckpointManager *self,
                              const char *const *device_paths,
                              guint32 rollback_timeout,
                              NMCheckpointCreateFlags flags,
                              GError **error)
{
129
	NMManager *manager;
130 131 132 133 134 135
	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);
136 137
	manager = GET_MANAGER (self);

138 139
	devices = g_ptr_array_new ();

140
	if (!device_paths || !device_paths[0]) {
141
		const CList *tmp_lst;
142

143
		nm_manager_for_each_device (manager, device, tmp_lst) {
144 145
			if (!nm_device_is_real (device))
				continue;
146 147
			nm_assert (nm_dbus_object_get_path (NM_DBUS_OBJECT (device)));
			g_ptr_array_add (devices, device);
148
		}
149 150 151 152
	} 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;
153 154 155 156 157 158 159 160 161 162 163 164 165 166
	} 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);
167
		}
168 169
	}

170 171 172 173 174 175 176 177
	if (!devices->len) {
		g_set_error_literal (error,
		                     NM_MANAGER_ERROR,
		                     NM_MANAGER_ERROR_INVALID_ARGUMENTS,
		                     "no device available");
		return NULL;
	}

178 179 180 181 182 183
	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) {
184
				g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS,
185 186
				             "device '%s' is already included in checkpoint %s",
				             nm_device_get_iface (device),
187
				             nm_dbus_object_get_path (NM_DBUS_OBJECT (checkpoint)));
188 189 190 191 192
				return NULL;
			}
		}
	}

193
	checkpoint = nm_checkpoint_new (manager, devices, rollback_timeout, flags);
194

195
	nm_dbus_object_export (NM_DBUS_OBJECT (checkpoint));
196

197
	nm_checkpoint_set_timeout_callback (checkpoint, rollback_timeout_cb, self);
198
	c_list_link_tail (&self->checkpoints_lst_head, &checkpoint->checkpoints_lst);
199
	notify_checkpoints (self);
200 201 202
	return checkpoint;
}

203 204
void
nm_checkpoint_manager_destroy_all (NMCheckpointManager *self)
205
{
206
	NMCheckpoint *checkpoint;
207

208
	g_return_if_fail (self);
209

210
	while ((checkpoint = c_list_first_entry (&self->checkpoints_lst_head, NMCheckpoint, checkpoints_lst)))
211
		destroy_checkpoint (self, checkpoint, TRUE);
212 213 214 215
}

gboolean
nm_checkpoint_manager_destroy (NMCheckpointManager *self,
216
                               const char *path,
217 218
                               GError **error)
{
219
	NMCheckpoint *checkpoint;
220 221

	g_return_val_if_fail (self, FALSE);
222
	g_return_val_if_fail (path && path[0] == '/', FALSE);
223 224
	g_return_val_if_fail (!error || !*error, FALSE);

225
	if (!nm_streq (path, "/")) {
226 227 228 229 230 231 232 233 234 235 236 237 238
		nm_checkpoint_manager_destroy_all (self);
		return TRUE;
	}

	checkpoint = nm_checkpoint_manager_lookup_by_path (self, path);
	if (!checkpoint) {
		g_set_error (error,
		             NM_MANAGER_ERROR,
		             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
		             "checkpoint %s does not exist", path);
		return FALSE;
	}

239
	destroy_checkpoint (self, checkpoint, TRUE);
240
	return TRUE;
241 242 243 244
}

gboolean
nm_checkpoint_manager_rollback (NMCheckpointManager *self,
245
                                const char *path,
246 247 248
                                GVariant **results,
                                GError **error)
{
249
	NMCheckpoint *checkpoint;
250 251

	g_return_val_if_fail (self, FALSE);
252
	g_return_val_if_fail (path && path[0] == '/', FALSE);
253 254 255
	g_return_val_if_fail (results, FALSE);
	g_return_val_if_fail (!error || !*error, FALSE);

256
	checkpoint = nm_checkpoint_manager_lookup_by_path (self, path);
257
	if (!checkpoint) {
258
		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
259
		             "checkpoint %s does not exist", path);
260 261 262
		return FALSE;
	}

263
	*results = rollback_checkpoint (self, checkpoint);
264 265 266
	return TRUE;
}

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
NMCheckpoint *
nm_checkpoint_manager_lookup_by_path (NMCheckpointManager *self, const char *path)
{
	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))
		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)
286
{
287
	NMCheckpoint *checkpoint;
288
	const char **strv;
289 290
	guint num, i = 0;

291 292 293
	num = c_list_length (&self->checkpoints_lst_head);
	NM_SET_OUT (out_length, num);
	if (!num)
294 295
		return NULL;

296
	strv = g_new (const char *, num + 1);
297
	c_list_for_each_entry (checkpoint, &self->checkpoints_lst_head, checkpoints_lst)
298
		strv[i++] = nm_dbus_object_get_path (NM_DBUS_OBJECT (checkpoint));
299 300 301 302 303
	nm_assert (i == num);
	strv[i] = NULL;
	return strv;
}

304 305 306
/*****************************************************************************/

NMCheckpointManager *
307
nm_checkpoint_manager_new (NMManager *manager, GParamSpec *spec)
308 309 310 311 312 313 314 315 316 317 318 319 320 321
{
	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;
322
	self->property_spec = spec;
323
	c_list_init (&self->checkpoints_lst_head);
324 325 326 327
	return self;
}

void
328
nm_checkpoint_manager_free (NMCheckpointManager *self)
329 330 331 332
{
	if (!self)
		return;

333
	nm_checkpoint_manager_destroy_all (self);
334 335
	g_slice_free (NMCheckpointManager, self);
}