diff options
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | Makefile.examples | 2 | ||||
-rwxr-xr-x[-rw-r--r--] | examples/python/gi/checkpoint.py | 183 | ||||
-rw-r--r-- | examples/python/nmex.py | 72 | ||||
-rw-r--r-- | introspection/org.freedesktop.NetworkManager.xml | 21 | ||||
-rw-r--r-- | libnm-core/nm-dbus-interface.h | 17 | ||||
-rw-r--r-- | libnm/libnm.ver | 9 | ||||
-rw-r--r-- | libnm/nm-client.c | 159 | ||||
-rw-r--r-- | libnm/nm-client.h | 47 | ||||
-rw-r--r-- | libnm/nm-manager.c | 164 | ||||
-rw-r--r-- | libnm/nm-manager.h | 43 | ||||
-rw-r--r-- | src/devices/wifi/nm-device-olpc-mesh.c | 5 | ||||
-rw-r--r-- | src/devices/wifi/nm-iwd-manager.c | 30 | ||||
-rw-r--r-- | src/nm-audit-manager.h | 1 | ||||
-rw-r--r-- | src/nm-checkpoint-manager.c | 328 | ||||
-rw-r--r-- | src/nm-checkpoint-manager.h | 22 | ||||
-rw-r--r-- | src/nm-checkpoint.c | 229 | ||||
-rw-r--r-- | src/nm-checkpoint.h | 27 | ||||
-rw-r--r-- | src/nm-dbus-utils.c | 45 | ||||
-rw-r--r-- | src/nm-dbus-utils.h | 7 | ||||
-rw-r--r-- | src/nm-manager.c | 69 | ||||
-rw-r--r-- | src/nm-manager.h | 15 | ||||
-rw-r--r-- | src/nm-policy.c | 25 |
23 files changed, 1013 insertions, 511 deletions
diff --git a/Makefile.am b/Makefile.am index f52e1f0248..61b89bd9f8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1466,10 +1466,10 @@ src_libNetworkManager_la_CPPFLAGS = $(src_cppflags) src_libNetworkManager_la_SOURCES = \ \ - src/nm-checkpoint-manager.c \ - src/nm-checkpoint-manager.h \ src/nm-checkpoint.c \ src/nm-checkpoint.h \ + src/nm-checkpoint-manager.c \ + src/nm-checkpoint-manager.h \ \ src/devices/nm-device.c \ src/devices/nm-device.h \ diff --git a/Makefile.examples b/Makefile.examples index d49db6ce86..d5433395c3 100644 --- a/Makefile.examples +++ b/Makefile.examples @@ -150,6 +150,8 @@ EXTRA_DIST += \ examples/nm-conf.d/30-anon.conf \ examples/nm-conf.d/31-mac-addr-change.conf \ \ + examples/python/nmex.py \ + \ examples/python/dbus/nm-state.py \ examples/python/dbus/add-connection.py \ examples/python/dbus/add-connection-compat.py \ diff --git a/examples/python/gi/checkpoint.py b/examples/python/gi/checkpoint.py index 513e6c3a0a..7c36cdba57 100644..100755 --- a/examples/python/gi/checkpoint.py +++ b/examples/python/gi/checkpoint.py @@ -18,98 +18,153 @@ import gi gi.require_version('NM', '1.0') from gi.repository import GLib, NM +import os +os.sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +import nmex + +############################################################################### + def usage(): - print "Usage: %s [COMMAND [ARG]...]" % sys.argv[0] - print "" - print " COMMANDS: show" - print " create TIMEOUT [DEV]..." - print " destroy PATH|NUMBER" - print " rollback PATH|NUMBER" - print + print("Usage: %s [COMMAND [ARG]...]" % sys.argv[0]) + print("") + print(" COMMANDS: [show]") + print(" create TIMEOUT [--destroy-all|--delete-new-connections|--disconnect-new-devices|--allow-overlapping|DEV]...") + print(" destroy PATH|NUMBER") + print(" rollback PATH|NUMBER") + print(" adjust-rollback-timeout PATH|NUMBER TIMEOUT") + print("") sys.exit(1) -def create_cb(client, result, data): +def show(c, ts = None): + cr = c.get_created() + rt = c.get_rollback_timeout() + print("%s:" % c.get_path()) + print(" created: %u%s" % (cr, "" if ts is None else (" (%s sec ago)" % ((ts - cr) / 1000.0)))) + if rt == 0: + print(" timeout: infinity") + else: + print(" timeout: %u seconds%s" % (rt, "" if ts is None else (" (circa %s sec left)" % ((cr + (rt * 1000) - ts) / 1000.0)))) + print(" devices: %s" % (' '.join(sorted(map(lambda x: x.get_iface(), c.get_devices()))))) + +def find_checkpoint(client, path): + for c in client.get_checkpoints(): + if c.get_path() == path: + return c + return None + +def validate_path(path, client): try: - checkpoint = client.checkpoint_create_finish(result) - print("%s" % checkpoint.get_path()) - except Exception, e: - sys.stderr.write("Failed: %s\n" % e.message) - main_loop.quit() + num = int(path) + path = "/org/freedesktop/NetworkManager/Checkpoint/%u" % (num) + except Exception as e: + pass + + if not path or path[0] != '/': + sys.exit('Invalid checkpoint path \"%s\"' % (path)) + + if client is not None: + checkpoint = find_checkpoint(client, path) + if checkpoint is None: + print('WARNING: no checkpoint with path "%s" found' % (path)) + + return path def do_create(client): + flags = NM.CheckpointCreateFlags.NONE if len(sys.argv) < 3: - sys.exit("Failed: %s\n" % e.message) + sys.exit("Failed: missing argument timeout") timeout = int(sys.argv[2]) devices = [] for arg in sys.argv[3:]: - d = client.get_device_by_iface(arg) - if d is None: - sys.exit("Unknown device %s" % arg) - devices.append(d) - - client.checkpoint_create_async(devices, timeout, 0, None, create_cb, None) - -def destroy_cb(client, result, data): - try: - if client.checkpoint_destroy_finish(result) == True: - print "Success" - except Exception, e: - sys.stderr.write("Failed: %s\n" % e.message) - main_loop.quit() + if arg == '--destroy-all': + flags |= NM.CheckpointCreateFlags.DESTROY_ALL + elif arg == '--delete-new-connections': + flags |= NM.CheckpointCreateFlags.DELETE_NEW_CONNECTIONS + elif arg == '--disconnect-new-devices': + flags |= NM.CheckpointCreateFlags.DISCONNECT_NEW_DEVICES + elif arg == '--allow-overlapping': + flags |= NM.CheckpointCreateFlags.ALLOW_OVERLAPPING + else: + d = client.get_device_by_iface(arg) + if d is None: + sys.exit("Unknown device %s" % arg) + devices.append(d) -def find_checkpoint(client, arg): - try: - num = int(arg) - path = "/org/freedesktop/NetworkManager/Checkpoint/%u" % num - except Exception, e: - path = arg + def create_cb(client, result, data): + try: + checkpoint = client.checkpoint_create_finish(result) + print("%s" % checkpoint.get_path()) + except Exception as e: + sys.stderr.write("Failed: %s\n" % e.message) + main_loop.quit() - for c in client.get_checkpoints(): - if c.get_path() == path: - return c - return None + client.checkpoint_create(devices, timeout, flags, None, create_cb, None) def do_destroy(client): if len(sys.argv) < 3: sys.exit("Missing checkpoint path") - checkpoint = find_checkpoint(client, sys.argv[2]) - if checkpoint is None: - sys.exit("Uknown checkpoint %s" % sys.argv[2]) + path = validate_path(sys.argv[2], client) - client.checkpoint_destroy_async(checkpoint, None, destroy_cb, None) + def destroy_cb(client, result, data): + try: + if client.checkpoint_destroy_finish(result) == True: + print("Success") + except Exception as e: + sys.stderr.write("Failed: %s\n" % e.message) + main_loop.quit() -def rollback_cb(client, result, data): - try: - res = client.checkpoint_rollback_finish(result) - for path in res: - d = client.get_device_by_path(path) - if d is None: - iface = path - else: - iface = d.get_iface() - print "%s => %s" % (iface, "OK" if res[path] == 0 else "ERROR") - except Exception, e: - sys.stderr.write("Failed: %s\n" % e.message) - main_loop.quit() + client.checkpoint_destroy(path, None, destroy_cb, None) def do_rollback(client): if len(sys.argv) < 3: sys.exit("Missing checkpoint path") - checkpoint = find_checkpoint(client, sys.argv[2]) - if checkpoint is None: - sys.exit("Uknown checkpoint %s" % sys.argv[2]) + path = validate_path(sys.argv[2], client) + + def rollback_cb(client, result, data): + try: + res = client.checkpoint_rollback_finish(result) + for path in res: + d = client.get_device_by_path(path) + if d is None: + iface = path + else: + iface = d.get_iface() + print("%s => %s" % (iface, "OK" if res[path] == 0 else "ERROR")) + except Exception as e: + sys.stderr.write("Failed: %s\n" % e.message) + main_loop.quit() + + client.checkpoint_rollback(path, None, rollback_cb, None) + +def do_adjust_rollback_timeout(client): + if len(sys.argv) < 3: + sys.exit("Missing checkpoint path") + if len(sys.argv) < 4: + sys.exit("Missing timeout") + try: + add_timeout = int(sys.argv[3]) + except: + sys.exit("Invalid timeout") + + path = validate_path(sys.argv[2], client) + + def adjust_rollback_timeout_cb(client, result, data): + try: + client.checkpoint_adjust_rollback_timeout_finish(result) + print("Success") + except Exception as e: + sys.stderr.write("Failed: %s\n" % e.message) + main_loop.quit() - client.checkpoint_rollback_async(checkpoint, None, rollback_cb, None) + client.checkpoint_adjust_rollback_timeout(path, add_timeout, None, adjust_rollback_timeout_cb, None) def do_show(client): + ts = nmex.nm_boot_time_ms() for c in client.get_checkpoints(): - print "%s:" % c.get_path() - print " created: %u" % c.get_created() - print " timeout: %u seconds" % c.get_rollback_timeout() - print " devices:", ' '.join(sorted(map(lambda x: x.get_iface(), c.get_devices()))) + show(c, ts) if __name__ == '__main__': nm_client = NM.Client.new(None) @@ -124,6 +179,8 @@ if __name__ == '__main__': do_destroy(nm_client) elif sys.argv[1] == 'rollback': do_rollback(nm_client) + elif sys.argv[1] == 'adjust-rollback-timeout': + do_adjust_rollback_timeout(nm_client) else: usage() diff --git a/examples/python/nmex.py b/examples/python/nmex.py new file mode 100644 index 0000000000..a85eecaf87 --- /dev/null +++ b/examples/python/nmex.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# -*- Mode: Python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +# vim: ft=python ts=4 sts=4 sw=4 et ai + +# 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Copyright 2018 Red Hat, Inc. + +############################################################################### +# nmex.py contains helper functions used by some examples. The helper functions +# should be simple and independent, so that the user can extract them easily +# when modifying the example to his needs. +############################################################################### + +def _sys_clock_gettime_ns_lazy(): + import ctypes + + class timespec(ctypes.Structure): + _fields_ = [ + ('tv_sec', ctypes.c_long), + ('tv_nsec', ctypes.c_long) + ] + + librt = ctypes.CDLL('librt.so.1', use_errno=True) + clock_gettime = librt.clock_gettime + clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)] + + t = timespec() + def f(clock_id): + if clock_gettime(clock_id, ctypes.pointer(t)) != 0: + import os + errno_ = ctypes.get_errno() + raise OSError(errno_, os.strerror(errno_)) + return (t.tv_sec * 1000000000) + t.tv_nsec + return f + +_sys_clock_gettime_ns = None + +# call POSIX clock_gettime() and return it as integer (in nanoseconds) +def sys_clock_gettime_ns(clock_id): + global _sys_clock_gettime_ns + if _sys_clock_gettime_ns is None: + _sys_clock_gettime_ns = _sys_clock_gettime_ns_lazy() + return _sys_clock_gettime_ns(clock_id) + +def nm_boot_time_ns(): + # NetworkManager exposes some timestamps as CLOCK_BOOTTIME. + # Try that first (number 7). + try: + return sys_clock_gettime_ns(7) + except OSError as e: + # On systems, where this is not available, fallback to + # CLOCK_MONOTONIC (numeric 1). + # That is what NetworkManager does as well. + import errno + if e.errno == errno.EINVAL: + return sys_clock_gettime_ns(1) + raise +def nm_boot_time_us(): + return nm_boot_time_ns() / 1000 +def nm_boot_time_ms(): + return nm_boot_time_ns() / 1000000 +def nm_boot_time_s(): + return nm_boot_time_ns() / 1000000000 + +############################################################################### diff --git a/introspection/org.freedesktop.NetworkManager.xml b/introspection/org.freedesktop.NetworkManager.xml index 26a618c1bf..b8ad5911f2 100644 --- a/introspection/org.freedesktop.NetworkManager.xml +++ b/introspection/org.freedesktop.NetworkManager.xml @@ -252,6 +252,27 @@ </method> <!-- + CheckpointAdjustRollbackTimeout: + @add_timeout: number of seconds from ~now~ in which the + timeout will expire. Set to 0 to disable the timeout. + Note that the added seconds start counting from now, + not "Created" timestamp or the previous expiration + time. Note that the "Created" property of the checkpoint + will stay unchanged by this call. However, the "RollbackTimeout" + will be recalculated to give the approximate new expiration time. + The new "RollbackTimeout" property will be approximate up to + one second precision, which is the accuracy of the property. + + Reset the timeout for rollback for the checkpoint. + + Since: 1.12 + --> + <method name="CheckpointAdjustRollbackTimeout"> + <arg name="checkpoint" type="o" direction="in"/> + <arg name="add_timeout" type="u" direction="in"/> + </method> + + <!-- Devices: The list of realized network devices. Realized devices are those which diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h index b2e448dfa3..ec9c7f1aee 100644 --- a/libnm-core/nm-dbus-interface.h +++ b/libnm-core/nm-dbus-interface.h @@ -842,16 +842,29 @@ typedef enum { * delete any new connection added after the checkpoint (Since: 1.6) * @NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES: upon rollback, * disconnect any new device appeared after the checkpoint (Since: 1.6) + * @NM_CHECKPOINT_CREATE_FLAG_ALLOW_OVERLAPPING: by default, creating + * a checkpoint fails if there are already existing checkoints that + * reference the same devices. With this flag, creation of such + * checkpoints is allowed, however, if an older checkpoint + * that references overlapping devices gets rolled back, it will + * automatically destroy this checkpoint during rollback. This + * allows to create several overlapping checkpoints in parallel, + * and rollback to them at will. With the special case that + * rolling back to an older checkpoint will invalidate all + * overlapping younger checkpoints. This opts-in that the + * checkpoint can be automatically destroyed by the rollback + * of an older checkpoint. (Since: 1.12) * * The flags for CheckpointCreate call * - * Since: 1.4 + * Since: 1.4 (gi flags generated since 1.12) */ -typedef enum { /*< skip >*/ +typedef enum { /*< flags >*/ NM_CHECKPOINT_CREATE_FLAG_NONE = 0, NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL = 0x01, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS = 0x02, NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES = 0x04, + NM_CHECKPOINT_CREATE_FLAG_ALLOW_OVERLAPPING = 0x08, } NMCheckpointCreateFlags; /** diff --git a/libnm/libnm.ver b/libnm/libnm.ver index b475dcf7ee..386216184e 100644 --- a/libnm/libnm.ver +++ b/libnm/libnm.ver @@ -1334,15 +1334,18 @@ global: libnm_1_12_0 { global: + nm_checkpoint_create_flags_get_type; nm_checkpoint_get_created; nm_checkpoint_get_devices; nm_checkpoint_get_rollback_timeout; nm_checkpoint_get_type; - nm_client_checkpoint_create_async; + nm_client_checkpoint_adjust_rollback_timeout; + nm_client_checkpoint_adjust_rollback_timeout_finish; + nm_client_checkpoint_create; nm_client_checkpoint_create_finish; - nm_client_checkpoint_destroy_async; + nm_client_checkpoint_destroy; nm_client_checkpoint_destroy_finish; - nm_client_checkpoint_rollback_async; + nm_client_checkpoint_rollback; nm_client_checkpoint_rollback_finish; nm_client_get_checkpoints; nm_device_ip_tunnel_get_flags; diff --git a/libnm/nm-client.c b/libnm/nm-client.c index da0c100804..a31a0845e5 100644 --- a/libnm/nm-client.c +++ b/libnm/nm-client.c @@ -2121,7 +2121,7 @@ nm_client_get_checkpoints (NMClient *client) } /** - * nm_client_checkpoint_create_async: + * nm_client_checkpoint_create: * @client: the %NMClient * @devices: (element-type NMDevice): a list of devices for which a * checkpoint should be created. @@ -2139,13 +2139,13 @@ nm_client_get_checkpoints (NMClient *client) * Since: 1.12 **/ void -nm_client_checkpoint_create_async (NMClient *client, - const GPtrArray *devices, - guint32 rollback_timeout, - NMCheckpointCreateFlags flags, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +nm_client_checkpoint_create (NMClient *client, + const GPtrArray *devices, + guint32 rollback_timeout, + NMCheckpointCreateFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { GSimpleAsyncResult *simple; GError *error = NULL; @@ -2158,12 +2158,12 @@ nm_client_checkpoint_create_async (NMClient *client, } simple = g_simple_async_result_new (G_OBJECT (client), callback, user_data, - nm_client_checkpoint_create_async); + nm_client_checkpoint_create); if (cancellable) g_simple_async_result_set_check_cancellable (simple, cancellable); - nm_manager_checkpoint_create_async (NM_CLIENT_GET_PRIVATE (client)->manager, - devices, rollback_timeout, flags, - cancellable, checkpoint_create_cb, simple); + nm_manager_checkpoint_create (NM_CLIENT_GET_PRIVATE (client)->manager, + devices, rollback_timeout, flags, + cancellable, checkpoint_create_cb, simple); } /** @@ -2172,7 +2172,7 @@ nm_client_checkpoint_create_async (NMClient *client, * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * - * Gets the result of a call to nm_client_checkpoint_create_async(). + * Gets the result of a call to nm_client_checkpoint_create(). * * Returns: (transfer full): the new #NMCheckpoint on success, %NULL on * failure, in which case @error will be set. @@ -2214,9 +2214,9 @@ checkpoint_destroy_cb (GObject *object, } /** - * nm_client_checkpoint_destroy_async: + * nm_client_checkpoint_destroy: * @client: the %NMClient - * @checkpoint: a checkpoint + * @checkpoint_path: the D-Bus path for the checkpoint * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the add operation completes * @user_data: (closure): caller-specific data passed to @callback @@ -2226,16 +2226,17 @@ checkpoint_destroy_cb (GObject *object, * Since: 1.12 **/ void -nm_client_checkpoint_destroy_async (NMClient *client, - NMCheckpoint *checkpoint, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +nm_client_checkpoint_destroy (NMClient *client, + const char *checkpoint_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { GSimpleAsyncResult *simple; GError *error = NULL; g_return_if_fail (NM_IS_CLIENT (client)); + g_return_if_fail (checkpoint_path && checkpoint_path[0] == '/'); if (!_nm_client_check_nm_running (client, &error)) { g_simple_async_report_take_gerror_in_idle (G_OBJECT (client), callback, user_data, error); @@ -2243,12 +2244,12 @@ nm_client_checkpoint_destroy_async (NMClient *client, } simple = g_simple_async_result_new (G_OBJECT (client), callback, user_data, - nm_client_checkpoint_destroy_async); + nm_client_checkpoint_destroy); if (cancellable) g_simple_async_result_set_check_cancellable (simple, cancellable); - nm_manager_checkpoint_destroy_async (NM_CLIENT_GET_PRIVATE (client)->manager, - checkpoint, - cancellable, checkpoint_destroy_cb, simple); + nm_manager_checkpoint_destroy (NM_CLIENT_GET_PRIVATE (client)->manager, + checkpoint_path, + cancellable, checkpoint_destroy_cb, simple); } /** @@ -2257,7 +2258,7 @@ nm_client_checkpoint_destroy_async (NMClient *client, * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * - * Gets the result of a call to nm_client_checkpoint_destroy_async(). + * Gets the result of a call to nm_client_checkpoint_destroy(). * * Returns: %TRUE on success or %FALSE on failure, in which case * @error will be set. @@ -2301,9 +2302,9 @@ checkpoint_rollback_cb (GObject *object, } /** - * nm_client_checkpoint_rollback_async: + * nm_client_checkpoint_rollback: * @client: the %NMClient - * @checkpoint: a checkpoint + * @checkpoint_path: the D-Bus path to the checkpoint * @cancellable: a #GCancellable, or %NULL * @callback: (scope async): callback to be called when the add operation completes * @user_data: (closure): caller-specific data passed to @callback @@ -2313,16 +2314,17 @@ checkpoint_rollback_cb (GObject *object, * Since: 1.12 **/ void -nm_client_checkpoint_rollback_async (NMClient *client, - NMCheckpoint *checkpoint, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +nm_client_checkpoint_rollback (NMClient *client, + const char *checkpoint_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { GSimpleAsyncResult *simple; GError *error = NULL; g_return_if_fail (NM_IS_CLIENT (client)); + g_return_if_fail (checkpoint_path && checkpoint_path[0] == '/'); if (!_nm_client_check_nm_running (client, &error)) { g_simple_async_report_take_gerror_in_idle (G_OBJECT (client), callback, user_data, error); @@ -2330,12 +2332,12 @@ nm_client_checkpoint_rollback_async (NMClient *client, } simple = g_simple_async_result_new (G_OBJECT (client), callback, user_data, - nm_client_checkpoint_rollback_async); + nm_client_checkpoint_rollback); if (cancellable) g_simple_async_result_set_check_cancellable (simple, cancellable); - nm_manager_checkpoint_rollback_async (NM_CLIENT_GET_PRIVATE (client)->manager, - checkpoint, - cancellable, checkpoint_rollback_cb, simple); + nm_manager_checkpoint_rollback (NM_CLIENT_GET_PRIVATE (client)->manager, + checkpoint_path, + cancellable, checkpoint_rollback_cb, simple); } /** @@ -2344,7 +2346,7 @@ nm_client_checkpoint_rollback_async (NMClient *client, * @result: the result passed to the #GAsyncReadyCallback * @error: location for a #GError, or %NULL * - * Gets the result of a call to nm_client_checkpoint_rollback_async(). + * Gets the result of a call to nm_client_checkpoint_rollback(). * * Returns: (transfer full) (element-type utf8 guint32): an hash table of * devices and results. Devices are represented by their original @@ -2372,6 +2374,89 @@ nm_client_checkpoint_rollback_finish (NMClient *client, } } +static void +checkpoint_adjust_rollback_timeout_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + gs_unref_object GSimpleAsyncResult *simple = user_data; + GError *error = NULL; + + if (nm_manager_checkpoint_adjust_rollback_timeout_finish (NM_MANAGER (object), result, &error)) + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + else + g_simple_async_result_take_error (simple, error); + + g_simple_async_result_complete (simple); +} + +/** + * nm_client_checkpoint_adjust_rollback_timeout: + * @client: the %NMClient + * @checkpoint_path: a D-Bus path to a checkpoint + * @add_timeout: the timeout in seconds counting from now. + * Set to zero, to disable the timeout. + * @cancellable: a #GCancellable, or %NULL + * @callback: (scope async): callback to be called when the add operation completes + * @user_data: (closure): caller-specific data passed to @callback + * + * Resets the timeout for the checkpoint with path @checkpoint_path + * to @timeout_add. + * + * Since: 1.12 + **/ +void +nm_client_checkpoint_adjust_rollback_timeout (NMClient *client, + const char *checkpoint_path, + guint32 add_timeout, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + GError *error = NULL; + + g_return_if_fail (NM_IS_CLIENT (client)); + g_return_if_fail (checkpoint_path && checkpoint_path[0] == '/'); + + if (!_nm_client_check_nm_running (client, &error)) { + g_simple_async_report_take_gerror_in_idle (G_OBJECT (client), callback, user_data, error); + return; + } + + simple = g_simple_async_result_new (G_OBJECT (client), callback, user_data, + nm_client_checkpoint_rollback); + if (cancellable) + g_simple_async_result_set_check_cancellable (simple, cancellable); + nm_manager_checkpoint_adjust_rollback_timeout (NM_CLIENT_GET_PRIVATE (client)->manager, + checkpoint_path, add_timeout, + cancellable, checkpoint_adjust_rollback_timeout_cb, simple); +} + +/** + * nm_client_checkpoint_adjust_rollback_timeout_finish: + * @client: an #NMClient + * @result: the result passed to the #GAsyncReadyCallback + * @error: location for a #GError, or %NULL + * + * Gets the result of a call to nm_client_checkpoint_adjust_rollback_timeout(). + * + * Returns: %TRUE on success or %FALSE on failure. + * + * Since: 1.12 + **/ +gboolean +nm_client_checkpoint_adjust_rollback_timeout_finish (NMClient *client, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (NM_IS_CLIENT (client), FALSE); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); + + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), + error); +} + /****************************************************************/ /* Object Initialization */ /****************************************************************/ diff --git a/libnm/nm-client.h b/libnm/nm-client.h index 9833f48294..6f4a86c957 100644 --- a/libnm/nm-client.h +++ b/libnm/nm-client.h @@ -408,40 +408,53 @@ NM_AVAILABLE_IN_1_12 const GPtrArray *nm_client_get_checkpoints (NMClient *client); NM_AVAILABLE_IN_1_12 -void nm_client_checkpoint_create_async (NMClient *client, - const GPtrArray *devices, - guint32 rollback_timeout, - NMCheckpointCreateFlags flags, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); +void nm_client_checkpoint_create (NMClient *client, + const GPtrArray *devices, + guint32 rollback_timeout, + NMCheckpointCreateFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); NM_AVAILABLE_IN_1_12 NMCheckpoint *nm_client_checkpoint_create_finish (NMClient *client, GAsyncResult *result, GError **error); NM_AVAILABLE_IN_1_12 -void nm_client_checkpoint_destroy_async (NMClient *client, - NMCheckpoint *checkpoint, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); +void nm_client_checkpoint_destroy (NMClient *client, + const char *checkpoint_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); NM_AVAILABLE_IN_1_12 gboolean nm_client_checkpoint_destroy_finish (NMClient *client, GAsyncResult *result, GError **error); NM_AVAILABLE_IN_1_12 -void nm_client_checkpoint_rollback_async (NMClient *client, - NMCheckpoint *checkpoint, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); +void nm_client_checkpoint_rollback (NMClient *client, + const char *checkpoint_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); NM_AVAILABLE_IN_1_12 GHashTable *nm_client_checkpoint_rollback_finish (NMClient *client, GAsyncResult *result, GError **error); +NM_AVAILABLE_IN_1_12 +void nm_client_checkpoint_adjust_rollback_timeout (NMClient *client, + const char *checkpoint_path, + guint32 add_timeout, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +NM_AVAILABLE_IN_1_12 +gboolean nm_client_checkpoint_adjust_rollback_timeout_finish (NMClient *client, + GAsyncResult *result, + GError **error); + G_END_DECLS #endif /* __NM_CLIENT_H__ */ diff --git a/libnm/nm-manager.c b/libnm/nm-manager.c index 6ad5ad9d38..52b2e13510 100644 --- a/libnm/nm-manager.c +++ b/libnm/nm-manager.c @@ -150,7 +150,7 @@ find_checkpoint_info (NMManager *manager, const char *path) for (iter = priv->added_checkpoints; iter; iter = g_slist_next (iter)) { info = iter->data; - if (nm_streq (path, info->path)) + if (nm_streq0 (path, info->path)) return info; } @@ -790,8 +790,7 @@ NMDevice * nm_manager_get_device_by_path (NMManager *manager, const char *object_path) { const GPtrArray *devices; - int i; - NMDevice *device = NULL; + guint i; g_return_val_if_fail (NM_IS_MANAGER (manager), NULL); g_return_val_if_fail (object_path, NULL); @@ -800,12 +799,25 @@ nm_manager_get_device_by_path (NMManager *manager, const char *object_path) for (i = 0; i < devices->len; i++) { NMDevice *candidate = g_ptr_array_index (devices, i); if (!strcmp (nm_object_get_path (NM_OBJECT (candidate)), object_path)) { - device = candidate; - break; + return candidate; } } + return NULL; +} - return device; +static NMCheckpoint * +get_checkpoint_by_path (NMManager *manager, const char *object_path) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager); + NMCheckpoint *candidate; + guint i; + + for (i = 0; i < priv->checkpoints->len; i++) { + candidate = priv->checkpoints->pdata[i]; + if (nm_streq (nm_object_get_path (NM_OBJECT (candidate)), object_path)) + return candidate; + } + return NULL; } NMDevice * @@ -1196,11 +1208,6 @@ checkpoint_added (NMManager *manager, NMCheckpoint *checkpoint) checkpoint_info_complete (manager, info, checkpoint, NULL); } -static void -checkpoint_removed (NMManager *manager, NMCheckpoint *checkpoint) -{ -} - gboolean nm_manager_deactivate_connection (NMManager *manager, NMActiveConnection *active, @@ -1331,25 +1338,37 @@ checkpoint_created_cb (GObject *object, gpointer user_data) { CheckpointInfo *info = user_data; - GError *error = NULL; + NMManager *self = info->manager; + gs_free_error GError *error = NULL; + NMCheckpoint *checkpoint; nmdbus_manager_call_checkpoint_create_finish (NMDBUS_MANAGER (object), &info->path, result, &error); if (error) { g_dbus_error_strip_remote_error (error); - checkpoint_info_complete (info->manager, info, NULL, error); - g_clear_error (&error); + checkpoint_info_complete (self, info, NULL, error); + return; } + + checkpoint = get_checkpoint_by_path (self, info->path); + if (!checkpoint) { + /* this is really problematic. The async request returned, but + * we don't yet have a visible (fully initialized) NMCheckpoint instance + * to return. Wait longer for it to appear. However, it's ugly. */ + return; + } + + checkpoint_info_complete (self, info, checkpoint, NULL); } void -nm_manager_checkpoint_create_async (NMManager *manager, - const GPtrArray *devices, - guint32 rollback_timeout, - NMCheckpointCreateFlags flags, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +nm_manager_checkpoint_create (NMManager *manager, + const GPtrArray *devices, + guint32 rollback_timeout, + NMCheckpointCreateFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager); gs_free const char **paths = NULL; @@ -1360,7 +1379,7 @@ nm_manager_checkpoint_create_async (NMManager *manager, info = g_slice_new0 (CheckpointInfo); info->manager = manager; info->simple = g_simple_async_result_new (G_OBJECT (manager), callback, user_data, - nm_manager_checkpoint_create_async); + nm_manager_checkpoint_create); if (cancellable) g_simple_async_result_set_check_cancellable (info->simple, cancellable); paths = get_device_paths (devices); @@ -1411,26 +1430,24 @@ checkpoint_destroy_cb (GObject *object, } void -nm_manager_checkpoint_destroy_async (NMManager *manager, - NMCheckpoint *checkpoint, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +nm_manager_checkpoint_destroy (NMManager *manager, + const char *checkpoint_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { - const char *path; GSimpleAsyncResult *simple; g_return_if_fail (NM_IS_MANAGER (manager)); - g_return_if_fail (NM_IS_CHECKPOINT (checkpoint)); + g_return_if_fail (checkpoint_path && checkpoint_path[0] == '/'); simple = g_simple_async_result_new (G_OBJECT (manager), callback, user_data, - nm_manager_checkpoint_destroy_async); + nm_manager_checkpoint_destroy); if (cancellable) g_simple_async_result_set_check_cancellable (simple, cancellable); - path = nm_object_get_path (NM_OBJECT (checkpoint)); nmdbus_manager_call_checkpoint_destroy (NM_MANAGER_GET_PRIVATE (manager)->proxy, - path, + checkpoint_path, cancellable, checkpoint_destroy_cb, simple); } @@ -1443,7 +1460,7 @@ nm_manager_checkpoint_destroy_finish (NMManager *manager, GSimpleAsyncResult *simple; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (manager), - nm_manager_checkpoint_destroy_async), + nm_manager_checkpoint_destroy), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); @@ -1484,26 +1501,24 @@ checkpoint_rollback_cb (GObject *object, } void -nm_manager_checkpoint_rollback_async (NMManager *manager, - NMCheckpoint *checkpoint, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +nm_manager_checkpoint_rollback (NMManager *manager, + const char *checkpoint_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { - const char *path; GSimpleAsyncResult *simple; g_return_if_fail (NM_IS_MANAGER (manager)); - g_return_if_fail (NM_IS_CHECKPOINT (checkpoint)); + g_return_if_fail (checkpoint_path && checkpoint_path[0] == '/'); simple = g_simple_async_result_new (G_OBJECT (manager), callback, user_data, - nm_manager_checkpoint_rollback_async); + nm_manager_checkpoint_rollback); if (cancellable) g_simple_async_result_set_check_cancellable (simple, cancellable); - path = nm_object_get_path (NM_OBJECT (checkpoint)); nmdbus_manager_call_checkpoint_rollback (NM_MANAGER_GET_PRIVATE (manager)->proxy, - path, + checkpoint_path, cancellable, checkpoint_rollback_cb, simple); } @@ -1516,7 +1531,7 @@ nm_manager_checkpoint_rollback_finish (NMManager *manager, GSimpleAsyncResult *simple; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (manager), - nm_manager_checkpoint_rollback_async), + nm_manager_checkpoint_rollback), NULL); simple = G_SIMPLE_ASYNC_RESULT (result); @@ -1526,6 +1541,66 @@ nm_manager_checkpoint_rollback_finish (NMManager *manager, return g_simple_async_result_get_op_res_gpointer (simple); } +static void +checkpoint_adjust_rollback_timeout_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + gs_unref_object GSimpleAsyncResult *simple = user_data; + GError *error = NULL; + + if (nmdbus_manager_call_checkpoint_adjust_rollback_timeout_finish (NMDBUS_MANAGER (object), + result, + &error)) + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + else { + g_dbus_error_strip_remote_error (error); + g_simple_async_result_take_error (simple, error); + } + g_simple_async_result_complete (simple); +} + +void +nm_manager_checkpoint_adjust_rollback_timeout (NMManager *manager, + const char *checkpoint_path, + guint32 add_timeout, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + + g_return_if_fail (NM_IS_MANAGER (manager)); + g_return_if_fail (checkpoint_path && checkpoint_path[0] == '/'); + + simple = g_simple_async_result_new (G_OBJECT (manager), callback, user_data, + nm_manager_checkpoint_adjust_rollback_timeout); + if (cancellable) + g_simple_async_result_set_check_cancellable (simple, cancellable); + + nmdbus_manager_call_checkpoint_adjust_rollback_timeout (NM_MANAGER_GET_PRIVATE (manager)->proxy, + checkpoint_path, + add_timeout, + cancellable, + checkpoint_adjust_rollback_timeout_cb, + simple); +} + +gboolean +nm_manager_checkpoint_adjust_rollback_timeout_finish (NMManager *manager, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (manager), + nm_manager_checkpoint_adjust_rollback_timeout), + FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + return !g_simple_async_result_propagate_error (simple, error); +} + /*****************************************************************************/ static void @@ -1832,7 +1907,6 @@ nm_manager_class_init (NMManagerClass *manager_class) manager_class->active_connection_added = active_connection_added; manager_class->active_connection_removed = active_connection_removed; manager_class->checkpoint_added = checkpoint_added; - manager_class->checkpoint_removed = checkpoint_removed; /* properties */ diff --git a/libnm/nm-manager.h b/libnm/nm-manager.h index ac0630ccf4..0a278aee58 100644 --- a/libnm/nm-manager.h +++ b/libnm/nm-manager.h @@ -184,31 +184,40 @@ gboolean nm_manager_deactivate_connection_finish (NMManager *manager, GError **error); const GPtrArray *nm_manager_get_checkpoints (NMManager *manager); -void nm_manager_checkpoint_create_async (NMManager *manager, - const GPtrArray *devices, - guint32 rollback_timeout, - NMCheckpointCreateFlags flags, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); +void nm_manager_checkpoint_create (NMManager *manager, + const GPtrArray *devices, + guint32 rollback_timeout, + NMCheckpointCreateFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); NMCheckpoint *nm_manager_checkpoint_create_finish (NMManager *manager, GAsyncResult *result, GError **error); -void nm_manager_checkpoint_destroy_async (NMManager *manager, - NMCheckpoint *checkpoint, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); +void nm_manager_checkpoint_destroy (NMManager *manager, + const char *checkpoint_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); gboolean nm_manager_checkpoint_destroy_finish (NMManager *manager, GAsyncResult *result, GError **error); -void nm_manager_checkpoint_rollback_async (NMManager *manager, - NMCheckpoint *checkpoint, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); +void nm_manager_checkpoint_rollback (NMManager *manager, + const char *checkpoint_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); GHashTable *nm_manager_checkpoint_rollback_finish (NMManager *manager, GAsyncResult *result, GError **error); +void nm_manager_checkpoint_adjust_rollback_timeout (NMManager *manager, + const char *checkpoint_path, + guint32 add_timeout, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean nm_manager_checkpoint_adjust_rollback_timeout_finish (NMManager *manager, + GAsyncResult *result, + GError **error); #endif /* __NM_MANAGER_H__ */ diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c index d655406437..cd2c68af86 100644 --- a/src/devices/wifi/nm-device-olpc-mesh.c +++ b/src/devices/wifi/nm-device-olpc-mesh.c @@ -387,7 +387,7 @@ static void find_companion (NMDeviceOlpcMesh *self) { NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self); - const CList *all_devices; + const CList *tmp_lst; NMDevice *candidate; if (priv->companion) @@ -396,8 +396,7 @@ find_companion (NMDeviceOlpcMesh *self) nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_COMPANION, TRUE); /* Try to find the companion if it's already known to the NMManager */ - all_devices = nm_manager_get_devices (priv->manager); - c_list_for_each_entry (candidate, all_devices, devices_lst) { + nm_manager_for_each_device (priv->manager, candidate, tmp_lst) { if (check_companion (self, candidate)) { nm_device_queue_recheck_available (NM_DEVICE (self), NM_DEVICE_STATE_REASON_NONE, diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c index ea903bc724..450009f0c6 100644 --- a/src/devices/wifi/nm-iwd-manager.c +++ b/src/devices/wifi/nm-iwd-manager.c @@ -39,7 +39,7 @@ typedef struct { } KnownNetworkData; typedef struct { - NMManager *nm_manager; + NMManager *manager; GCancellable *cancellable; gboolean running; GDBusObjectManager *object_manager; @@ -138,7 +138,7 @@ psk_agent_dbus_method_cb (GDBusConnection *connection, goto return_error; } - device = nm_manager_get_device_by_ifindex (priv->nm_manager, ifindex); + device = nm_manager_get_device_by_ifindex (priv->manager, ifindex); if (!NM_IS_DEVICE_IWD (device)) { _LOGE ("IWD device named %s is not a Wifi device in IWD Agent request", ifname); @@ -291,7 +291,7 @@ set_device_dbus_object (NMIwdManager *self, GDBusInterface *interface, return; } - device = nm_manager_get_device_by_ifindex (priv->nm_manager, ifindex); + device = nm_manager_get_device_by_ifindex (priv->manager, ifindex); if (!NM_IS_DEVICE_IWD (device)) { _LOGE ("IWD device named %s is not a Wifi device", ifname); return; @@ -460,7 +460,7 @@ name_owner_changed (GObject *object, GParamSpec *pspec, gpointer user_data) g_clear_object (&priv->object_manager); prepare_object_manager (self); } else { - const CList *all_devices; + const CList *tmp_lst; NMDevice *device; if (!priv->running) @@ -468,13 +468,11 @@ name_owner_changed (GObject *object, GParamSpec *pspec, gpointer user_data) priv->running = false; - all_devices = nm_manager_get_devices (priv->nm_manager); - c_list_for_each_entry (device, all_devices, devices_lst) { - if (!NM_IS_DEVICE_IWD (device)) - continue; - - nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), - NULL); + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + if (NM_IS_DEVICE_IWD (device)) { + nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), + NULL); + } } } } @@ -639,8 +637,8 @@ nm_iwd_manager_init (NMIwdManager *self) { NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); - priv->nm_manager = g_object_ref (nm_manager_get ()); - g_signal_connect (priv->nm_manager, NM_MANAGER_DEVICE_ADDED, + priv->manager = g_object_ref (nm_manager_get ()); + g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED, G_CALLBACK (device_added), self); priv->cancellable = g_cancellable_new (); @@ -679,9 +677,9 @@ dispose (GObject *object) g_slist_free_full (priv->known_networks, (GDestroyNotify) known_network_free); priv->known_networks = NULL; - if (priv->nm_manager) { - g_signal_handlers_disconnect_by_data (priv->nm_manager, self); - g_clear_object (&priv->nm_manager); + if (priv->manager) { + g_signal_handlers_disconnect_by_data (priv->manager, self); + g_clear_object (&priv->manager); } G_OBJECT_CLASS (nm_iwd_manager_parent_class)->dispose (object); diff --git a/src/nm-audit-manager.h b/src/nm-audit-manager.h index 56e26584e5..59b8048f5a 100644 --- a/src/nm-audit-manager.h +++ b/src/nm-audit-manager.h @@ -57,6 +57,7 @@ typedef struct _NMAuditManagerClass NMAuditManagerClass; #define NM_AUDIT_OP_CHECKPOINT_CREATE "checkpoint-create" #define NM_AUDIT_OP_CHECKPOINT_ROLLBACK "checkpoint-rollback" #define NM_AUDIT_OP_CHECKPOINT_DESTROY "checkpoint-destroy" +#define NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT "checkpoint-adjust-rollback-timeout" GType nm_audit_manager_get_type (void); NMAuditManager *nm_audit_manager_get (void); diff --git a/src/nm-checkpoint-manager.c b/src/nm-checkpoint-manager.c index 7345f9a41d..e55d632b91 100644 --- a/src/nm-checkpoint-manager.c +++ b/src/nm-checkpoint-manager.c @@ -35,9 +35,7 @@ struct _NMCheckpointManager { NMManager *_manager; GParamSpec *property_spec; - GHashTable *checkpoints; - CList list; - guint rollback_timeout_id; + CList checkpoints_lst_head; }; #define GET_MANAGER(self) \ @@ -58,13 +56,6 @@ struct _NMCheckpointManager { /*****************************************************************************/ -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), @@ -72,83 +63,60 @@ notify_checkpoints (NMCheckpointManager *self) { } static void -item_destroy (gpointer data) +destroy_checkpoint (NMCheckpointManager *self, NMCheckpoint *checkpoint, gboolean log_destroy) { - CheckpointItem *item = data; + 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)); - c_list_unlink_stale (&item->list); - nm_dbus_object_unexport (NM_DBUS_OBJECT (item->checkpoint)); - g_object_unref (G_OBJECT (item->checkpoint)); - g_slice_free (CheckpointItem, item); -} + nm_checkpoint_set_timeout_callback (checkpoint, NULL, NULL); -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_dbus_object_get_path (NM_DBUS_OBJECT (item->checkpoint)); - if (!g_hash_table_remove (self->checkpoints, path)) - nm_assert_not_reached(); - removed = TRUE; - } - } + c_list_unlink (&checkpoint->checkpoints_lst); - self->rollback_timeout_id = 0; - update_rollback_timeout (self); + if (log_destroy) + nm_checkpoint_log_destroy (checkpoint); - if (removed) - notify_checkpoints (self); + notify_checkpoints (self); - return G_SOURCE_REMOVE; + nm_dbus_object_unexport (NM_DBUS_OBJECT (checkpoint)); + g_object_unref (checkpoint); } -static void -update_rollback_timeout (NMCheckpointManager *self) +static GVariant * +rollback_checkpoint (NMCheckpointManager *self, NMCheckpoint *checkpoint) { - CheckpointItem *item; - gint64 ts, delta, next = G_MAXINT64; + GVariant *result; + const CList *iter; - c_list_for_each_entry (item, &self->list, list) { - ts = nm_checkpoint_get_rollback_ts (item->checkpoint); - if (ts && ts < next) - next = ts; - } + nm_assert (c_list_contains (&self->checkpoints_lst_head, &checkpoint->checkpoints_lst)); - nm_clear_g_source (&self->rollback_timeout_id); + /* 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); - 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); + 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 NMCheckpoint * -find_checkpoint_for_device (NMCheckpointManager *self, NMDevice *device) +static void +rollback_timeout_cb (NMCheckpoint *checkpoint, + gpointer user_data) { - CheckpointItem *item; - - c_list_for_each_entry (item, &self->list, list) { - if (nm_checkpoint_includes_device (item->checkpoint, device)) - return item->checkpoint; - } + NMCheckpointManager *self = user_data; + gs_unref_variant GVariant *result = NULL; - return NULL; + result = rollback_checkpoint (self, checkpoint); } NMCheckpoint * @@ -160,57 +128,61 @@ nm_checkpoint_manager_create (NMCheckpointManager *self, { 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); + devices = g_ptr_array_new (); + if (!device_paths || !device_paths[0]) { - const char *device_path; - const CList *all_devices; - GPtrArray *paths; + const CList *tmp_lst; - paths = g_ptr_array_new (); - all_devices = nm_manager_get_devices (manager); - c_list_for_each_entry (device, all_devices, devices_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; - device_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (device)); - if (device_path) - g_ptr_array_add (paths, (gpointer) device_path); + nm_assert (nm_dbus_object_get_path (NM_DBUS_OBJECT (device))); + g_ptr_array_add (devices, device); } - 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; + } 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); + } } - 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 (!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)) { - for (i = 0; i < devices->len; i++) { - device = devices->pdata[i]; - checkpoint = find_checkpoint_for_device (self, device); - if (checkpoint) { + 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), @@ -220,115 +192,132 @@ nm_checkpoint_manager_create (NMCheckpointManager *self, } } - 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); + checkpoint = nm_checkpoint_new (manager, devices, rollback_timeout, flags); - checkpoint_path = nm_dbus_object_export (NM_DBUS_OBJECT (checkpoint)); - - item = g_slice_new0 (CheckpointItem); - item->checkpoint = checkpoint; - c_list_link_tail (&self->list, &item->list); - - if (!g_hash_table_insert (self->checkpoints, (gpointer) checkpoint_path, item)) - g_return_val_if_reached (NULL); + 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); - update_rollback_timeout (self); - return checkpoint; } -gboolean -nm_checkpoint_manager_destroy_all (NMCheckpointManager *self, - GError **error) +void +nm_checkpoint_manager_destroy_all (NMCheckpointManager *self) { - g_return_val_if_fail (self, FALSE); + NMCheckpoint *checkpoint; - g_hash_table_remove_all (self->checkpoints); - notify_checkpoints (self); + g_return_if_fail (self); - return TRUE; + 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 *checkpoint_path, + const char *path, GError **error) { - gboolean ret; + NMCheckpoint *checkpoint; g_return_val_if_fail (self, FALSE); - g_return_val_if_fail (checkpoint_path && checkpoint_path[0] == '/', FALSE); + g_return_val_if_fail (path && 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); + if (!nm_streq (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 *checkpoint_path, + const char *path, GVariant **results, GError **error) { - CheckpointItem *item; + NMCheckpoint *checkpoint; g_return_val_if_fail (self, FALSE); - g_return_val_if_fail (checkpoint_path && checkpoint_path[0] == '/', 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); - 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); + checkpoint = nm_checkpoint_manager_lookup_by_path (self, path, error); + if (!checkpoint) return FALSE; - } - - *results = nm_checkpoint_rollback (item->checkpoint); - g_hash_table_remove (self->checkpoints, checkpoint_path); - notify_checkpoints (self); + *results = rollback_checkpoint (self, checkpoint); return TRUE; } -char ** -nm_checkpoint_manager_get_checkpoint_paths (NMCheckpointManager *self) +NMCheckpoint * +nm_checkpoint_manager_lookup_by_path (NMCheckpointManager *self, const char *path, GError **error) { - CheckpointItem *item; - char **strv; - guint num, i = 0; + NMCheckpoint *checkpoint; + + g_return_val_if_fail (self, NULL); - num = g_hash_table_size (self->checkpoints); - if (!num) { - nm_assert (c_list_is_empty (&self->list)); + 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)) { + g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "checkpoint %s does not exist", path); return NULL; } - strv = g_new (char *, num + 1); - c_list_for_each_entry (item, &self->list, list) - strv[i++] = g_strdup (nm_dbus_object_get_path (NM_DBUS_OBJECT (item->checkpoint))); + 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 * @@ -347,22 +336,17 @@ nm_checkpoint_manager_new (NMManager *manager, GParamSpec *spec) * 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); - + c_list_init (&self->checkpoints_lst_head); return self; } void -nm_checkpoint_manager_unref (NMCheckpointManager *self) +nm_checkpoint_manager_free (NMCheckpointManager *self) { if (!self) return; - nm_clear_g_source (&self->rollback_timeout_id); - g_hash_table_destroy (self->checkpoints); - + nm_checkpoint_manager_destroy_all (self); g_slice_free (NMCheckpointManager, self); } diff --git a/src/nm-checkpoint-manager.h b/src/nm-checkpoint-manager.h index 4ff75303ed..ca66ef168a 100644 --- a/src/nm-checkpoint-manager.h +++ b/src/nm-checkpoint-manager.h @@ -28,7 +28,12 @@ typedef struct _NMCheckpointManager NMCheckpointManager; NMCheckpointManager *nm_checkpoint_manager_new (NMManager *manager, GParamSpec *spec); -void nm_checkpoint_manager_unref (NMCheckpointManager *self); + +void nm_checkpoint_manager_free (NMCheckpointManager *self); + +NMCheckpoint *nm_checkpoint_manager_lookup_by_path (NMCheckpointManager *self, + const char *path, + GError **error); NMCheckpoint *nm_checkpoint_manager_create (NMCheckpointManager *self, const char *const*device_names, @@ -36,17 +41,22 @@ NMCheckpoint *nm_checkpoint_manager_create (NMCheckpointManager *self, NMCheckpointCreateFlags flags, GError **error); -gboolean nm_checkpoint_manager_destroy_all (NMCheckpointManager *self, - GError **error); +void nm_checkpoint_manager_destroy_all (NMCheckpointManager *self); gboolean nm_checkpoint_manager_destroy (NMCheckpointManager *self, - const char *checkpoint_path, + const char *path, GError **error); gboolean nm_checkpoint_manager_rollback (NMCheckpointManager *self, - const char *checkpoint_path, + const char *path, GVariant **results, GError **error); -char **nm_checkpoint_manager_get_checkpoint_paths (NMCheckpointManager *self); +gboolean nm_checkpoint_manager_adjust_rollback_timeout (NMCheckpointManager *self, + const char *path, + guint32 add_timeout, + GError **error); + +const char **nm_checkpoint_manager_get_checkpoint_paths (NMCheckpointManager *self, + guint *out_length); #endif /* __NM_CHECKPOINT_MANAGER_H__ */ diff --git a/src/nm-checkpoint.c b/src/nm-checkpoint.c index d85d2d5499..c508405185 100644 --- a/src/nm-checkpoint.c +++ b/src/nm-checkpoint.c @@ -25,6 +25,7 @@ #include <string.h> #include "nm-active-connection.h" +#include "nm-act-request.h" #include "nm-auth-subject.h" #include "nm-core-utils.h" #include "nm-dbus-interface.h" @@ -48,27 +49,26 @@ typedef struct { NMUnmanFlagOp unmanaged_explicit; } DeviceCheckpoint; -NM_GOBJECT_PROPERTIES_DEFINE_BASE ( +NM_GOBJECT_PROPERTIES_DEFINE (NMCheckpoint, PROP_DEVICES, PROP_CREATED, PROP_ROLLBACK_TIMEOUT, ); -typedef struct { +struct _NMCheckpointPrivate { /* properties */ GHashTable *devices; - gint64 created; - guint32 rollback_timeout; + gint64 created_at_ms; + guint32 rollback_timeout_s; + guint timeout_id; + /* private members */ /* private members */ NMManager *manager; - gint64 rollback_ts; NMCheckpointCreateFlags flags; GHashTable *connection_uuids; -} NMCheckpointPrivate; -struct _NMCheckpoint { - NMDBusObject parent; - NMCheckpointPrivate _priv; + NMCheckpointTimeoutCallback timeout_cb; + gpointer timeout_data; }; struct _NMCheckpointClass { @@ -77,7 +77,7 @@ struct _NMCheckpointClass { G_DEFINE_TYPE (NMCheckpoint, nm_checkpoint, NM_TYPE_DBUS_OBJECT) -#define NM_CHECKPOINT_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMCheckpoint, NM_IS_CHECKPOINT) +#define NM_CHECKPOINT_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMCheckpoint, NM_IS_CHECKPOINT) /*****************************************************************************/ @@ -101,20 +101,53 @@ G_DEFINE_TYPE (NMCheckpoint, nm_checkpoint, NM_TYPE_DBUS_OBJECT) /*****************************************************************************/ -guint64 -nm_checkpoint_get_rollback_ts (NMCheckpoint *self) +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) { - g_return_val_if_fail (NM_IS_CHECKPOINT (self), 0); + NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self); + guint i; - return NM_CHECKPOINT_GET_PRIVATE (self)->rollback_ts; + for (i = 0; i < n_devices; i++) { + if (g_hash_table_contains (priv->devices, devices[i])) + return devices[i]; + } + return NULL; } -gboolean -nm_checkpoint_includes_device (NMCheckpoint *self, NMDevice *device) +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; - return g_hash_table_contains (priv->devices, 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 * @@ -348,11 +381,10 @@ next_dev: } if (NM_FLAGS_HAS (priv->flags, NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES)) { - const CList *all_devices; + const CList *tmp_lst; NMDeviceState state; - all_devices = nm_manager_get_devices (priv->manager); - c_list_for_each_entry (device, all_devices, devices_lst) { + 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); @@ -371,8 +403,7 @@ next_dev: } static DeviceCheckpoint * -device_checkpoint_create (NMDevice *device, - GError **error) +device_checkpoint_create (NMDevice *device) { DeviceCheckpoint *dev_checkpoint; NMConnection *applied_connection; @@ -380,6 +411,9 @@ device_checkpoint_create (NMDevice *device, 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); @@ -389,25 +423,21 @@ device_checkpoint_create (NMDevice *device, dev_checkpoint->realized = nm_device_is_real (device); 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); + dev_checkpoint->unmanaged_explicit = !!nm_device_get_unmanaged_flags (device, + NM_UNMANAGED_USER_EXPLICIT); } else dev_checkpoint->unmanaged_explicit = NM_UNMAN_FLAG_OP_FORGET; - applied_connection = nm_device_get_applied_connection (device); - if (applied_connection) { - dev_checkpoint->applied_connection = - nm_simple_connection_new_clone (applied_connection); + 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); - settings_connection = nm_device_get_settings_connection (device); - g_return_val_if_fail (settings_connection, NULL); + dev_checkpoint->applied_connection = nm_simple_connection_new_clone (applied_connection); dev_checkpoint->settings_connection = - nm_simple_connection_new_clone (NM_CONNECTION (settings_connection)); - - act_request = nm_device_get_act_request (device); - g_return_val_if_fail (act_request, NULL); + nm_simple_connection_new_clone (NM_CONNECTION (settings_connection)); dev_checkpoint->ac_version_id = - nm_active_connection_version_id_get (NM_ACTIVE_CONNECTION (act_request)); + nm_active_connection_version_id_get (NM_ACTIVE_CONNECTION (act_request)); } return dev_checkpoint; @@ -426,6 +456,57 @@ device_checkpoint_destroy (gpointer data) g_slice_free (DeviceCheckpoint, 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_ms (); + 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 @@ -434,22 +515,20 @@ get_property (GObject *object, guint prop_id, { 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_dbus_utils_g_value_set_object_path_array (value, devices, NULL, NULL); + nm_dbus_utils_g_value_set_object_path_from_hash (value, + priv->devices, + FALSE); break; case PROP_CREATED: - g_value_set_int64 (value, priv->created); + g_value_set_int64 (value, + nm_utils_monotonic_timestamp_as_boottime (priv->created_at_ms, + NM_UTILS_NS_PER_MSEC)); break; case PROP_ROLLBACK_TIMEOUT: - g_value_set_uint (value, priv->rollback_timeout); + g_value_set_uint (value, priv->rollback_timeout_s); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -462,47 +541,47 @@ get_property (GObject *object, guint prop_id, static void nm_checkpoint_init (NMCheckpoint *self) { - NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (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); } NMCheckpoint * -nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_timeout, - NMCheckpointCreateFlags flags, GError **error) +nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_timeout_s, + NMCheckpointCreateFlags flags) { NMCheckpoint *self; NMCheckpointPrivate *priv; NMSettingsConnection *const *con; - DeviceCheckpoint *dev_checkpoint; - NMDevice *device; + 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 (!error || !*error, NULL); - - if (!devices->len) { - g_set_error_literal (error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_INVALID_ARGUMENTS, - "no device available"); - return 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 = 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->rollback_timeout_s = rollback_timeout_s; + priv->created_at_ms = nm_utils_get_monotonic_timestamp_ms (); 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++) { @@ -512,13 +591,15 @@ nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_time } 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); + NMDevice *device = devices->pdata[i]; + + /* FIXME: as long as the check point instance exists, it won't let go + * of the device. That is a bug, for example, if you have a ethernet + * device that gets removed (rmmod), the checkpoint will reference + * a non-existing D-Bus path of a device. */ + g_hash_table_insert (priv->devices, + device, + device_checkpoint_create (device)); } return self; @@ -530,9 +611,13 @@ dispose (GObject *object) NMCheckpoint *self = NM_CHECKPOINT (object); NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self); + nm_assert (c_list_is_empty (&self->checkpoints_lst)); + g_clear_pointer (&priv->devices, g_hash_table_unref); g_clear_pointer (&priv->connection_uuids, g_hash_table_unref); + nm_clear_g_source (&priv->timeout_id); + G_OBJECT_CLASS (nm_checkpoint_parent_class)->dispose (object); } @@ -557,6 +642,8 @@ 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); diff --git a/src/nm-checkpoint.h b/src/nm-checkpoint.h index 5d0f73295a..c8598f380a 100644 --- a/src/nm-checkpoint.h +++ b/src/nm-checkpoint.h @@ -35,16 +35,35 @@ #define NM_CHECKPOINT_CREATED "created" #define NM_CHECKPOINT_ROLLBACK_TIMEOUT "rollback-timeout" -typedef struct _NMCheckpoint NMCheckpoint; +typedef struct _NMCheckpointPrivate NMCheckpointPrivate; + +typedef struct { + NMDBusObject parent; + NMCheckpointPrivate *_priv; + CList checkpoints_lst; +} NMCheckpoint; + typedef struct _NMCheckpointClass NMCheckpointClass; GType nm_checkpoint_get_type (void); NMCheckpoint *nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_timeout, - NMCheckpointCreateFlags flags, GError **error); + NMCheckpointCreateFlags flags); + +typedef void (*NMCheckpointTimeoutCallback) (NMCheckpoint *self, + gpointer user_data); + +void nm_checkpoint_log_destroy (NMCheckpoint *self); + +void nm_checkpoint_set_timeout_callback (NMCheckpoint *self, + NMCheckpointTimeoutCallback callback, + gpointer user_data); -guint64 nm_checkpoint_get_rollback_ts (NMCheckpoint *checkpoint); -gboolean nm_checkpoint_includes_device (NMCheckpoint *checkpoint, NMDevice *device); GVariant *nm_checkpoint_rollback (NMCheckpoint *self); +void nm_checkpoint_adjust_rollback_timeout (NMCheckpoint *self, guint32 add_timeout); + +NMDevice *nm_checkpoint_includes_devices (NMCheckpoint *self, NMDevice *const*devices, guint n_devices); +NMDevice *nm_checkpoint_includes_devices_of (NMCheckpoint *self, NMCheckpoint *cp_for_devices); + #endif /* __NETWORKMANAGER_CHECKPOINT_H__ */ diff --git a/src/nm-dbus-utils.c b/src/nm-dbus-utils.c index eab6cb6125..2464ff79f7 100644 --- a/src/nm-dbus-utils.c +++ b/src/nm-dbus-utils.c @@ -121,32 +121,35 @@ nm_dbus_utils_g_value_set_object_path (GValue *value, gpointer object) } void -nm_dbus_utils_g_value_set_object_path_array (GValue *value, - GSList *objects, - gboolean (*filter_func) (GObject *object, gpointer user_data), - gpointer user_data) +nm_dbus_utils_g_value_set_object_path_from_hash (GValue *value, + GHashTable *hash /* has keys of NMDBusObject type. */, + gboolean expect_all_exported) { - char **paths; - guint i; - GSList *iter; - - paths = g_new (char *, g_slist_length (objects) + 1); - for (i = 0, iter = objects; iter; iter = iter->next) { - NMDBusObject *object = iter->data; + NMDBusObject *obj; + char **strv; + guint i, n; + GHashTableIter iter; + + nm_assert (value); + nm_assert (hash); + + n = g_hash_table_size (hash); + strv = g_new (char *, n + 1); + i = 0; + g_hash_table_iter_init (&iter, hash); + while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) { const char *path; - path = nm_dbus_object_get_path (object); - if (!path) - continue; - if ( filter_func - && !filter_func ((GObject *) object, user_data)) + path = nm_dbus_object_get_path (obj); + if (!path) { + nm_assert (!expect_all_exported); continue; - paths[i++] = g_strdup (path); + } + strv[i++] = g_strdup (path); } - paths[i] = NULL; - g_value_take_boxed (value, paths); + nm_assert (i <= n); + strv[i] = NULL; + g_value_take_boxed (value, strv); } /*****************************************************************************/ - - diff --git a/src/nm-dbus-utils.h b/src/nm-dbus-utils.h index 4b0ec5c477..1d9c92ec36 100644 --- a/src/nm-dbus-utils.h +++ b/src/nm-dbus-utils.h @@ -166,9 +166,8 @@ GVariant *nm_dbus_utils_get_property (GObject *obj, void nm_dbus_utils_g_value_set_object_path (GValue *value, gpointer object); -void nm_dbus_utils_g_value_set_object_path_array (GValue *value, - GSList *objects, - gboolean (*filter_func) (GObject *object, gpointer user_data), - gpointer user_data); +void nm_dbus_utils_g_value_set_object_path_from_hash (GValue *value, + GHashTable *hash, + gboolean expect_all_exported); #endif /* __NM_DBUS_UTILS_H__ */ diff --git a/src/nm-manager.c b/src/nm-manager.c index 7629d0df34..b64cb39aa4 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -5956,13 +5956,15 @@ checkpoint_auth_done_cb (NMAuthChain *chain, GVariant *variant = NULL; GError *error = NULL; const char *arg = NULL; + guint32 add_timeout; op = nm_auth_chain_get_data (chain, "audit-op"); priv->auth_chains = g_slist_remove (priv->auth_chains, chain); result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK); - if ( nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_DESTROY) - || nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_ROLLBACK)) + if (NM_IN_STRSET (op, NM_AUDIT_OP_CHECKPOINT_DESTROY, + NM_AUDIT_OP_CHECKPOINT_ROLLBACK, + NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT)) arg = checkpoint_path = nm_auth_chain_get_data (chain, "checkpoint_path"); if (auth_error) { @@ -5995,6 +5997,10 @@ checkpoint_auth_done_cb (NMAuthChain *chain, } else if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_ROLLBACK)) { nm_checkpoint_manager_rollback (_checkpoint_mgr_get (self, TRUE), checkpoint_path, &variant, &error); + } else if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT)) { + add_timeout = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "add_timeout")); + nm_checkpoint_manager_adjust_rollback_timeout (_checkpoint_mgr_get (self, TRUE), + checkpoint_path, add_timeout, &error); } else g_return_if_reached (); } @@ -6007,7 +6013,6 @@ checkpoint_auth_done_cb (NMAuthChain *chain, else g_dbus_method_invocation_return_value (context, variant); - nm_auth_chain_unref (chain); } @@ -6110,6 +6115,39 @@ impl_manager_checkpoint_rollback (NMDBusObject *obj, nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); } +static void +impl_manager_checkpoint_adjust_rollback_timeout (NMDBusObject *obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended *method_info, + GDBusConnection *connection, + const char *sender, + GDBusMethodInvocation *invocation, + GVariant *parameters) +{ + NMManager *self = NM_MANAGER (obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); + NMAuthChain *chain; + const char *checkpoint_path; + guint32 add_timeout; + + chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal (invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "Unable to authenticate request."); + return; + } + + g_variant_get (parameters, "(&ou)", &checkpoint_path, &add_timeout); + + priv->auth_chains = g_slist_append (priv->auth_chains, chain); + nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT, NULL); + nm_auth_chain_set_data (chain, "checkpoint_path", g_strdup (checkpoint_path), g_free); + nm_auth_chain_set_data (chain, "add_timeout", GUINT_TO_POINTER (add_timeout), NULL); + nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); +} + /*****************************************************************************/ static void @@ -6471,7 +6509,6 @@ get_property (GObject *object, guint prop_id, NMConfigData *config_data; const NMGlobalDnsConfig *dns_config; const char *type; - char **strv; const char *path; NMActiveConnection *ac; GPtrArray *ptrarr; @@ -6578,10 +6615,11 @@ get_property (GObject *object, guint prop_id, TRUE))); break; case PROP_CHECKPOINTS: - strv = NULL; - if (priv->checkpoint_mgr) - strv = nm_checkpoint_manager_get_checkpoint_paths (priv->checkpoint_mgr); - g_value_take_boxed (value, strv); + g_value_take_boxed (value, + priv->checkpoint_mgr + ? nm_utils_strv_make_deep_copied (nm_checkpoint_manager_get_checkpoint_paths (priv->checkpoint_mgr, + NULL)) + : NULL); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -6664,10 +6702,7 @@ dispose (GObject *object) nm_clear_g_source (&priv->devices_inited_id); - if (priv->checkpoint_mgr) { - nm_checkpoint_manager_destroy_all (priv->checkpoint_mgr, NULL); - g_clear_pointer (&priv->checkpoint_mgr, nm_checkpoint_manager_unref); - } + g_clear_pointer (&priv->checkpoint_mgr, nm_checkpoint_manager_free); if (priv->auth_mgr) { g_signal_handlers_disconnect_by_func (priv->auth_mgr, @@ -6967,6 +7002,16 @@ static const NMDBusInterfaceInfoExtended interface_info_manager = { ), .handle = impl_manager_checkpoint_rollback, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( + NM_DEFINE_GDBUS_METHOD_INFO_INIT ( + "CheckpointAdjustRollbackTimeout", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( + NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"), + NM_DEFINE_GDBUS_ARG_INFO ("add_timeout", "u"), + ), + ), + .handle = impl_manager_checkpoint_adjust_rollback_timeout, + ), ), .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS ( &nm_signal_info_property_changed_legacy, diff --git a/src/nm-manager.h b/src/nm-manager.h index 638ceed6ea..3d651c7f7d 100644 --- a/src/nm-manager.h +++ b/src/nm-manager.h @@ -83,13 +83,14 @@ gboolean nm_manager_start (NMManager *manager, GError **error); void nm_manager_stop (NMManager *manager); NMState nm_manager_get_state (NMManager *manager); + const CList * nm_manager_get_active_connections (NMManager *manager); #define nm_manager_for_each_active_connection(manager, iter, tmp_list) \ for (tmp_list = nm_manager_get_active_connections (manager), \ iter = c_list_entry (tmp_list->next, NMActiveConnection, active_connections_lst); \ ({ \ - gboolean _has_next = (&iter->active_connections_lst != tmp_list); \ + const gboolean _has_next = (&iter->active_connections_lst != tmp_list); \ \ if (!_has_next) \ iter = NULL; \ @@ -107,6 +108,18 @@ void nm_manager_write_device_state (NMManager *manager); const CList * nm_manager_get_devices (NMManager *manager); +#define nm_manager_for_each_device(manager, iter, tmp_list) \ + for (tmp_list = nm_manager_get_devices (manager), \ + iter = c_list_entry (tmp_list->next, NMDevice, devices_lst); \ + ({ \ + const gboolean _has_next = (&iter->devices_lst != tmp_list); \ + \ + if (!_has_next) \ + iter = NULL; \ + _has_next; \ + }); \ + iter = c_list_entry (iter->devices_lst.next, NMDevice, devices_lst)) + NMDevice * nm_manager_get_device_by_ifindex (NMManager *manager, int ifindex); NMDevice * nm_manager_get_device_by_path (NMManager *manager, diff --git a/src/nm-policy.c b/src/nm-policy.c index 72351efe06..3984feb326 100644 --- a/src/nm-policy.c +++ b/src/nm-policy.c @@ -385,7 +385,7 @@ get_best_ip_device (NMPolicy *self, gboolean fully_activated) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); - const CList *all_devices; + const CList *tmp_lst; NMDevice *device; NMDevice *best_device; NMDevice *prev_device; @@ -401,8 +401,7 @@ get_best_ip_device (NMPolicy *self, ? (fully_activated ? priv->default_device4 : priv->activating_device4) : (fully_activated ? priv->default_device6 : priv->activating_device6); - all_devices = nm_manager_get_devices (priv->manager); - c_list_for_each_entry (device, all_devices, devices_lst) { + nm_manager_for_each_device (priv->manager, device, tmp_lst) { NMDeviceState state; const NMPObject *r; NMConnection *connection; @@ -462,11 +461,10 @@ static gboolean all_devices_not_active (NMPolicy *self) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); - const CList *all_devices; + const CList *tmp_lst; NMDevice *device; - all_devices = nm_manager_get_devices (priv->manager); - c_list_for_each_entry (device, all_devices, devices_lst) { + nm_manager_for_each_device (priv->manager, device, tmp_lst) { NMDeviceState state; state = nm_device_get_state (device); @@ -2186,13 +2184,12 @@ schedule_activate_all_cb (gpointer user_data) { NMPolicy *self = user_data; NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); - const CList *all_devices; + const CList *tmp_lst; NMDevice *device; priv->schedule_activate_all_id = 0; - all_devices = nm_manager_get_devices (priv->manager); - c_list_for_each_entry (device, all_devices, devices_lst) + nm_manager_for_each_device (priv->manager, device, tmp_lst) schedule_activate_check (self, device); return G_SOURCE_REMOVE; @@ -2227,7 +2224,7 @@ firewall_state_changed (NMFirewallManager *manager, { NMPolicy *self = (NMPolicy *) user_data; NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); - const CList *all_devices; + const CList *tmp_lst; NMDevice *device; if (initialized_now) { @@ -2241,8 +2238,7 @@ firewall_state_changed (NMFirewallManager *manager, return; /* add interface of each device to correct zone */ - all_devices = nm_manager_get_devices (priv->manager); - c_list_for_each_entry (device, all_devices, devices_lst) + nm_manager_for_each_device (priv->manager, device, tmp_lst) nm_device_update_firewall_zone (device); } @@ -2288,14 +2284,13 @@ connection_updated (NMSettings *settings, { NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF (priv); - const CList *all_devices; + const CList *tmp_lst; NMDevice *device = NULL; NMDevice *dev; if (by_user) { /* find device with given connection */ - all_devices = nm_manager_get_devices (priv->manager); - c_list_for_each_entry (dev, all_devices, devices_lst) { + nm_manager_for_each_device (priv->manager, dev, tmp_lst) { if (nm_device_get_settings_connection (dev) == connection) { device = dev; break; |