mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-06 10:50:14 +01:00
We use clang-format for automatic formatting of our source files. Since clang-format is actively maintained software, the actual formatting depends on the used version of clang-format. That is unfortunate and painful, but really unavoidable unless clang-format would be strictly bug-compatible. So the version that we must use is from the current Fedora release, which is also tested by our gitlab-ci. Previously, we were using Fedora 34 with clang-tools-extra-12.0.1-1.fc34.x86_64. As Fedora 35 comes along, we need to update our formatting as Fedora 35 comes with version "13.0.0~rc1-1.fc35". An alternative would be to freeze on version 12, but that has different problems (like, it's cumbersome to rebuild clang 12 on Fedora 35 and it would be cumbersome for our developers which are on Fedora 35 to use a clang that they cannot easily install). The (differently painful) solution is to reformat from time to time, as we switch to a new Fedora (and thus clang) version. Usually we would expect that such a reformatting brings minor changes. But this time, the changes are huge. That is mentioned in the release notes [1] as Makes PointerAligment: Right working with AlignConsecutiveDeclarations. (Fixes https://llvm.org/PR27353) [1] https://releases.llvm.org/13.0.0/tools/clang/docs/ReleaseNotes.html#clang-format
349 lines
12 KiB
C
349 lines
12 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-manager.h"
|
|
|
|
#include "nm-checkpoint.h"
|
|
#include "nm-connection.h"
|
|
#include "nm-core-utils.h"
|
|
#include "devices/nm-device.h"
|
|
#include "nm-manager.h"
|
|
#include "nm-utils.h"
|
|
#include "c-list/src/c-list.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
struct _NMCheckpointManager {
|
|
NMManager *_manager;
|
|
GParamSpec *property_spec;
|
|
CList checkpoints_lst_head;
|
|
};
|
|
|
|
#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__)
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
notify_checkpoints(NMCheckpointManager *self)
|
|
{
|
|
g_object_notify_by_pspec((GObject *) GET_MANAGER(self), self->property_spec);
|
|
}
|
|
|
|
static void
|
|
destroy_checkpoint(NMCheckpointManager *self, NMCheckpoint *checkpoint, gboolean log_destroy)
|
|
{
|
|
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);
|
|
|
|
if (log_destroy)
|
|
nm_checkpoint_log_destroy(checkpoint);
|
|
|
|
notify_checkpoints(self);
|
|
|
|
nm_dbus_object_unexport(NM_DBUS_OBJECT(checkpoint));
|
|
g_object_unref(checkpoint);
|
|
}
|
|
|
|
static GVariant *
|
|
rollback_checkpoint(NMCheckpointManager *self, NMCheckpoint *checkpoint)
|
|
{
|
|
GVariant *result;
|
|
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);
|
|
}
|
|
}
|
|
|
|
result = nm_checkpoint_rollback(checkpoint);
|
|
destroy_checkpoint(self, checkpoint, FALSE);
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
rollback_timeout_cb(NMCheckpoint *checkpoint, gpointer user_data)
|
|
{
|
|
NMCheckpointManager *self = user_data;
|
|
gs_unref_variant GVariant *result = NULL;
|
|
|
|
result = rollback_checkpoint(self, checkpoint);
|
|
}
|
|
|
|
NMCheckpoint *
|
|
nm_checkpoint_manager_create(NMCheckpointManager *self,
|
|
const char *const *device_paths,
|
|
guint32 rollback_timeout,
|
|
NMCheckpointCreateFlags flags,
|
|
GError **error)
|
|
{
|
|
NMManager *manager;
|
|
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);
|
|
manager = GET_MANAGER(self);
|
|
|
|
devices = g_ptr_array_new();
|
|
|
|
if (!device_paths || !device_paths[0]) {
|
|
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;
|
|
nm_assert(nm_dbus_object_get_path(NM_DBUS_OBJECT(device)));
|
|
g_ptr_array_add(devices, device);
|
|
}
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
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))
|
|
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_dbus_object_get_path(NM_DBUS_OBJECT(checkpoint)));
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
checkpoint = nm_checkpoint_new(manager, devices, rollback_timeout, flags);
|
|
|
|
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;
|
|
}
|
|
|
|
void
|
|
nm_checkpoint_manager_destroy_all(NMCheckpointManager *self)
|
|
{
|
|
NMCheckpoint *checkpoint;
|
|
|
|
g_return_if_fail(self);
|
|
|
|
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 *path, 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);
|
|
|
|
if (!nm_dbus_path_not_empty(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 *path,
|
|
GVariant **results,
|
|
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(results, FALSE);
|
|
g_return_val_if_fail(!error || !*error, FALSE);
|
|
|
|
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 =
|
|
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;
|
|
}
|
|
|
|
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, 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->property_spec = spec;
|
|
c_list_init(&self->checkpoints_lst_head);
|
|
return self;
|
|
}
|
|
|
|
void
|
|
nm_checkpoint_manager_free(NMCheckpointManager *self)
|
|
{
|
|
if (!self)
|
|
return;
|
|
|
|
nm_checkpoint_manager_destroy_all(self);
|
|
g_slice_free(NMCheckpointManager, self);
|
|
}
|