diff options
| author | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
| commit | e74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch) | |
| tree | 3469f17ea9af91f7ff169b890633bda68b0cf76e /src/nm-cloud-setup/nm-cloud-setup-utils.c | |
| parent | bfe522304da217296e2a61040f58e35ec5d6f3f2 (diff) | |
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'src/nm-cloud-setup/nm-cloud-setup-utils.c')
| -rw-r--r-- | src/nm-cloud-setup/nm-cloud-setup-utils.c | 916 |
1 files changed, 916 insertions, 0 deletions
diff --git a/src/nm-cloud-setup/nm-cloud-setup-utils.c b/src/nm-cloud-setup/nm-cloud-setup-utils.c new file mode 100644 index 00000000..ed36b4dd --- /dev/null +++ b/src/nm-cloud-setup/nm-cloud-setup-utils.c @@ -0,0 +1,916 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-client-aux-extern/nm-default-client.h" + +#include "nm-cloud-setup-utils.h" + +#include <linux/if_ether.h> +#include <linux/if_infiniband.h> + +#include "libnm-glib-aux/nm-time-utils.h" +#include "libnm-glib-aux/nm-logging-base.h" +#include "libnm-glib-aux/nm-str-buf.h" + +/*****************************************************************************/ + +volatile NMLogLevel _nm_logging_configured_level = LOGL_TRACE; + +void +_nm_logging_enabled_init(const char *level_str) +{ + NMLogLevel level; + + if (!_nm_log_parse_level(level_str, &level)) + level = LOGL_WARN; + else if (level == _LOGL_KEEP) + level = LOGL_WARN; + + _nm_logging_configured_level = level; +} + +void +_nm_log_impl_cs(NMLogLevel level, const char *fmt, ...) +{ + gs_free char *msg = NULL; + va_list ap; + const char * level_str; + gint64 ts; + + va_start(ap, fmt); + msg = g_strdup_vprintf(fmt, ap); + va_end(ap); + + switch (level) { + case LOGL_TRACE: + level_str = "<trace>"; + break; + case LOGL_DEBUG: + level_str = "<debug>"; + break; + case LOGL_INFO: + level_str = "<info> "; + break; + case LOGL_WARN: + level_str = "<warn> "; + break; + default: + nm_assert(level == LOGL_ERR); + level_str = "<error>"; + break; + } + + ts = nm_utils_clock_gettime_nsec(CLOCK_BOOTTIME); + + g_print("[%" G_GINT64_FORMAT ".%05" G_GINT64_FORMAT "] %s %s\n", + ts / NM_UTILS_NSEC_PER_SEC, + (ts / (NM_UTILS_NSEC_PER_SEC / 10000)) % 10000, + level_str, + msg); +} + +void +_nm_utils_monotonic_timestamp_initialized(const struct timespec *tp, + gint64 offset_sec, + gboolean is_boottime) +{} + +/*****************************************************************************/ + +G_LOCK_DEFINE_STATIC(_wait_for_objects_lock); +static GSList *_wait_for_objects_list; +static GSList *_wait_for_objects_iterate_loops; + +static void +_wait_for_objects_maybe_quit_mainloops_with_lock(void) +{ + GSList *iter; + + if (!_wait_for_objects_list) { + for (iter = _wait_for_objects_iterate_loops; iter; iter = iter->next) + g_main_loop_quit(iter->data); + } +} + +static void +_wait_for_objects_weak_cb(gpointer data, GObject *where_the_object_was) +{ + G_LOCK(_wait_for_objects_lock); + nm_assert(g_slist_find(_wait_for_objects_list, where_the_object_was)); + _wait_for_objects_list = g_slist_remove(_wait_for_objects_list, where_the_object_was); + _wait_for_objects_maybe_quit_mainloops_with_lock(); + G_UNLOCK(_wait_for_objects_lock); +} + +/** + * nmcs_wait_for_objects_register: + * @target: a #GObject to wait for. + * + * Registers @target as a pointer to wait during shutdown. Using + * nmcs_wait_for_objects_iterate_until_done() we keep waiting until + * @target gets destroyed, which means that it gets completely unreferenced. + */ +gpointer +nmcs_wait_for_objects_register(gpointer target) +{ + g_return_val_if_fail(G_IS_OBJECT(target), NULL); + + G_LOCK(_wait_for_objects_lock); + _wait_for_objects_list = g_slist_prepend(_wait_for_objects_list, target); + G_UNLOCK(_wait_for_objects_lock); + + g_object_weak_ref(target, _wait_for_objects_weak_cb, NULL); + return target; +} + +typedef struct { + GMainLoop *loop; + gboolean got_timeout; +} WaitForObjectsData; + +static gboolean +_wait_for_objects_iterate_until_done_timeout_cb(gpointer user_data) +{ + WaitForObjectsData *data = user_data; + + data->got_timeout = TRUE; + g_main_loop_quit(data->loop); + return G_SOURCE_CONTINUE; +} + +static gboolean +_wait_for_objects_iterate_until_done_idle_cb(gpointer user_data) +{ + /* This avoids a race where: + * + * - we check whether there are objects to wait for. + * - the last object to wait for gets removed (issuing g_main_loop_quit()). + * - we run the mainloop (and missed our signal). + * + * It's really a missing feature of GMainLoop where the "is-running" flag is always set to + * TRUE by g_main_loop_run(). That means, you cannot catch a g_main_loop_quit() in a race + * free way while not iterating the loop. + * + * Avoid this, by checking once again after we start running the mainloop. + */ + + G_LOCK(_wait_for_objects_lock); + _wait_for_objects_maybe_quit_mainloops_with_lock(); + G_UNLOCK(_wait_for_objects_lock); + return G_SOURCE_REMOVE; +} + +/** + * nmcs_wait_for_objects_iterate_until_done: + * @context: the #GMainContext to iterate. + * @timeout_msec: timeout or -1 for no timeout. + * + * Iterates the provided @context until all objects that we wait for + * are destroyed. + * + * The purpose of this is to cleanup all objects that we have on exit. That + * is especially because objects have asynchronous operations pending that + * should be cancelled and properly completed during exit. + * + * Returns: %FALSE on timeout or %TRUE if all objects destroyed before timeout. + */ +gboolean +nmcs_wait_for_objects_iterate_until_done(GMainContext *context, int timeout_msec) +{ + nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(context, FALSE); + nm_auto_destroy_and_unref_gsource GSource *timeout_source = NULL; + WaitForObjectsData data; + gboolean has_more_objects; + + G_LOCK(_wait_for_objects_lock); + if (!_wait_for_objects_list) { + G_UNLOCK(_wait_for_objects_lock); + return TRUE; + } + _wait_for_objects_iterate_loops = g_slist_prepend(_wait_for_objects_iterate_loops, loop); + G_UNLOCK(_wait_for_objects_lock); + + data = (WaitForObjectsData){ + .loop = loop, + .got_timeout = FALSE, + }; + + if (timeout_msec >= 0) { + timeout_source = nm_g_source_attach( + nm_g_timeout_source_new(timeout_msec, + G_PRIORITY_DEFAULT, + _wait_for_objects_iterate_until_done_timeout_cb, + &data, + NULL), + context); + } + + has_more_objects = TRUE; + while (has_more_objects && !data.got_timeout) { + nm_auto_destroy_and_unref_gsource GSource *idle_source = NULL; + + idle_source = + nm_g_source_attach(nm_g_idle_source_new(G_PRIORITY_DEFAULT, + _wait_for_objects_iterate_until_done_idle_cb, + &data, + NULL), + context); + + g_main_loop_run(loop); + + G_LOCK(_wait_for_objects_lock); + has_more_objects = (!!_wait_for_objects_list); + if (data.got_timeout || !has_more_objects) + _wait_for_objects_iterate_loops = g_slist_remove(_wait_for_objects_iterate_loops, loop); + G_UNLOCK(_wait_for_objects_lock); + } + + return !data.got_timeout; +} + +/*****************************************************************************/ + +typedef struct { + GTask * task; + GSource * source_timeout; + GSource * source_next_poll; + GMainContext * context; + GCancellable * internal_cancellable; + NMCSUtilsPollProbeStartFcn probe_start_fcn; + NMCSUtilsPollProbeFinishFcn probe_finish_fcn; + gpointer probe_user_data; + gulong cancellable_id; + gint64 last_poll_start_ms; + int sleep_timeout_ms; + int ratelimit_timeout_ms; + bool completed : 1; +} PollTaskData; + +static void +_poll_task_data_free(gpointer data) +{ + PollTaskData *poll_task_data = data; + + nm_assert(G_IS_TASK(poll_task_data->task)); + nm_assert(!poll_task_data->source_next_poll); + nm_assert(!poll_task_data->source_timeout); + nm_assert(poll_task_data->cancellable_id == 0); + + g_main_context_unref(poll_task_data->context); + + nm_g_slice_free(poll_task_data); +} + +static void +_poll_return(PollTaskData *poll_task_data, GError *error_take) +{ + nm_clear_g_source_inst(&poll_task_data->source_next_poll); + nm_clear_g_source_inst(&poll_task_data->source_timeout); + nm_clear_g_cancellable_disconnect(g_task_get_cancellable(poll_task_data->task), + &poll_task_data->cancellable_id); + + nm_clear_g_cancellable(&poll_task_data->internal_cancellable); + + if (error_take) + g_task_return_error(poll_task_data->task, g_steal_pointer(&error_take)); + else + g_task_return_boolean(poll_task_data->task, TRUE); + + g_object_unref(poll_task_data->task); +} + +static gboolean _poll_start_cb(gpointer user_data); + +static void +_poll_done_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + PollTaskData * poll_task_data = user_data; + _nm_unused gs_unref_object GTask *task = + poll_task_data->task; /* balance ref from _poll_start_cb() */ + gs_free_error GError *error = NULL; + gint64 now_ms; + gint64 wait_ms; + gboolean is_finished; + + is_finished = + poll_task_data->probe_finish_fcn(source, result, poll_task_data->probe_user_data, &error); + + if (nm_utils_error_is_cancelled(error)) { + /* we already handle this differently. Nothing to do. */ + return; + } + + if (error || is_finished) { + _poll_return(poll_task_data, g_steal_pointer(&error)); + return; + } + + now_ms = nm_utils_get_monotonic_timestamp_msec(); + if (poll_task_data->ratelimit_timeout_ms > 0) + wait_ms = + (poll_task_data->last_poll_start_ms + poll_task_data->ratelimit_timeout_ms) - now_ms; + else + wait_ms = 0; + if (poll_task_data->sleep_timeout_ms > 0) + wait_ms = MAX(wait_ms, poll_task_data->sleep_timeout_ms); + + poll_task_data->source_next_poll = + nm_g_source_attach(nm_g_timeout_source_new(MAX(1, wait_ms), + G_PRIORITY_DEFAULT, + _poll_start_cb, + poll_task_data, + NULL), + poll_task_data->context); +} + +static gboolean +_poll_start_cb(gpointer user_data) +{ + PollTaskData *poll_task_data = user_data; + + nm_clear_g_source_inst(&poll_task_data->source_next_poll); + + poll_task_data->last_poll_start_ms = nm_utils_get_monotonic_timestamp_msec(); + + g_object_ref(poll_task_data->task); /* balanced by _poll_done_cb() */ + + poll_task_data->probe_start_fcn(poll_task_data->internal_cancellable, + poll_task_data->probe_user_data, + _poll_done_cb, + poll_task_data); + + return G_SOURCE_CONTINUE; +} + +static gboolean +_poll_timeout_cb(gpointer user_data) +{ + PollTaskData *poll_task_data = user_data; + + _poll_return(poll_task_data, nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN, "timeout expired")); + return G_SOURCE_CONTINUE; +} + +static void +_poll_cancelled_cb(GObject *object, gpointer user_data) +{ + PollTaskData *poll_task_data = user_data; + GError * error = NULL; + + nm_clear_g_signal_handler(g_task_get_cancellable(poll_task_data->task), + &poll_task_data->cancellable_id); + nm_utils_error_set_cancelled(&error, FALSE, NULL); + _poll_return(poll_task_data, error); +} + +/** + * nmcs_utils_poll: + * @poll_timeout_ms: if >= 0, then this is the overall timeout for how long we poll. + * When this timeout expires, the request completes with failure (and error set). + * @ratelimit_timeout_ms: if > 0, we ratelimit the starts from one prope_start_fcn + * call to the next. + * @sleep_timeout_ms: if > 0, then we wait after a probe finished this timeout + * before the next. Together with @ratelimit_timeout_ms this determines how + * frequently we probe. + * @probe_start_fcn: used to start a (asynchronous) probe. A probe must be completed + * by calling the provided callback. While a probe is in progress, we will not + * start another. This function is already invoked the first time synchronously, + * during nmcs_utils_poll(). + * @probe_finish_fcn: will be called from the callback of @probe_start_fcn. If the + * function returns %TRUE (polling done) or an error, polling stops. Otherwise, + * another poll will be started. + * @probe_user_data: user_data for the probe functions. + * @cancellable: cancellable for polling. + * @callback: when polling completes. + * @user_data: for @callback. + * + * This uses the current g_main_context_get_thread_default() for scheduling + * actions. + */ +void +nmcs_utils_poll(int poll_timeout_ms, + int ratelimit_timeout_ms, + int sleep_timeout_ms, + NMCSUtilsPollProbeStartFcn probe_start_fcn, + NMCSUtilsPollProbeFinishFcn probe_finish_fcn, + gpointer probe_user_data, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + PollTaskData *poll_task_data; + + poll_task_data = g_slice_new(PollTaskData); + *poll_task_data = (PollTaskData){ + .task = nm_g_task_new(NULL, cancellable, nmcs_utils_poll, callback, user_data), + .probe_start_fcn = probe_start_fcn, + .probe_finish_fcn = probe_finish_fcn, + .probe_user_data = probe_user_data, + .completed = FALSE, + .context = g_main_context_ref_thread_default(), + .sleep_timeout_ms = sleep_timeout_ms, + .ratelimit_timeout_ms = ratelimit_timeout_ms, + .internal_cancellable = g_cancellable_new(), + }; + + nmcs_wait_for_objects_register(poll_task_data->task); + + g_task_set_task_data(poll_task_data->task, poll_task_data, _poll_task_data_free); + + if (poll_timeout_ms >= 0) { + poll_task_data->source_timeout = + nm_g_source_attach(nm_g_timeout_source_new(poll_timeout_ms, + G_PRIORITY_DEFAULT, + _poll_timeout_cb, + poll_task_data, + NULL), + poll_task_data->context); + } + + poll_task_data->source_next_poll = nm_g_source_attach( + nm_g_idle_source_new(G_PRIORITY_DEFAULT, _poll_start_cb, poll_task_data, NULL), + poll_task_data->context); + + if (cancellable) { + gulong signal_id; + + signal_id = g_cancellable_connect(cancellable, + G_CALLBACK(_poll_cancelled_cb), + poll_task_data, + NULL); + if (signal_id == 0) { + /* the request is already cancelled. Return. */ + return; + } + poll_task_data->cancellable_id = signal_id; + } +} + +/** + * nmcs_utils_poll_finish: + * @result: the GAsyncResult from the GAsyncReadyCallback callback. + * @probe_user_data: the user data provided to nmcs_utils_poll(). + * @error: the failure code. + * + * Returns: %TRUE if the polling completed with success. In that case, + * the error won't be set. + * If the request was cancelled, this is indicated by @error and + * %FALSE will be returned. + * If the probe returned a failure, this returns %FALSE and the error + * provided by @probe_finish_fcn. + * If the request times out, this returns %FALSE with error set. + * Error is always set if (and only if) the function returns %FALSE. + */ +gboolean +nmcs_utils_poll_finish(GAsyncResult *result, gpointer *probe_user_data, GError **error) +{ + GTask * task; + PollTaskData *poll_task_data; + + g_return_val_if_fail(nm_g_task_is_valid(result, NULL, nmcs_utils_poll), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + task = G_TASK(result); + + if (probe_user_data) { + poll_task_data = g_task_get_task_data(task); + NM_SET_OUT(probe_user_data, poll_task_data->probe_user_data); + } + + return g_task_propagate_boolean(task, error); +} + +/*****************************************************************************/ + +char * +nmcs_utils_hwaddr_normalize(const char *hwaddr, gssize len) +{ + gs_free char *hwaddr_clone = NULL; + char * hw; + guint8 buf[ETH_ALEN]; + gsize l; + + nm_assert(len >= -1); + + if (len < 0) { + if (!hwaddr) + return NULL; + l = strlen(hwaddr); + } else { + l = len; + if (l > 0 && hwaddr[l - 1] == '\0') { + /* we accept one '\0' at the end of the string. */ + l--; + } + if (memchr(hwaddr, '\0', l)) { + /* but we don't accept other NUL characters in the middle. */ + return NULL; + } + } + + if (l == 0) + return NULL; + + nm_assert(hwaddr); + hw = nm_strndup_a(300, hwaddr, l, &hwaddr_clone); + + g_strstrip(hw); + + /* we cannot use _nm_utils_hwaddr_aton() because that requires a delimiter. + * Azure exposes MAC addresses without delimiter, so accept that too. */ + if (!nm_utils_hexstr2bin_full(hw, + FALSE, + FALSE, + FALSE, + ":-", + sizeof(buf), + buf, + sizeof(buf), + NULL)) + return NULL; + + return nm_utils_hwaddr_ntoa(buf, sizeof(buf)); +} + +/*****************************************************************************/ + +gboolean +nmcs_utils_ipaddr_normalize_bin(int addr_family, + const char *addr, + gssize len, + int * out_addr_family, + gpointer out_addr_bin) +{ + gs_free char *addr_clone = NULL; + char * ad; + gsize l; + + nm_assert(len >= -1); + + if (len < 0) { + if (!addr) + return FALSE; + l = strlen(addr); + } else { + l = len; + if (l > 0 && addr[l - 1] == '\0') { + /* we accept one '\0' at the end of the string. */ + l--; + } + if (memchr(addr, '\0', l)) { + /* but we don't accept other NUL characters in the middle. */ + return FALSE; + } + } + + if (l == 0) + return FALSE; + + nm_assert(addr); + ad = nm_strndup_a(300, addr, l, &addr_clone); + + g_strstrip(ad); + + return nm_utils_parse_inaddr_bin(addr_family, ad, out_addr_family, out_addr_bin); +} + +char * +nmcs_utils_ipaddr_normalize(int addr_family, const char *addr, gssize len) +{ + NMIPAddr ipaddr; + + if (!nmcs_utils_ipaddr_normalize_bin(addr_family, addr, len, &addr_family, &ipaddr)) + return NULL; + + return nm_utils_inet_ntop_dup(addr_family, &ipaddr); +} + +/*****************************************************************************/ + +const char * +nmcs_utils_parse_memmem(GBytes *mem, const char *needle) +{ + const char *mem_data; + gsize mem_size; + + g_return_val_if_fail(mem, NULL); + g_return_val_if_fail(needle, NULL); + + mem_data = g_bytes_get_data(mem, &mem_size); + return memmem(mem_data, mem_size, needle, strlen(needle)); +} + +const char * +nmcs_utils_parse_get_full_line(GBytes *mem, const char *needle) +{ + const char *mem_data; + gsize mem_size; + gsize c; + gsize l; + + const char *line; + + line = nmcs_utils_parse_memmem(mem, needle); + if (!line) + return NULL; + + mem_data = g_bytes_get_data(mem, &mem_size); + + if (line != mem_data && line[-1] != '\n') { + /* the line must be preceeded either by the begin of the data or + * by a newline. */ + return NULL; + } + + c = mem_size - (line - mem_data); + l = strlen(needle); + + if (c != l && line[l] != '\n') { + /* the end of the needle must be either a newline or the end of the buffer. */ + return NULL; + } + + return line; +} + +/*****************************************************************************/ + +char * +nmcs_utils_uri_build_concat_v(const char *base, const char **components, gsize n_components) +{ + NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE); + + nm_assert(base); + nm_assert(base[0]); + nm_assert(!NM_STR_HAS_SUFFIX(base, "/")); + + nm_str_buf_append(&strbuf, base); + + if (n_components > 0 && components[0] && components[0][0] == '/') { + /* the first component starts with a slash. We allow that, and don't add a duplicate + * slash. Otherwise, we add a separator after base. + * + * We only do that for the first component. */ + } else + nm_str_buf_append_c(&strbuf, '/'); + + while (n_components > 0) { + if (!components[0]) { + /* we allow NULL, to indicate nothing to append */ + } else + nm_str_buf_append(&strbuf, components[0]); + components++; + n_components--; + } + + return nm_str_buf_finalize(&strbuf, NULL); +} + +const char * +nmcs_utils_uri_complete_interned(const char *uri) +{ + gs_free char *s = NULL; + + if (nm_str_is_empty(uri)) + return NULL; + if (NM_STR_HAS_PREFIX(uri, "http://") || NM_STR_HAS_PREFIX(uri, "https://") || strchr(uri, '/')) + return g_intern_string(uri); + + s = g_strconcat("http://", uri, NULL); + return g_intern_string(s); +} + +/*****************************************************************************/ + +gboolean +nmcs_setting_ip_replace_ipv4_addresses(NMSettingIPConfig *s_ip, + NMIPAddress ** entries_arr, + guint entries_len) +{ + gboolean any_changes = FALSE; + guint i_next; + guint num; + guint i; + + num = nm_setting_ip_config_get_num_addresses(s_ip); + + i_next = 0; + + for (i = 0; i < entries_len; i++) { + NMIPAddress *entry = entries_arr[i]; + + if (!any_changes) { + if (i_next < num) { + if (nm_ip_address_cmp_full(entry, + nm_setting_ip_config_get_address(s_ip, i_next), + NM_IP_ADDRESS_CMP_FLAGS_WITH_ATTRS) + == 0) { + i_next++; + continue; + } + } + while (i_next < num) + nm_setting_ip_config_remove_address(s_ip, --num); + any_changes = TRUE; + } + + if (!nm_setting_ip_config_add_address(s_ip, entry)) + continue; + + i_next++; + } + if (!any_changes) { + while (i_next < num) { + nm_setting_ip_config_remove_address(s_ip, --num); + any_changes = TRUE; + } + } + + return any_changes; +} + +gboolean +nmcs_setting_ip_replace_ipv4_routes(NMSettingIPConfig *s_ip, + NMIPRoute ** entries_arr, + guint entries_len) +{ + gboolean any_changes = FALSE; + guint i_next; + guint num; + guint i; + + num = nm_setting_ip_config_get_num_routes(s_ip); + + i_next = 0; + + for (i = 0; i < entries_len; i++) { + NMIPRoute *entry = entries_arr[i]; + + if (!any_changes) { + if (i_next < num) { + if (nm_ip_route_equal_full(entry, + nm_setting_ip_config_get_route(s_ip, i_next), + NM_IP_ROUTE_EQUAL_CMP_FLAGS_WITH_ATTRS)) { + i_next++; + continue; + } + } + while (i_next < num) + nm_setting_ip_config_remove_route(s_ip, --num); + any_changes = TRUE; + } + + if (!nm_setting_ip_config_add_route(s_ip, entry)) + continue; + + i_next++; + } + if (!any_changes) { + while (i_next < num) { + nm_setting_ip_config_remove_route(s_ip, --num); + any_changes = TRUE; + } + } + + return any_changes; +} + +gboolean +nmcs_setting_ip_replace_ipv4_rules(NMSettingIPConfig *s_ip, + NMIPRoutingRule ** entries_arr, + guint entries_len) +{ + gboolean any_changes = FALSE; + guint i_next; + guint num; + guint i; + + num = nm_setting_ip_config_get_num_routing_rules(s_ip); + + i_next = 0; + + for (i = 0; i < entries_len; i++) { + NMIPRoutingRule *entry = entries_arr[i]; + + if (!any_changes) { + if (i_next < num) { + if (nm_ip_routing_rule_cmp(entry, + nm_setting_ip_config_get_routing_rule(s_ip, i_next)) + == 0) { + i_next++; + continue; + } + } + while (i_next < num) + nm_setting_ip_config_remove_routing_rule(s_ip, --num); + any_changes = TRUE; + } + + nm_setting_ip_config_add_routing_rule(s_ip, entry); + i_next++; + } + if (!any_changes) { + while (i_next < num) { + nm_setting_ip_config_remove_routing_rule(s_ip, --num); + any_changes = TRUE; + } + } + + return any_changes; +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop * main_loop; + NMConnection *connection; + GError * error; + guint64 version_id; +} DeviceGetAppliedConnectionData; + +static void +_nmcs_device_get_applied_connection_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + DeviceGetAppliedConnectionData *data = user_data; + + data->connection = nm_device_get_applied_connection_finish(NM_DEVICE(source), + result, + &data->version_id, + &data->error); + g_main_loop_quit(data->main_loop); +} + +NMConnection * +nmcs_device_get_applied_connection(NMDevice * device, + GCancellable *cancellable, + guint64 * version_id, + GError ** error) +{ + nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new(NULL, FALSE); + DeviceGetAppliedConnectionData data = { + .main_loop = main_loop, + }; + + nm_device_get_applied_connection_async(device, + 0, + cancellable, + _nmcs_device_get_applied_connection_cb, + &data); + + g_main_loop_run(main_loop); + + if (data.error) + g_propagate_error(error, data.error); + NM_SET_OUT(version_id, data.version_id); + return data.connection; +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop *main_loop; + GError * error; +} DeviceReapplyData; + +static void +_nmcs_device_reapply_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + DeviceReapplyData *data = user_data; + + nm_device_reapply_finish(NM_DEVICE(source), result, &data->error); + g_main_loop_quit(data->main_loop); +} + +gboolean +nmcs_device_reapply(NMDevice * device, + GCancellable *sigterm_cancellable, + NMConnection *connection, + guint64 version_id, + gboolean * out_version_id_changed, + GError ** error) +{ + nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new(NULL, FALSE); + DeviceReapplyData data = { + .main_loop = main_loop, + }; + + nm_device_reapply_async(device, + connection, + version_id, + 0, + sigterm_cancellable, + _nmcs_device_reapply_cb, + &data); + + g_main_loop_run(main_loop); + + if (data.error) { + NM_SET_OUT( + out_version_id_changed, + g_error_matches(data.error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_VERSION_ID_MISMATCH)); + g_propagate_error(error, data.error); + return FALSE; + } + + NM_SET_OUT(out_version_id_changed, FALSE); + return TRUE; +} |