// SPDX-License-Identifier: LGPL-2.1+
#include "nm-default.h"
#include "nm-cloud-setup-utils.h"
#include "nm-glib-aux/nm-time-utils.h"
#include "nm-glib-aux/nm-logging-base.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,
gboolean success,
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, success);
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, TRUE, 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, FALSE, 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;
_LOGD (">> poll cancelled");
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, FALSE, 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 (but no 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 (asynchrnous) 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 without error set.
*/
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;
guint8 buf[ETH_ALEN];
nm_assert (len >= -1);
if (len < 0) {
if (!hwaddr)
return NULL;
} else {
if (len == 0)
return NULL;
nm_assert (hwaddr);
hwaddr = nm_strndup_a (300, hwaddr, len, &hwaddr_clone);
}
if (!nm_utils_hwaddr_aton (hwaddr, buf, sizeof (buf)))
return NULL;
return nm_utils_hwaddr_ntoa (buf, sizeof (buf));
}
/*****************************************************************************/
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)
{
GString *uri;
nm_assert (base);
nm_assert (base[0]);
nm_assert (!NM_STR_HAS_SUFFIX (base, "/"));
uri = g_string_sized_new (100);
g_string_append (uri, 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
g_string_append_c (uri, '/');
while (n_components > 0) {
if (!components[0]) {
/* we allow NULL, to indicate nothing to append*/
} else
g_string_append (uri, components[0]);
components++;
n_components--;
}
return g_string_free (uri, FALSE);
}
/*****************************************************************************/
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;
}