NetworkManager/src/nm-checkpoint.c
Lubomir Rintel 972e0d2803 all: rename the introspection data to use the interface paths in names
This makes it easier to install the files with proper names.
Also, it makes the makefile rules slightly simpler.

Lastly, the documentation is now generated into docs/api, which makes it
possible to get rid of the awkward relative file names in docbook.
2016-11-23 15:43:42 +01:00

487 lines
16 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.h"
#include <string.h>
#include "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"
#include "introspection/org.freedesktop.NetworkManager.Checkpoint.h"
/*****************************************************************************/
typedef struct {
char *original_dev_path;
NMDevice *device;
NMConnection *applied_connection;
NMConnection *settings_connection;
NMDeviceState state;
bool realized:1;
bool unmanaged_explicit:1;
} DeviceCheckpoint;
NM_GOBJECT_PROPERTIES_DEFINE_BASE (
PROP_DEVICES,
PROP_CREATED,
PROP_ROLLBACK_TIMEOUT,
);
typedef struct {
/* properties */
GHashTable *devices;
gint64 created;
guint32 rollback_timeout;
/* private members */
NMManager *manager;
gint64 rollback_ts;
NMCheckpointCreateFlags flags;
GHashTable *connection_uuids;
} NMCheckpointPrivate;
struct _NMCheckpoint {
NMExportedObject parent;
NMCheckpointPrivate _priv;
};
struct _NMCheckpointClass {
NMExportedObjectClass parent;
};
G_DEFINE_TYPE (NMCheckpoint, nm_checkpoint, NM_TYPE_EXPORTED_OBJECT)
#define NM_CHECKPOINT_GET_PRIVATE(self) _NM_GET_PRIVATE (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, \
"%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
__prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} G_STMT_END
/*****************************************************************************/
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_uuid;
_LOGD ("rollback: restoring device %s (state %d, realized %d, explicitly unmanaged %d)",
nm_device_get_iface (device),
(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 (nm_device_is_software (device)) {
/* 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;
}
activate:
if (dev_checkpoint->state == NM_DEVICE_STATE_UNMANAGED) {
if ( nm_device_get_state (device) != NM_DEVICE_STATE_UNMANAGED
|| dev_checkpoint->unmanaged_explicit) {
_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;
}
if (dev_checkpoint->applied_connection) {
/* The device had an active connection, check if the
* connection still exists
* */
con_uuid = nm_connection_get_uuid (dev_checkpoint->settings_connection);
connection = nm_settings_get_connection_by_uuid (nm_settings_get (), con_uuid);
if (connection) {
/* If the connection is still there, restore its content
* and save it
* */
_LOGD ("rollback: connection %s still exists", con_uuid);
nm_connection_replace_settings_from_connection (NM_CONNECTION (connection),
dev_checkpoint->settings_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_uuid);
connection = nm_settings_add_connection (nm_settings_get (),
dev_checkpoint->settings_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,
dev_checkpoint->applied_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);
}
if (NM_FLAGS_HAS (priv->flags, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS)) {
NMSettingsConnection *con;
gs_free_slist GSList *list = NULL;
GSList *item;
g_return_val_if_fail (priv->connection_uuids, NULL);
list = nm_settings_get_connections_sorted (nm_settings_get ());
for (item = list; item; item = g_slist_next (item)) {
con = item->data;
if (!g_hash_table_contains (priv->connection_uuids,
nm_settings_connection_get_uuid (con))) {
_LOGD ("rollback: deleting new connection %s (%s)",
nm_settings_connection_get_uuid (con),
nm_settings_connection_get_id (con));
nm_settings_connection_delete (con, NULL, NULL);
}
}
}
if (NM_FLAGS_HAS (priv->flags, NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES)) {
const GSList *list = nm_manager_get_devices (priv->manager);
NMDeviceState state;
NMDevice *dev;
for (list = nm_manager_get_devices (priv->manager); list ; list = g_slist_next (list)) {
dev = list->data;
if (!g_hash_table_contains (priv->devices, dev)) {
state = nm_device_get_state (dev);
if ( state > NM_DEVICE_STATE_DISCONNECTED
&& state < NM_DEVICE_STATE_DEACTIVATING) {
_LOGD ("rollback: disconnecting new device %s", nm_device_get_iface (dev));
nm_device_state_changed (dev,
NM_DEVICE_STATE_DEACTIVATING,
NM_DEVICE_STATE_REASON_USER_REQUESTED);
}
}
}
}
return g_variant_new ("(a{su})", &builder);
}
static DeviceCheckpoint *
device_checkpoint_create (NMDevice *device,
GError **error)
{
DeviceCheckpoint *dev_checkpoint;
NMConnection *applied_connection;
NMSettingsConnection *settings_connection;
const char *path;
gboolean unmanaged_explicit;
path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (device));
unmanaged_explicit = !!nm_device_get_unmanaged_flags (device,
NM_UNMANAGED_USER_EXPLICIT);
dev_checkpoint = g_slice_new0 (DeviceCheckpoint);
dev_checkpoint->device = g_object_ref (device);
dev_checkpoint->original_dev_path = g_strdup (path);
dev_checkpoint->state = nm_device_get_state (device);
dev_checkpoint->realized = nm_device_is_real (device);
dev_checkpoint->unmanaged_explicit = unmanaged_explicit;
applied_connection = nm_device_get_applied_connection (device);
if (applied_connection) {
dev_checkpoint->applied_connection =
nm_simple_connection_new_clone (applied_connection);
settings_connection = nm_device_get_settings_connection (device);
g_return_val_if_fail (settings_connection, NULL);
dev_checkpoint->settings_connection =
nm_simple_connection_new_clone (NM_CONNECTION (settings_connection));
}
return dev_checkpoint;
}
static void
device_checkpoint_destroy (gpointer data)
{
DeviceCheckpoint *dev_checkpoint = data;
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_slice_free (DeviceCheckpoint, dev_checkpoint);
}
/*****************************************************************************/
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_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);
}
NMCheckpoint *
nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_timeout,
NMCheckpointCreateFlags flags, GError **error)
{
NMCheckpoint *self;
NMCheckpointPrivate *priv;
NMSettingsConnection *const *con;
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) {
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;
priv->flags = flags;
if (NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS)) {
priv->connection_uuids = g_hash_table_new_full (g_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++) {
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_clear_pointer (&priv->connection_uuids, g_hash_table_unref);
G_OBJECT_CLASS (nm_checkpoint_parent_class)->dispose (object);
}
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);
exported_object_class->export_path = NM_DBUS_PATH "/Checkpoint/%u";
exported_object_class->export_on_construction = FALSE;
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);
nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (checkpoint_class),
NMDBUS_TYPE_CHECKPOINT_SKELETON,
NULL);
}