mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-04 15:40:22 +01:00
Most notably, it renames
c_list_unlink_init() -> c_list_unlink()
c_list_unlink() -> c_list_unlink_stale()
$ sed -e 's/\<c_list_unlink\>/c_list_unlink_old/g' \
-e 's/\<c_list_unlink_init\>/c_list_unlink/g' \
-e 's/\<c_list_unlink_old\>/c_list_unlink_stale/g' \
$(git grep -l c_list_unlink -- ':(exclude)shared/nm-utils/c-list.h') \
-i
374 lines
11 KiB
C
374 lines
11 KiB
C
/* -*- 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 "devices/nm-device.h"
|
|
#include "nm-exported-object.h"
|
|
#include "nm-manager.h"
|
|
#include "nm-utils.h"
|
|
#include "nm-utils/c-list.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
struct _NMCheckpointManager {
|
|
NMManager *_manager;
|
|
GParamSpec *property_spec;
|
|
GHashTable *checkpoints;
|
|
CList list;
|
|
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_DOMAIN LOGD_CORE
|
|
#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "checkpoint", __VA_ARGS__)
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
CList list;
|
|
NMCheckpoint *checkpoint;
|
|
} CheckpointItem;
|
|
|
|
static void update_rollback_timeout (NMCheckpointManager *self);
|
|
|
|
static void
|
|
notify_checkpoints (NMCheckpointManager *self) {
|
|
g_object_notify_by_pspec ((GObject *) GET_MANAGER (self),
|
|
self->property_spec);
|
|
}
|
|
|
|
static void
|
|
item_destroy (gpointer data)
|
|
{
|
|
CheckpointItem *item = data;
|
|
|
|
c_list_unlink_stale (&item->list);
|
|
nm_exported_object_unexport (NM_EXPORTED_OBJECT (item->checkpoint));
|
|
g_object_unref (G_OBJECT (item->checkpoint));
|
|
g_slice_free (CheckpointItem, item);
|
|
}
|
|
|
|
static gboolean
|
|
rollback_timeout_cb (NMCheckpointManager *self)
|
|
{
|
|
CheckpointItem *item, *safe;
|
|
GVariant *result;
|
|
gint64 ts, now;
|
|
const char *path;
|
|
gboolean removed = FALSE;
|
|
|
|
now = nm_utils_get_monotonic_timestamp_ms ();
|
|
|
|
c_list_for_each_entry_safe (item, safe, &self->list, list) {
|
|
ts = nm_checkpoint_get_rollback_ts (item->checkpoint);
|
|
if (ts && ts <= now) {
|
|
result = nm_checkpoint_rollback (item->checkpoint);
|
|
if (result)
|
|
g_variant_unref (result);
|
|
path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (item->checkpoint));
|
|
if (!g_hash_table_remove (self->checkpoints, path))
|
|
nm_assert_not_reached();
|
|
removed = TRUE;
|
|
}
|
|
}
|
|
|
|
self->rollback_timeout_id = 0;
|
|
update_rollback_timeout (self);
|
|
|
|
if (removed)
|
|
notify_checkpoints (self);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
update_rollback_timeout (NMCheckpointManager *self)
|
|
{
|
|
CheckpointItem *item;
|
|
gint64 ts, delta, next = G_MAXINT64;
|
|
|
|
c_list_for_each_entry (item, &self->list, list) {
|
|
ts = nm_checkpoint_get_rollback_ts (item->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)
|
|
{
|
|
CheckpointItem *item;
|
|
|
|
c_list_for_each_entry (item, &self->list, list) {
|
|
if (nm_checkpoint_includes_device (item->checkpoint, device))
|
|
return item->checkpoint;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
NMCheckpoint *
|
|
nm_checkpoint_manager_create (NMCheckpointManager *self,
|
|
const char *const *device_paths,
|
|
guint32 rollback_timeout,
|
|
NMCheckpointCreateFlags flags,
|
|
GError **error)
|
|
{
|
|
NMManager *manager;
|
|
NMCheckpoint *checkpoint;
|
|
CheckpointItem *item;
|
|
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);
|
|
|
|
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);
|
|
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);
|
|
}
|
|
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;
|
|
}
|
|
|
|
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 (!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) {
|
|
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)));
|
|
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));
|
|
|
|
item = g_slice_new0 (CheckpointItem);
|
|
item->checkpoint = checkpoint;
|
|
c_list_link_tail (&self->list, &item->list);
|
|
|
|
if (!nm_g_hash_table_insert (self->checkpoints,
|
|
(gpointer) checkpoint_path,
|
|
item))
|
|
g_return_val_if_reached (NULL);
|
|
|
|
notify_checkpoints (self);
|
|
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);
|
|
notify_checkpoints (self);
|
|
|
|
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) {
|
|
notify_checkpoints (self);
|
|
} else {
|
|
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)
|
|
{
|
|
CheckpointItem *item;
|
|
|
|
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);
|
|
|
|
item = g_hash_table_lookup (self->checkpoints, checkpoint_path);
|
|
if (!item) {
|
|
g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
|
|
"checkpoint %s does not exist", checkpoint_path);
|
|
return FALSE;
|
|
}
|
|
|
|
*results = nm_checkpoint_rollback (item->checkpoint);
|
|
g_hash_table_remove (self->checkpoints, checkpoint_path);
|
|
notify_checkpoints (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
char **
|
|
nm_checkpoint_manager_get_checkpoint_paths (NMCheckpointManager *self)
|
|
{
|
|
CheckpointItem *item;
|
|
char **strv;
|
|
guint num, i = 0;
|
|
|
|
num = g_hash_table_size (self->checkpoints);
|
|
if (!num) {
|
|
nm_assert (c_list_is_empty (&self->list));
|
|
return NULL;
|
|
}
|
|
|
|
strv = g_new (char *, num + 1);
|
|
c_list_for_each_entry (item, &self->list, list)
|
|
strv[i++] = g_strdup (nm_exported_object_get_path (NM_EXPORTED_OBJECT (item->checkpoint)));
|
|
nm_assert (i == num);
|
|
strv[i] = NULL;
|
|
|
|
return strv;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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->checkpoints = g_hash_table_new_full (nm_str_hash, g_str_equal,
|
|
NULL, item_destroy);
|
|
self->property_spec = spec;
|
|
c_list_init (&self->list);
|
|
|
|
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);
|
|
}
|