mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-02-27 22:10:40 +01:00
When we have a bridge interface with ports attached externally (that is,
not by NetworkManager itself), then it can make sense that during
checkpoint rollback we want to keep those ports attached.
During rollback, we may need to deactivate the bridge device and
re-activate it. Implement this, by setting a flag before deactivating,
which prevents external ports to be detached. The flag gets cleared,
when the device state changes to activated (the following activation)
or unmanaged.
This is an ugly solution, for several reasons.
For one, NMDevice tracks its ports in the "slaves" list. But what
it does is ugly. There is no clear concept to understand what it
actually tacks. For example, it tracks externally added interfaces
(nm_device_sys_iface_state_is_external()) that are attached while
not being connected. But it also tracks interfaces that we want to attach
during activation (but which are not yet actually enslaved). It also tracks
slaves that have no actual netdev device (OVS). So it's not clear what this
list contains and what it should contain at any point in time. When we skip
the change of the slaves states during nm_device_master_release_slaves_all(),
it's not really clear what the effects are. It's ugly, but probably correct
enough. What would be better, if we had a clear purpose of what the
lists (or several lists) mean. E.g. a list of all ports that are
currently, physically attached vs. a list of ports we want to attach vs.
a list of OVS slaves that have no actual netdev device.
Another problem is that we attach state on the device
("activation_state_preserve_external_ports"), which should linger there
during the deactivation and reactivation. How can we be sure that we don't
leave that flag dangling there, and that the desired following activation
is the one we cared about? If the follow-up activation fails short (e.g. an
unmanaged command comes first), will we properly disconnect the slaves?
Should we even? In practice, it might be correct enough.
Also, we only implement this for bridges. I think this is where it makes
the most sense. And after all, it's an odd thing to preserve unknown,
external things during a rollback -- unknown, because we have no knowledge
about why these ports are attached and what to do with them.
Also, the change doesn't remember the ports that were attached when the
checkpoint was created. Instead, we preserve all ports that are attached
during rollback. That seems more useful and easier to implement. So we
don't actually rollback to the configuration when the checkpoint was
created. Instead, we rollback, but keep external devices.
Also, we do this now by default and introduce a flag to get the previous
behavior.
https://bugzilla.redhat.com/show_bug.cgi?id=2035519
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/ # 909
(cherry picked from commit 98b3056604)
793 lines
31 KiB
C
793 lines
31 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* Copyright (C) 2016 Red Hat, Inc.
|
|
*/
|
|
|
|
#include "src/core/nm-default-daemon.h"
|
|
|
|
#include "nm-checkpoint.h"
|
|
|
|
#include "nm-active-connection.h"
|
|
#include "nm-act-request.h"
|
|
#include "libnm-core-aux-intern/nm-auth-subject.h"
|
|
#include "nm-core-utils.h"
|
|
#include "nm-dbus-interface.h"
|
|
#include "devices/nm-device.h"
|
|
#include "nm-manager.h"
|
|
#include "settings/nm-settings.h"
|
|
#include "settings/nm-settings-connection.h"
|
|
#include "nm-simple-connection.h"
|
|
#include "nm-utils.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
char *original_dev_path;
|
|
char *original_dev_name;
|
|
NMDeviceType dev_type;
|
|
NMDevice *device;
|
|
NMConnection *applied_connection;
|
|
NMConnection *settings_connection;
|
|
guint64 ac_version_id;
|
|
NMDeviceState state;
|
|
bool is_software : 1;
|
|
bool realized : 1;
|
|
bool activation_lifetime_bound_to_profile_visibility : 1;
|
|
NMUnmanFlagOp unmanaged_explicit;
|
|
NMActivationReason activation_reason;
|
|
gulong dev_exported_change_id;
|
|
} DeviceCheckpoint;
|
|
|
|
NM_GOBJECT_PROPERTIES_DEFINE(NMCheckpoint, PROP_DEVICES, PROP_CREATED, PROP_ROLLBACK_TIMEOUT, );
|
|
|
|
struct _NMCheckpointPrivate {
|
|
/* properties */
|
|
GHashTable *devices;
|
|
GPtrArray *removed_devices;
|
|
gint64 created_at_ms;
|
|
guint32 rollback_timeout_s;
|
|
guint timeout_id;
|
|
/* private members */
|
|
NMManager *manager;
|
|
NMCheckpointCreateFlags flags;
|
|
GHashTable *connection_uuids;
|
|
gulong dev_removed_id;
|
|
|
|
NMCheckpointTimeoutCallback timeout_cb;
|
|
gpointer timeout_data;
|
|
};
|
|
|
|
struct _NMCheckpointClass {
|
|
NMDBusObjectClass parent;
|
|
};
|
|
|
|
G_DEFINE_TYPE(NMCheckpoint, nm_checkpoint, NM_TYPE_DBUS_OBJECT)
|
|
|
|
#define NM_CHECKPOINT_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMCheckpoint, NM_IS_CHECKPOINT)
|
|
|
|
/*****************************************************************************/
|
|
|
|
#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, \
|
|
NULL, \
|
|
NULL, \
|
|
"%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
|
|
__prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
|
|
} \
|
|
} \
|
|
G_STMT_END
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
nm_checkpoint_log_destroy(NMCheckpoint *self)
|
|
{
|
|
_LOGI("destroy %s", nm_dbus_object_get_path(NM_DBUS_OBJECT(self)));
|
|
}
|
|
|
|
void
|
|
nm_checkpoint_set_timeout_callback(NMCheckpoint *self,
|
|
NMCheckpointTimeoutCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self);
|
|
|
|
/* in glib world, we would have a GSignal for this. But as there
|
|
* is only one subscriber, it's simpler to just set and unset(!)
|
|
* the callback this way. */
|
|
priv->timeout_cb = callback;
|
|
priv->timeout_data = user_data;
|
|
}
|
|
|
|
NMDevice *
|
|
nm_checkpoint_includes_devices(NMCheckpoint *self, NMDevice *const *devices, guint n_devices)
|
|
{
|
|
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self);
|
|
guint i;
|
|
|
|
for (i = 0; i < n_devices; i++) {
|
|
if (g_hash_table_contains(priv->devices, devices[i]))
|
|
return devices[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
NMDevice *
|
|
nm_checkpoint_includes_devices_of(NMCheckpoint *self, NMCheckpoint *cp_for_devices)
|
|
{
|
|
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self);
|
|
NMCheckpointPrivate *priv2 = NM_CHECKPOINT_GET_PRIVATE(cp_for_devices);
|
|
GHashTableIter iter;
|
|
NMDevice *device;
|
|
|
|
g_hash_table_iter_init(&iter, priv2->devices);
|
|
while (g_hash_table_iter_next(&iter, (gpointer *) &device, NULL)) {
|
|
if (g_hash_table_contains(priv->devices, device))
|
|
return device;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static NMSettingsConnection *
|
|
find_settings_connection(NMCheckpoint *self,
|
|
DeviceCheckpoint *dev_checkpoint,
|
|
gboolean *need_update,
|
|
gboolean *need_activation)
|
|
{
|
|
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self);
|
|
NMActiveConnection *active;
|
|
NMSettingsConnection *sett_conn;
|
|
const char *uuid, *ac_uuid;
|
|
const CList *tmp_clist;
|
|
|
|
*need_activation = FALSE;
|
|
*need_update = FALSE;
|
|
|
|
uuid = nm_connection_get_uuid(dev_checkpoint->settings_connection);
|
|
sett_conn = nm_settings_get_connection_by_uuid(NM_SETTINGS_GET, uuid);
|
|
|
|
if (!sett_conn)
|
|
return NULL;
|
|
|
|
/* Now check if the connection changed, ... */
|
|
if (!nm_connection_compare(dev_checkpoint->settings_connection,
|
|
nm_settings_connection_get_connection(sett_conn),
|
|
NM_SETTING_COMPARE_FLAG_EXACT)) {
|
|
_LOGT("rollback: settings connection %s changed", uuid);
|
|
*need_update = TRUE;
|
|
*need_activation = TRUE;
|
|
}
|
|
|
|
/* ... is active, ... */
|
|
nm_manager_for_each_active_connection (priv->manager, active, tmp_clist) {
|
|
ac_uuid =
|
|
nm_settings_connection_get_uuid(nm_active_connection_get_settings_connection(active));
|
|
if (nm_streq(uuid, ac_uuid)) {
|
|
_LOGT("rollback: connection %s is active", uuid);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!active) {
|
|
_LOGT("rollback: connection %s is not active", uuid);
|
|
*need_activation = TRUE;
|
|
return sett_conn;
|
|
}
|
|
|
|
/* ... or if the connection was reactivated/reapplied */
|
|
if (nm_active_connection_version_id_get(active) != dev_checkpoint->ac_version_id) {
|
|
_LOGT("rollback: active connection version id of %s changed", uuid);
|
|
*need_activation = TRUE;
|
|
}
|
|
|
|
return sett_conn;
|
|
}
|
|
|
|
static gboolean
|
|
restore_and_activate_connection(NMCheckpoint *self, DeviceCheckpoint *dev_checkpoint)
|
|
{
|
|
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self);
|
|
NMSettingsConnection *connection;
|
|
gs_unref_object NMAuthSubject *subject = NULL;
|
|
GError *local_error = NULL;
|
|
gboolean need_update, need_activation;
|
|
NMSettingsConnectionPersistMode persist_mode;
|
|
NMSettingsConnectionIntFlags sett_flags;
|
|
NMSettingsConnectionIntFlags sett_mask;
|
|
|
|
connection = find_settings_connection(self, dev_checkpoint, &need_update, &need_activation);
|
|
|
|
/* FIXME: we need to ensure to re-create/update the profile for the
|
|
* same settings plugin. E.g. if it was a keyfile in /run or /etc,
|
|
* it must be again. If it was previously handled by a certain settings plugin,
|
|
* so it must again.
|
|
*
|
|
* FIXME: preserve and restore the right settings flags (volatile, nm-generated). */
|
|
sett_flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
|
|
sett_mask = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
|
|
|
|
if (connection) {
|
|
if (need_update) {
|
|
_LOGD("rollback: updating connection %s", nm_settings_connection_get_uuid(connection));
|
|
persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP;
|
|
nm_settings_connection_update(
|
|
connection,
|
|
dev_checkpoint->settings_connection,
|
|
persist_mode,
|
|
sett_flags,
|
|
sett_mask,
|
|
NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
|
|
| NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET,
|
|
"checkpoint-rollback",
|
|
NULL);
|
|
}
|
|
} else {
|
|
/* The connection was deleted, recreate it */
|
|
_LOGD("rollback: adding connection %s again",
|
|
nm_connection_get_uuid(dev_checkpoint->settings_connection));
|
|
|
|
persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
|
|
if (!nm_settings_add_connection(NM_SETTINGS_GET,
|
|
dev_checkpoint->settings_connection,
|
|
persist_mode,
|
|
NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
|
|
sett_flags,
|
|
&connection,
|
|
&local_error)) {
|
|
_LOGD("rollback: connection add failure: %s", local_error->message);
|
|
g_clear_error(&local_error);
|
|
return FALSE;
|
|
}
|
|
|
|
/* If the device is software, a brand new NMDevice may have been created */
|
|
if (dev_checkpoint->is_software && !dev_checkpoint->device) {
|
|
dev_checkpoint->device = nm_manager_get_device(priv->manager,
|
|
dev_checkpoint->original_dev_name,
|
|
dev_checkpoint->dev_type);
|
|
nm_g_object_ref(dev_checkpoint->device);
|
|
}
|
|
need_activation = TRUE;
|
|
}
|
|
|
|
if (!dev_checkpoint->device) {
|
|
_LOGD("rollback: device cannot be restored");
|
|
return FALSE;
|
|
}
|
|
|
|
if (need_activation) {
|
|
_LOGD("rollback: reactivating connection %s", nm_settings_connection_get_uuid(connection));
|
|
subject = nm_auth_subject_new_internal();
|
|
|
|
/* Disconnect the device if needed. This necessary because now
|
|
* the manager prevents the reactivation of the same connection by
|
|
* an internal subject. */
|
|
if (nm_device_get_state(dev_checkpoint->device) > NM_DEVICE_STATE_DISCONNECTED
|
|
&& nm_device_get_state(dev_checkpoint->device) < NM_DEVICE_STATE_DEACTIVATING) {
|
|
if (!NM_FLAGS_HAS(priv->flags, NM_CHECKPOINT_CREATE_FLAG_NO_PRESERVE_EXTERNAL_PORTS)) {
|
|
nm_device_activation_state_set_preserve_external_ports(dev_checkpoint->device,
|
|
TRUE);
|
|
}
|
|
|
|
nm_device_state_changed(dev_checkpoint->device,
|
|
NM_DEVICE_STATE_DEACTIVATING,
|
|
NM_DEVICE_STATE_REASON_NEW_ACTIVATION);
|
|
}
|
|
|
|
if (!nm_manager_activate_connection(
|
|
priv->manager,
|
|
connection,
|
|
dev_checkpoint->applied_connection,
|
|
NULL,
|
|
dev_checkpoint->device,
|
|
subject,
|
|
NM_ACTIVATION_TYPE_MANAGED,
|
|
dev_checkpoint->activation_reason,
|
|
dev_checkpoint->activation_lifetime_bound_to_profile_visibility
|
|
? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
|
|
: NM_ACTIVATION_STATE_FLAG_NONE,
|
|
&local_error)) {
|
|
_LOGW("rollback: reactivation of connection %s/%s failed: %s",
|
|
nm_settings_connection_get_id(connection),
|
|
nm_settings_connection_get_uuid(connection),
|
|
local_error->message);
|
|
g_clear_error(&local_error);
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
GVariant *
|
|
nm_checkpoint_rollback(NMCheckpoint *self)
|
|
{
|
|
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self);
|
|
DeviceCheckpoint *dev_checkpoint;
|
|
GHashTableIter iter;
|
|
NMDevice *device;
|
|
GVariantBuilder builder;
|
|
uint i;
|
|
|
|
_LOGI("rollback of %s", nm_dbus_object_get_path(NM_DBUS_OBJECT(self)));
|
|
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{su}"));
|
|
|
|
/* Start creating removed devices (if any and if possible) */
|
|
if (priv->removed_devices) {
|
|
for (i = 0; i < priv->removed_devices->len; i++) {
|
|
guint32 result = NM_ROLLBACK_RESULT_OK;
|
|
|
|
dev_checkpoint = priv->removed_devices->pdata[i];
|
|
_LOGD("rollback: restoring removed device %s (state %d, realized %d, explicitly "
|
|
"unmanaged %d)",
|
|
dev_checkpoint->original_dev_name,
|
|
(int) dev_checkpoint->state,
|
|
dev_checkpoint->realized,
|
|
dev_checkpoint->unmanaged_explicit);
|
|
|
|
if (dev_checkpoint->applied_connection) {
|
|
if (!restore_and_activate_connection(self, dev_checkpoint))
|
|
result = NM_ROLLBACK_RESULT_ERR_FAILED;
|
|
}
|
|
g_variant_builder_add(&builder, "{su}", dev_checkpoint->original_dev_path, result);
|
|
}
|
|
}
|
|
|
|
/* 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)) {
|
|
guint32 result = NM_ROLLBACK_RESULT_OK;
|
|
|
|
_LOGD("rollback: restoring device %s (state %d, realized %d, explicitly unmanaged %d)",
|
|
dev_checkpoint->original_dev_name,
|
|
(int) dev_checkpoint->state,
|
|
dev_checkpoint->realized,
|
|
dev_checkpoint->unmanaged_explicit);
|
|
|
|
if (nm_device_is_real(device)) {
|
|
if (!dev_checkpoint->realized) {
|
|
_LOGD("rollback: device was not realized, unmanage it");
|
|
nm_device_set_unmanaged_by_flags_queue(device,
|
|
NM_UNMANAGED_USER_EXPLICIT,
|
|
TRUE,
|
|
NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
|
|
goto next_dev;
|
|
}
|
|
} else {
|
|
if (dev_checkpoint->realized) {
|
|
if (dev_checkpoint->is_software) {
|
|
/* try to recreate software device */
|
|
_LOGD("rollback: software device not realized, will re-activate");
|
|
goto activate;
|
|
} else {
|
|
_LOGD("rollback: device is not realized");
|
|
result = NM_ROLLBACK_RESULT_ERR_FAILED;
|
|
}
|
|
}
|
|
goto next_dev;
|
|
}
|
|
|
|
/* Manage the device again if needed */
|
|
if (nm_device_get_unmanaged_flags(device, NM_UNMANAGED_USER_EXPLICIT)
|
|
&& dev_checkpoint->unmanaged_explicit != NM_UNMAN_FLAG_OP_SET_UNMANAGED) {
|
|
_LOGD("rollback: restore unmanaged user-explicit");
|
|
nm_device_set_unmanaged_by_flags_queue(device,
|
|
NM_UNMANAGED_USER_EXPLICIT,
|
|
dev_checkpoint->unmanaged_explicit,
|
|
NM_DEVICE_STATE_REASON_NOW_MANAGED);
|
|
}
|
|
|
|
if (dev_checkpoint->state == NM_DEVICE_STATE_UNMANAGED) {
|
|
if (nm_device_get_state(device) != NM_DEVICE_STATE_UNMANAGED
|
|
|| dev_checkpoint->unmanaged_explicit == NM_UNMAN_FLAG_OP_SET_UNMANAGED) {
|
|
_LOGD("rollback: explicitly unmanage device");
|
|
nm_device_set_unmanaged_by_flags_queue(device,
|
|
NM_UNMANAGED_USER_EXPLICIT,
|
|
TRUE,
|
|
NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
|
|
}
|
|
goto next_dev;
|
|
}
|
|
|
|
activate:
|
|
if (dev_checkpoint->applied_connection) {
|
|
if (!restore_and_activate_connection(self, dev_checkpoint)) {
|
|
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);
|
|
}
|
|
|
|
if (NM_FLAGS_HAS(priv->flags, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS)) {
|
|
NMSettingsConnection *con;
|
|
gs_free NMSettingsConnection **list = NULL;
|
|
|
|
g_return_val_if_fail(priv->connection_uuids, NULL);
|
|
list = nm_settings_get_connections_clone(
|
|
NM_SETTINGS_GET,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
nm_settings_connection_cmp_autoconnect_priority_p_with_data,
|
|
NULL);
|
|
|
|
for (i = 0; list[i]; i++) {
|
|
con = list[i];
|
|
if (!g_hash_table_contains(priv->connection_uuids,
|
|
nm_settings_connection_get_uuid(con))) {
|
|
_LOGD("rollback: deleting new connection %s", nm_settings_connection_get_uuid(con));
|
|
nm_settings_connection_delete(con, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NM_FLAGS_HAS(priv->flags, NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES)) {
|
|
const CList *tmp_lst;
|
|
NMDeviceState state;
|
|
|
|
nm_manager_for_each_device (priv->manager, device, tmp_lst) {
|
|
if (g_hash_table_contains(priv->devices, device))
|
|
continue;
|
|
state = nm_device_get_state(device);
|
|
if (state > NM_DEVICE_STATE_DISCONNECTED && state < NM_DEVICE_STATE_DEACTIVATING) {
|
|
_LOGD("rollback: disconnecting new device %s", nm_device_get_iface(device));
|
|
nm_device_state_changed(device,
|
|
NM_DEVICE_STATE_DEACTIVATING,
|
|
NM_DEVICE_STATE_REASON_USER_REQUESTED);
|
|
}
|
|
}
|
|
}
|
|
|
|
return g_variant_new("(a{su})", &builder);
|
|
}
|
|
|
|
static void
|
|
device_checkpoint_destroy(gpointer data)
|
|
{
|
|
DeviceCheckpoint *dev_checkpoint = data;
|
|
|
|
nm_clear_g_signal_handler(dev_checkpoint->device, &dev_checkpoint->dev_exported_change_id);
|
|
g_clear_object(&dev_checkpoint->applied_connection);
|
|
g_clear_object(&dev_checkpoint->settings_connection);
|
|
g_clear_object(&dev_checkpoint->device);
|
|
g_free(dev_checkpoint->original_dev_path);
|
|
g_free(dev_checkpoint->original_dev_name);
|
|
|
|
g_slice_free(DeviceCheckpoint, dev_checkpoint);
|
|
}
|
|
|
|
static void
|
|
_move_dev_to_removed_devices(NMDevice *device, NMCheckpoint *checkpoint)
|
|
{
|
|
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(checkpoint);
|
|
DeviceCheckpoint *dev_checkpoint;
|
|
|
|
g_return_if_fail(device);
|
|
|
|
dev_checkpoint = g_hash_table_lookup(priv->devices, device);
|
|
if (!dev_checkpoint)
|
|
return;
|
|
|
|
g_hash_table_steal(priv->devices, dev_checkpoint->device);
|
|
nm_clear_g_signal_handler(dev_checkpoint->device, &dev_checkpoint->dev_exported_change_id);
|
|
g_clear_object(&dev_checkpoint->device);
|
|
|
|
if (!priv->removed_devices)
|
|
priv->removed_devices =
|
|
g_ptr_array_new_with_free_func((GDestroyNotify) device_checkpoint_destroy);
|
|
g_ptr_array_add(priv->removed_devices, dev_checkpoint);
|
|
|
|
_notify(checkpoint, PROP_DEVICES);
|
|
}
|
|
|
|
static void
|
|
_dev_exported_changed(NMDBusObject *obj, NMCheckpoint *checkpoint)
|
|
{
|
|
_move_dev_to_removed_devices(NM_DEVICE(obj), checkpoint);
|
|
}
|
|
|
|
static DeviceCheckpoint *
|
|
device_checkpoint_create(NMCheckpoint *checkpoint, NMDevice *device)
|
|
{
|
|
DeviceCheckpoint *dev_checkpoint;
|
|
NMConnection *applied_connection;
|
|
NMSettingsConnection *settings_connection;
|
|
const char *path;
|
|
NMActRequest *act_request;
|
|
|
|
nm_assert(NM_IS_DEVICE(device));
|
|
nm_assert(nm_device_is_real(device));
|
|
|
|
path = nm_dbus_object_get_path(NM_DBUS_OBJECT(device));
|
|
|
|
dev_checkpoint = g_slice_new0(DeviceCheckpoint);
|
|
dev_checkpoint->device = g_object_ref(device);
|
|
dev_checkpoint->original_dev_path = g_strdup(path);
|
|
dev_checkpoint->original_dev_name = g_strdup(nm_device_get_iface(device));
|
|
dev_checkpoint->dev_type = nm_device_get_device_type(device);
|
|
dev_checkpoint->state = nm_device_get_state(device);
|
|
dev_checkpoint->is_software = nm_device_is_software(device);
|
|
dev_checkpoint->realized = nm_device_is_real(device);
|
|
dev_checkpoint->dev_exported_change_id = g_signal_connect(device,
|
|
NM_DBUS_OBJECT_EXPORTED_CHANGED,
|
|
G_CALLBACK(_dev_exported_changed),
|
|
checkpoint);
|
|
|
|
if (nm_device_get_unmanaged_mask(device, NM_UNMANAGED_USER_EXPLICIT)) {
|
|
dev_checkpoint->unmanaged_explicit =
|
|
!!nm_device_get_unmanaged_flags(device, NM_UNMANAGED_USER_EXPLICIT);
|
|
} else
|
|
dev_checkpoint->unmanaged_explicit = NM_UNMAN_FLAG_OP_FORGET;
|
|
|
|
act_request = nm_device_get_act_request(device);
|
|
if (act_request) {
|
|
settings_connection = nm_act_request_get_settings_connection(act_request);
|
|
applied_connection = nm_act_request_get_applied_connection(act_request);
|
|
|
|
dev_checkpoint->applied_connection = nm_simple_connection_new_clone(applied_connection);
|
|
dev_checkpoint->settings_connection = nm_simple_connection_new_clone(
|
|
nm_settings_connection_get_connection(settings_connection));
|
|
dev_checkpoint->ac_version_id =
|
|
nm_active_connection_version_id_get(NM_ACTIVE_CONNECTION(act_request));
|
|
dev_checkpoint->activation_reason =
|
|
nm_active_connection_get_activation_reason(NM_ACTIVE_CONNECTION(act_request));
|
|
dev_checkpoint->activation_lifetime_bound_to_profile_visibility =
|
|
NM_FLAGS_HAS(nm_active_connection_get_state_flags(NM_ACTIVE_CONNECTION(act_request)),
|
|
NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
|
|
}
|
|
|
|
return dev_checkpoint;
|
|
}
|
|
|
|
static gboolean
|
|
_timeout_cb(gpointer user_data)
|
|
{
|
|
NMCheckpoint *self = user_data;
|
|
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self);
|
|
|
|
priv->timeout_id = 0;
|
|
|
|
if (priv->timeout_cb)
|
|
priv->timeout_cb(self, priv->timeout_data);
|
|
|
|
/* beware, @self likely got destroyed! */
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
void
|
|
nm_checkpoint_adjust_rollback_timeout(NMCheckpoint *self, guint32 add_timeout)
|
|
{
|
|
guint32 rollback_timeout_s;
|
|
gint64 now_ms, add_timeout_ms, rollback_timeout_ms;
|
|
|
|
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self);
|
|
|
|
nm_clear_g_source(&priv->timeout_id);
|
|
|
|
if (add_timeout == 0)
|
|
rollback_timeout_s = 0;
|
|
else {
|
|
now_ms = nm_utils_get_monotonic_timestamp_msec();
|
|
add_timeout_ms = ((gint64) add_timeout) * 1000;
|
|
rollback_timeout_ms = (now_ms - priv->created_at_ms) + add_timeout_ms;
|
|
|
|
/* round to nearest integer second. Since NM_CHECKPOINT_ROLLBACK_TIMEOUT is
|
|
* in units seconds, it will be able to exactly express the timeout. */
|
|
rollback_timeout_s = NM_MIN((rollback_timeout_ms + 500) / 1000, (gint64) G_MAXUINT32);
|
|
|
|
/* we expect the timeout to be positive, because add_timeout_ms is positive.
|
|
* We cannot accept a zero, because it means "infinity". */
|
|
nm_assert(rollback_timeout_s > 0);
|
|
|
|
priv->timeout_id =
|
|
g_timeout_add(NM_MIN(add_timeout_ms, (gint64) G_MAXUINT32), _timeout_cb, self);
|
|
}
|
|
|
|
if (rollback_timeout_s != priv->rollback_timeout_s) {
|
|
priv->rollback_timeout_s = rollback_timeout_s;
|
|
_notify(self, PROP_ROLLBACK_TIMEOUT);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DEVICES:
|
|
nm_dbus_utils_g_value_set_object_path_from_hash(value, priv->devices, FALSE);
|
|
break;
|
|
case PROP_CREATED:
|
|
g_value_set_int64(
|
|
value,
|
|
nm_utils_monotonic_timestamp_as_boottime(priv->created_at_ms, NM_UTILS_NSEC_PER_MSEC));
|
|
break;
|
|
case PROP_ROLLBACK_TIMEOUT:
|
|
g_value_set_uint(value, priv->rollback_timeout_s);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
nm_checkpoint_init(NMCheckpoint *self)
|
|
{
|
|
NMCheckpointPrivate *priv;
|
|
|
|
priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_CHECKPOINT, NMCheckpointPrivate);
|
|
|
|
self->_priv = priv;
|
|
|
|
c_list_init(&self->checkpoints_lst);
|
|
|
|
priv->devices = g_hash_table_new_full(nm_direct_hash, NULL, NULL, device_checkpoint_destroy);
|
|
}
|
|
|
|
static void
|
|
_device_removed(NMManager *manager, NMDevice *device, gpointer user_data)
|
|
{
|
|
_move_dev_to_removed_devices(device, NM_CHECKPOINT(user_data));
|
|
}
|
|
|
|
NMCheckpoint *
|
|
nm_checkpoint_new(NMManager *manager,
|
|
GPtrArray *devices,
|
|
guint32 rollback_timeout_s,
|
|
NMCheckpointCreateFlags flags)
|
|
{
|
|
NMCheckpoint *self;
|
|
NMCheckpointPrivate *priv;
|
|
NMSettingsConnection *const *con;
|
|
gint64 rollback_timeout_ms;
|
|
guint i;
|
|
|
|
g_return_val_if_fail(manager, NULL);
|
|
g_return_val_if_fail(devices, NULL);
|
|
g_return_val_if_fail(devices->len > 0, NULL);
|
|
|
|
self = g_object_new(NM_TYPE_CHECKPOINT, NULL);
|
|
|
|
priv = NM_CHECKPOINT_GET_PRIVATE(self);
|
|
priv->manager = g_object_ref(manager);
|
|
priv->rollback_timeout_s = rollback_timeout_s;
|
|
priv->created_at_ms = nm_utils_get_monotonic_timestamp_msec();
|
|
priv->flags = flags;
|
|
|
|
if (rollback_timeout_s != 0) {
|
|
rollback_timeout_ms = ((gint64) rollback_timeout_s) * 1000;
|
|
priv->timeout_id =
|
|
g_timeout_add(NM_MIN(rollback_timeout_ms, (gint64) G_MAXUINT32), _timeout_cb, self);
|
|
}
|
|
|
|
if (NM_FLAGS_HAS(flags, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS)) {
|
|
priv->connection_uuids = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL);
|
|
for (con = nm_settings_get_connections(NM_SETTINGS_GET, NULL); *con; con++) {
|
|
g_hash_table_add(priv->connection_uuids,
|
|
g_strdup(nm_settings_connection_get_uuid(*con)));
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < devices->len; i++) {
|
|
NMDevice *device = devices->pdata[i];
|
|
|
|
/* As long as the check point instance exists, it will keep a reference
|
|
* to the device also if the device gets removed (by rmmod or by deleting
|
|
* a connection profile for a software device). */
|
|
g_hash_table_insert(priv->devices, device, device_checkpoint_create(self, device));
|
|
}
|
|
|
|
priv->dev_removed_id = g_signal_connect(priv->manager,
|
|
NM_MANAGER_DEVICE_REMOVED,
|
|
G_CALLBACK(_device_removed),
|
|
self);
|
|
return self;
|
|
}
|
|
|
|
static void
|
|
dispose(GObject *object)
|
|
{
|
|
NMCheckpoint *self = NM_CHECKPOINT(object);
|
|
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE(self);
|
|
|
|
nm_assert(c_list_is_empty(&self->checkpoints_lst));
|
|
|
|
nm_clear_pointer(&priv->devices, g_hash_table_unref);
|
|
nm_clear_pointer(&priv->connection_uuids, g_hash_table_unref);
|
|
nm_clear_pointer(&priv->removed_devices, g_ptr_array_unref);
|
|
|
|
nm_clear_g_signal_handler(priv->manager, &priv->dev_removed_id);
|
|
g_clear_object(&priv->manager);
|
|
|
|
nm_clear_g_source(&priv->timeout_id);
|
|
|
|
G_OBJECT_CLASS(nm_checkpoint_parent_class)->dispose(object);
|
|
}
|
|
|
|
static const NMDBusInterfaceInfoExtended interface_info_checkpoint = {
|
|
.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
|
|
NM_DBUS_INTERFACE_CHECKPOINT,
|
|
.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
|
|
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Devices", "ao", NM_CHECKPOINT_DEVICES),
|
|
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Created", "x", NM_CHECKPOINT_CREATED),
|
|
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("RollbackTimeout",
|
|
"u",
|
|
NM_CHECKPOINT_ROLLBACK_TIMEOUT), ), ),
|
|
};
|
|
|
|
static void
|
|
nm_checkpoint_class_init(NMCheckpointClass *checkpoint_class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(checkpoint_class);
|
|
NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(checkpoint_class);
|
|
|
|
g_type_class_add_private(object_class, sizeof(NMCheckpointPrivate));
|
|
|
|
dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/Checkpoint");
|
|
dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_checkpoint);
|
|
|
|
object_class->dispose = dispose;
|
|
object_class->get_property = get_property;
|
|
|
|
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);
|
|
}
|