diff options
| author | Michael Biebl <biebl@debian.org> | 2015-01-22 00:29:39 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2015-01-22 00:29:39 +0100 |
| commit | 2c032d8f1c6292c1338a615e6ec40252889ba85c (patch) | |
| tree | 1f77182220b2b0264288ba4a476ab47e5bc48716 /src/NetworkManagerUtils.c | |
| parent | 33491bc4279481db8ae47213e34a6d695a0e8830 (diff) | |
Imported Upstream version 1.0.0 upstream/1.0.0
Diffstat (limited to 'src/NetworkManagerUtils.c')
| -rw-r--r-- | src/NetworkManagerUtils.c | 1580 |
1 files changed, 1458 insertions, 122 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index 0aaa344f..2c291cdb 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -15,20 +15,30 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * Copyright (C) 2004 - 2012 Red Hat, Inc. - * Copyright (C) 2005 - 2008 Novell, Inc. + * Copyright 2004 - 2014 Red Hat, Inc. + * Copyright 2005 - 2008 Novell, Inc. */ +#include "config.h" + #include <glib.h> +#include <gio/gio.h> +#include <glib/gi18n.h> #include <errno.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <resolv.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <linux/if.h> +#include <linux/if_infiniband.h> #include "NetworkManagerUtils.h" +#include "nm-platform.h" #include "nm-utils.h" +#include "nm-core-internal.h" #include "nm-logging.h" #include "nm-device.h" #include "nm-setting-connection.h" @@ -36,40 +46,60 @@ #include "nm-setting-ip6-config.h" #include "nm-setting-wireless.h" #include "nm-setting-wireless-security.h" -#include "nm-manager-auth.h" +#include "nm-auth-utils.h" #include "nm-posix-signals.h" +#include "nm-dbus-glib-types.h" + +/* + * Some toolchains (E.G. uClibc 0.9.33 and earlier) don't export + * CLOCK_BOOTTIME even though the kernel supports it, so provide a + * local definition + */ +#ifndef CLOCK_BOOTTIME +#define CLOCK_BOOTTIME 7 +#endif /* - * nm_ethernet_address_is_valid + * nm_ethernet_address_is_valid: + * @addr: pointer to a binary or ASCII Ethernet address + * @len: length of @addr, or -1 if @addr is ASCII * * Compares an Ethernet address against known invalid addresses. - * + + * Returns: %TRUE if @addr is a valid Ethernet address, %FALSE if it is not. */ gboolean -nm_ethernet_address_is_valid (const struct ether_addr *test_addr) +nm_ethernet_address_is_valid (gconstpointer addr, gssize len) { guint8 invalid_addr1[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; guint8 invalid_addr2[ETH_ALEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; guint8 invalid_addr3[ETH_ALEN] = {0x44, 0x44, 0x44, 0x44, 0x44, 0x44}; guint8 invalid_addr4[ETH_ALEN] = {0x00, 0x30, 0xb4, 0x00, 0x00, 0x00}; /* prism54 dummy MAC */ + guchar first_octet; - g_return_val_if_fail (test_addr != NULL, FALSE); + g_return_val_if_fail (addr != NULL, FALSE); + g_return_val_if_fail (len == ETH_ALEN || len == -1, FALSE); /* Compare the AP address the card has with invalid ethernet MAC addresses. */ - if (!memcmp (test_addr->ether_addr_octet, &invalid_addr1, ETH_ALEN)) + if (nm_utils_hwaddr_matches (addr, len, invalid_addr1, ETH_ALEN)) return FALSE; - if (!memcmp (test_addr->ether_addr_octet, &invalid_addr2, ETH_ALEN)) + if (nm_utils_hwaddr_matches (addr, len, invalid_addr2, ETH_ALEN)) return FALSE; - if (!memcmp (test_addr->ether_addr_octet, &invalid_addr3, ETH_ALEN)) + if (nm_utils_hwaddr_matches (addr, len, invalid_addr3, ETH_ALEN)) return FALSE; - if (!memcmp (test_addr->ether_addr_octet, &invalid_addr4, ETH_ALEN)) + if (nm_utils_hwaddr_matches (addr, len, invalid_addr4, ETH_ALEN)) return FALSE; - if (test_addr->ether_addr_octet[0] & 1) /* Multicast addresses */ - return FALSE; + /* Check for multicast address */ + if (len == -1) + first_octet = strtoul (addr, NULL, 16); + else + first_octet = ((guint8 *)addr)[0]; + if (first_octet & 0x01) + return FALSE; return TRUE; } @@ -92,15 +122,15 @@ nm_utils_ip4_address_clear_host_address (in_addr_t addr, guint8 plen) * @src: source ip6 address * @plen: prefix length of network * - * Note: this function is self assignment save, to update @src inplace, set both + * Note: this function is self assignment safe, to update @src inplace, set both * @dst and @src to the same destination. */ -void +const struct in6_addr * nm_utils_ip6_address_clear_host_address (struct in6_addr *dst, const struct in6_addr *src, guint8 plen) { - g_return_if_fail (plen <= 128); - g_return_if_fail (src); - g_return_if_fail (dst); + g_return_val_if_fail (plen <= 128, NULL); + g_return_val_if_fail (src, NULL); + g_return_val_if_fail (dst, NULL); if (plen < 128) { guint nbytes = plen / 8; @@ -116,6 +146,8 @@ nm_utils_ip6_address_clear_host_address (struct in6_addr *dst, const struct in6_ memset (&dst->s6_addr[nbytes], 0, 16 - nbytes); } else if (src != dst) *dst = *src; + + return dst; } @@ -144,6 +176,657 @@ nm_spawn_process (const char *args) return status; } +/** + * nm_utils_get_start_time_for_pid: + * @pid: the process identifier + * + * Originally copied from polkit source (src/polkit/polkitunixprocess.c) + * and adjusted. + * + * Returns: the timestamp when the process started (by parsing /proc/$PID/stat). + * If an error occurs (e.g. the process does not exist), 0 is returned. + * + * The returned start time counts since boot, in the unit HZ (with HZ usually being (1/100) seconds) + **/ +guint64 +nm_utils_get_start_time_for_pid (pid_t pid) +{ + guint64 start_time; + gchar *filename; + gchar *contents; + size_t length; + gchar **tokens; + guint num_tokens; + gchar *p; + gchar *endp; + + start_time = 0; + contents = NULL; + + g_return_val_if_fail (pid > 0, 0); + + filename = g_strdup_printf ("/proc/%"G_GUINT64_FORMAT"/stat", (guint64) pid); + + if (!g_file_get_contents (filename, &contents, &length, NULL)) + goto out; + + /* start time is the token at index 19 after the '(process name)' entry - since only this + * field can contain the ')' character, search backwards for this to avoid malicious + * processes trying to fool us + */ + p = strrchr (contents, ')'); + if (p == NULL) + goto out; + p += 2; /* skip ') ' */ + if (p - contents >= (int) length) + goto out; + + tokens = g_strsplit (p, " ", 0); + + num_tokens = g_strv_length (tokens); + + if (num_tokens < 20) + goto out; + + start_time = strtoull (tokens[19], &endp, 10); + if (endp == tokens[19]) + goto out; + + g_strfreev (tokens); + + out: + g_free (filename); + g_free (contents); + + return start_time; +} + +/******************************************************************************************/ + +typedef struct { + pid_t pid; + guint64 log_domain; + union { + struct { + gint64 wait_start_us; + guint source_timeout_kill_id; + } async; + struct { + gboolean success; + int child_status; + } sync; + }; + NMUtilsKillChildAsyncCb callback; + void *user_data; + + char log_name[1]; /* variable-length object, must be last element!! */ +} KillChildAsyncData; + +#define LOG_NAME_FMT "kill child process '%s' (%ld)" +#define LOG_NAME_PROCESS_FMT "kill process '%s' (%ld)" +#define LOG_NAME_ARGS log_name,(long)pid + +static KillChildAsyncData * +_kc_async_data_alloc (pid_t pid, guint64 log_domain, const char *log_name, NMUtilsKillChildAsyncCb callback, void *user_data) +{ + KillChildAsyncData *data; + size_t log_name_len; + + /* append the name at the end of our KillChildAsyncData. */ + log_name_len = strlen (LOG_NAME_FMT) + 20 + strlen (log_name); + data = g_malloc (sizeof (KillChildAsyncData) - 1 + log_name_len); + g_snprintf (data->log_name, log_name_len, LOG_NAME_FMT, LOG_NAME_ARGS); + + data->pid = pid; + data->user_data = user_data; + data->callback = callback; + data->log_domain = log_domain; + + return data; +} + +#define KC_EXIT_TO_STRING_BUF_SIZE 128 +static const char * +_kc_exit_to_string (char *buf, int exit) +#define _kc_exit_to_string(buf, exit) ( G_STATIC_ASSERT_EXPR(sizeof (buf) == KC_EXIT_TO_STRING_BUF_SIZE && sizeof ((buf)[0]) == 1), _kc_exit_to_string (buf, exit) ) +{ + if (WIFEXITED (exit)) + g_snprintf (buf, KC_EXIT_TO_STRING_BUF_SIZE, "normally with status %d", WEXITSTATUS (exit)); + else if (WIFSIGNALED (exit)) + g_snprintf (buf, KC_EXIT_TO_STRING_BUF_SIZE, "by signal %d", WTERMSIG (exit)); + else + g_snprintf (buf, KC_EXIT_TO_STRING_BUF_SIZE, "with unexpected status %d", exit); + return buf; +} + +static const char * +_kc_signal_to_string (int sig) +{ + switch (sig) { + case 0: return "no signal (0)"; + case SIGKILL: return "SIGKILL (" G_STRINGIFY (SIGKILL) ")"; + case SIGTERM: return "SIGTERM (" G_STRINGIFY (SIGTERM) ")"; + default: + return "Unexpected signal"; + } +} + +#define KC_WAITED_TO_STRING 100 +static const char * +_kc_waited_to_string (char *buf, gint64 wait_start_us) +#define _kc_waited_to_string(buf, wait_start_us) ( G_STATIC_ASSERT_EXPR(sizeof (buf) == KC_WAITED_TO_STRING && sizeof ((buf)[0]) == 1), _kc_waited_to_string (buf, wait_start_us) ) +{ + g_snprintf (buf, KC_WAITED_TO_STRING, " (%ld usec elapsed)", (long) (nm_utils_get_monotonic_timestamp_us () - wait_start_us)); + return buf; +} + +static void +_kc_cb_watch_child (GPid pid, gint status, gpointer user_data) +{ + KillChildAsyncData *data = user_data; + char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE], buf_wait[KC_WAITED_TO_STRING]; + + if (data->async.source_timeout_kill_id) + g_source_remove (data->async.source_timeout_kill_id); + + nm_log_dbg (data->log_domain, "%s: terminated %s%s", + data->log_name, _kc_exit_to_string (buf_exit, status), + _kc_waited_to_string (buf_wait, data->async.wait_start_us)); + + if (data->callback) + data->callback (pid, TRUE, status, data->user_data); + + g_free (data); +} + +static gboolean +_kc_cb_timeout_grace_period (void *user_data) +{ + KillChildAsyncData *data = user_data; + int ret, errsv; + + data->async.source_timeout_kill_id = 0; + + if ((ret = kill (data->pid, SIGKILL)) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv != ESRCH) { + nm_log_err (LOGD_CORE | data->log_domain, "%s: kill(SIGKILL) returned unexpected return value %d: (%s, %d)", + data->log_name, ret, strerror (errsv), errsv); + } + } else { + nm_log_dbg (data->log_domain, "%s: process not terminated after %ld usec. Sending SIGKILL signal", + data->log_name, (long) (nm_utils_get_monotonic_timestamp_us () - data->async.wait_start_us)); + } + + return G_SOURCE_REMOVE; +} + +static gboolean +_kc_invoke_callback_idle (gpointer user_data) +{ + KillChildAsyncData *data = user_data; + + if (data->sync.success) { + char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE]; + + nm_log_dbg (data->log_domain, "%s: invoke callback: terminated %s", + data->log_name, _kc_exit_to_string (buf_exit, data->sync.child_status)); + } else + nm_log_dbg (data->log_domain, "%s: invoke callback: killing child failed", data->log_name); + + data->callback (data->pid, data->sync.success, data->sync.child_status, data->user_data); + g_free (data); + + return G_SOURCE_REMOVE; +} + +static void +_kc_invoke_callback (pid_t pid, guint64 log_domain, const char *log_name, NMUtilsKillChildAsyncCb callback, void *user_data, gboolean success, int child_status) +{ + KillChildAsyncData *data; + + if (!callback) + return; + + data = _kc_async_data_alloc (pid, log_domain, log_name, callback, user_data); + data->sync.success = success; + data->sync.child_status = child_status; + + g_idle_add (_kc_invoke_callback_idle, data); +} + +/* nm_utils_kill_child_async: + * @pid: the process id of the process to kill + * @sig: signal to send initially. Set to 0 to send not signal. + * @log_domain: the logging domain used for logging (LOGD_NONE to suppress logging) + * @log_name: for logging, the name of the processes to kill + * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value + * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter is ignored. + * @callback: (allow-none): callback after the child terminated. This function will always + * be invoked asynchronously. + * @user_data: passed on to callback + * + * Uses g_child_watch_add(), so note the glib comment: if you obtain pid from g_spawn_async() or + * g_spawn_async_with_pipes() you will need to pass %G_SPAWN_DO_NOT_REAP_CHILD as flag to the spawn + * function for the child watching to work. + * Also note, that you must g_source_remove() any other child watchers for @pid because glib + * supports only one watcher per child. + **/ +void +nm_utils_kill_child_async (pid_t pid, int sig, guint64 log_domain, + const char *log_name, guint32 wait_before_kill_msec, + NMUtilsKillChildAsyncCb callback, void *user_data) +{ + int status = 0, errsv; + pid_t ret; + KillChildAsyncData *data; + char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE]; + + g_return_if_fail (pid > 0); + g_return_if_fail (log_name != NULL); + + /* let's see if the child already terminated... */ + ret = waitpid (pid, &status, WNOHANG); + if (ret > 0) { + nm_log_dbg (log_domain, LOG_NAME_FMT ": process %ld already terminated %s", + LOG_NAME_ARGS, (long) ret, _kc_exit_to_string (buf_exit, status)); + _kc_invoke_callback (pid, log_domain, log_name, callback, user_data, TRUE, status); + return; + } else if (ret != 0) { + errsv = errno; + /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */ + if (errsv != ECHILD) { + nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)", + LOG_NAME_ARGS, strerror (errsv), errsv); + _kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1); + return; + } + } + + /* send the first signal. */ + if (kill (pid, sig) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv != ESRCH) { + nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error sending %s: %s (%d)", + LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv); + _kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1); + return; + } + + /* let's try again with waitpid, probably there was a race... */ + ret = waitpid (pid, &status, 0); + if (ret > 0) { + nm_log_dbg (log_domain, LOG_NAME_FMT ": process %ld already terminated %s", + LOG_NAME_ARGS, (long) ret, _kc_exit_to_string (buf_exit, status)); + _kc_invoke_callback (pid, log_domain, log_name, callback, user_data, TRUE, status); + } else { + errsv = errno; + nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed due to unexpected return value %ld by waitpid (%s, %d) after sending %s", + LOG_NAME_ARGS, (long) ret, strerror (errsv), errsv, _kc_signal_to_string (sig)); + _kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1); + } + return; + } + + data = _kc_async_data_alloc (pid, log_domain, log_name, callback, user_data); + data->async.wait_start_us = nm_utils_get_monotonic_timestamp_us (); + + if (sig != SIGKILL && wait_before_kill_msec > 0) { + data->async.source_timeout_kill_id = g_timeout_add (wait_before_kill_msec, _kc_cb_timeout_grace_period, data); + nm_log_dbg (log_domain, "%s: wait for process to terminate after sending %s (send SIGKILL in %ld milliseconds)...", + data->log_name, _kc_signal_to_string (sig), (long) wait_before_kill_msec); + } else { + data->async.source_timeout_kill_id = 0; + nm_log_dbg (log_domain, "%s: wait for process to terminate after sending %s...", + data->log_name, _kc_signal_to_string (sig)); + } + + g_child_watch_add (pid, _kc_cb_watch_child, data); +} + +static inline gulong +_sleep_duration_convert_ms_to_us (guint32 sleep_duration_msec) +{ + if (sleep_duration_msec > 0) { + guint64 x = (gint64) sleep_duration_msec * (guint64) 1000L; + + return x < G_MAXULONG ? (gulong) x : G_MAXULONG; + } + return G_USEC_PER_SEC / 20; +} + +/* nm_utils_kill_child_sync: + * @pid: process id to kill + * @sig: signal to sent initially. If 0, no signal is sent. If %SIGKILL, the + * second %SIGKILL signal is not sent after @wait_before_kill_msec milliseconds. + * @log_domain: log debug information for this domain. Errors and warnings are logged both + * as %LOGD_CORE and @log_domain. + * @log_name: name of the process to kill for logging. + * @child_status: (out) (allow-none): return the exit status of the child, if no error occured. + * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value + * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter has not effect. + * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate. + * Set to zero, to use the default (meaning 20 wakeups per seconds). + * + * Kill a child process synchronously and wait. The function first checks if the child already terminated + * and if it did, return the exit status. Otherwise send one @sig signal. @sig will always be + * sent unless the child already exited. If the child does not exit within @wait_before_kill_msec milliseconds, + * the function will send %SIGKILL and waits for the child indefinitly. If @wait_before_kill_msec is zero, no + * %SIGKILL signal will be sent. + **/ +gboolean +nm_utils_kill_child_sync (pid_t pid, int sig, guint64 log_domain, const char *log_name, + int *child_status, guint32 wait_before_kill_msec, + guint32 sleep_duration_msec) +{ + int status = 0, errsv; + pid_t ret; + gboolean success = FALSE; + gboolean was_waiting = FALSE, send_kill = FALSE; + char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE]; + char buf_wait[KC_WAITED_TO_STRING]; + gint64 wait_start_us; + + g_return_val_if_fail (pid > 0, FALSE); + g_return_val_if_fail (log_name != NULL, FALSE); + + /* check if the child process already terminated... */ + ret = waitpid (pid, &status, WNOHANG); + if (ret > 0) { + nm_log_dbg (log_domain, LOG_NAME_FMT ": process %ld already terminated %s", + LOG_NAME_ARGS, (long) ret, _kc_exit_to_string (buf_exit, status)); + success = TRUE; + goto out; + } else if (ret != 0) { + errsv = errno; + /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */ + if (errsv != ECHILD) { + nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)", + LOG_NAME_ARGS, strerror (errsv), errsv); + goto out; + } + } + + /* send first signal @sig */ + if (kill (pid, sig) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv != ESRCH) { + nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed to send %s: %s (%d)", + LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv); + } else { + /* let's try again with waitpid, probably there was a race... */ + ret = waitpid (pid, &status, 0); + if (ret > 0) { + nm_log_dbg (log_domain, LOG_NAME_FMT ": process %ld already terminated %s", + LOG_NAME_ARGS, (long) ret, _kc_exit_to_string (buf_exit, status)); + success = TRUE; + } else { + errsv = errno; + nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed due to unexpected return value %ld by waitpid (%s, %d) after sending %s", + LOG_NAME_ARGS, (long) ret, strerror (errsv), errsv, _kc_signal_to_string (sig)); + } + } + goto out; + } + + wait_start_us = nm_utils_get_monotonic_timestamp_us (); + + /* wait for the process to terminated... */ + if (sig != SIGKILL) { + gint64 wait_until, now; + gulong sleep_time, sleep_duration_usec; + int loop_count = 0; + + sleep_duration_usec = _sleep_duration_convert_ms_to_us (sleep_duration_msec); + wait_until = wait_before_kill_msec <= 0 ? 0 : wait_start_us + (((gint64) wait_before_kill_msec) * 1000L); + + while (TRUE) { + ret = waitpid (pid, &status, WNOHANG); + if (ret > 0) { + nm_log_dbg (log_domain, LOG_NAME_FMT ": after sending %s, process %ld exited %s%s", + LOG_NAME_ARGS, _kc_signal_to_string (sig), (long) ret, _kc_exit_to_string (buf_exit, status), + was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : ""); + success = TRUE; + goto out; + } + if (ret == -1) { + errsv = errno; + /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */ + if (errsv != ECHILD) { + nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": after sending %s, waitpid failed with %s (%d)%s", + LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv, + was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : ""); + goto out; + } + } + + if (!wait_until) + break; + + now = nm_utils_get_monotonic_timestamp_us (); + if (now >= wait_until) + break; + + if (!was_waiting) { + nm_log_dbg (log_domain, LOG_NAME_FMT ": waiting up to %ld milliseconds for process to terminate normally after sending %s...", + LOG_NAME_ARGS, (long) MAX (wait_before_kill_msec, 0), _kc_signal_to_string (sig)); + was_waiting = TRUE; + } + + sleep_time = MIN (wait_until - now, sleep_duration_usec); + if (loop_count < 20) { + /* At the beginning we expect the process to die fast. + * Limit the sleep time, the limit doubles with every iteration. */ + sleep_time = MIN (sleep_time, (((guint64) 1) << loop_count) * G_USEC_PER_SEC / 2000); + loop_count++; + } + g_usleep (sleep_time); + } + + /* send SIGKILL, if called with @wait_before_kill_msec > 0 */ + if (wait_until) { + nm_log_dbg (log_domain, LOG_NAME_FMT ": sending SIGKILL...", LOG_NAME_ARGS); + + send_kill = TRUE; + if (kill (pid, SIGKILL) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv != ESRCH) { + nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed to send SIGKILL (after sending %s), %s (%d)", + LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv); + goto out; + } + } + } + } + + if (!was_waiting) { + nm_log_dbg (log_domain, LOG_NAME_FMT ": waiting for process to terminate after sending %s%s...", + LOG_NAME_ARGS, _kc_signal_to_string (sig), send_kill ? " and SIGKILL" : ""); + } + + /* block until the child terminates. */ + while ((ret = waitpid (pid, &status, 0)) <= 0) { + errsv = errno; + + if (errsv != EINTR) { + nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": after sending %s%s, waitpid failed with %s (%d)%s", + LOG_NAME_ARGS, _kc_signal_to_string (sig), send_kill ? " and SIGKILL" : "", strerror (errsv), errsv, + _kc_waited_to_string (buf_wait, wait_start_us)); + goto out; + } + } + + nm_log_dbg (log_domain, LOG_NAME_FMT ": after sending %s%s, process %ld exited %s%s", + LOG_NAME_ARGS, _kc_signal_to_string (sig), send_kill ? " and SIGKILL" : "", (long) ret, + _kc_exit_to_string (buf_exit, status), _kc_waited_to_string (buf_wait, wait_start_us)); + success = TRUE; +out: + if (child_status) + *child_status = success ? status : -1; + return success; +} + +/* nm_utils_kill_process_sync: + * @pid: process id to kill + * @start_time: the start time of the process to kill (as obtained by nm_utils_get_start_time_for_pid()). + * This is an optional argument, to avoid (somewhat) killing the wrong process as @pid + * might get recycled. You can pass 0, to not provide this parameter. + * @sig: signal to sent initially. If 0, no signal is sent. If %SIGKILL, the + * second %SIGKILL signal is not sent after @wait_before_kill_msec milliseconds. + * @log_domain: log debug information for this domain. Errors and warnings are logged both + * as %LOGD_CORE and @log_domain. + * @log_name: name of the process to kill for logging. + * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value + * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter has no effect. + * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate. + * Set to zero, to use the default (meaning 20 wakeups per seconds). + * + * Kill a non-child process synchronously and wait. This function will not return before the + * process with PID @pid is gone. + **/ +void +nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, guint64 log_domain, + const char *log_name, guint32 wait_before_kill_msec, + guint32 sleep_duration_msec) +{ + int errsv; + guint64 start_time0; + gint64 wait_until, now, wait_start_us; + gulong sleep_time, sleep_duration_usec; + int loop_count = 0; + gboolean was_waiting = FALSE; + char buf_wait[KC_WAITED_TO_STRING]; + + g_return_if_fail (pid > 0); + g_return_if_fail (log_name != NULL); + g_return_if_fail (wait_before_kill_msec > 0); + + start_time0 = nm_utils_get_start_time_for_pid (pid); + if (start_time0 == 0) { + nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": cannot kill process %ld because it seems already gone", + LOG_NAME_ARGS, (long int) pid); + return; + } + if (start_time != 0 && start_time != start_time0) { + nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": don't kill process %ld because the start_time is unexpectedly %lu instead of %ld", + LOG_NAME_ARGS, (long int) pid, (long unsigned) start_time0, (long unsigned) start_time); + return; + } + + if (kill (pid, sig) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv == ESRCH) { + nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": failed to send %s because process seems gone", + LOG_NAME_ARGS, _kc_signal_to_string (sig)); + } else { + nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to send %s: %s (%d)", + LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv); + } + return; + } + + /* wait for the process to terminated... */ + + wait_start_us = nm_utils_get_monotonic_timestamp_us (); + + sleep_duration_usec = _sleep_duration_convert_ms_to_us (sleep_duration_msec); + wait_until = wait_start_us + (((gint64) wait_before_kill_msec) * 1000L); + + while (TRUE) { + start_time = nm_utils_get_start_time_for_pid (pid); + + if (start_time != start_time0) { + nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": process is gone after sending signal %s%s", + LOG_NAME_ARGS, _kc_signal_to_string (sig), + was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : ""); + return; + } + + if (kill (pid, 0) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv == ESRCH) { + nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": process is gone or a zombie after sending signal %s%s", + LOG_NAME_ARGS, _kc_signal_to_string (sig), + was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : ""); + } else { + nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to kill(%ld, 0): %s (%d)%s", + LOG_NAME_ARGS, (long int) pid, strerror (errsv), errsv, + was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : ""); + } + return; + } + + sleep_time = sleep_duration_usec; + if (wait_until != 0) { + now = nm_utils_get_monotonic_timestamp_us (); + if (sig != SIGKILL && now >= wait_until) { + /* Still not dead. SIGKILL now... */ + nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": sending SIGKILL", LOG_NAME_ARGS); + if (kill (pid, SIGKILL) != 0) { + errsv = errno; + /* ESRCH means, process does not exist or is already a zombie. */ + if (errsv != ESRCH) { + nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": process is gone or a zombie%s", + LOG_NAME_ARGS, _kc_waited_to_string (buf_wait, wait_start_us)); + } else { + nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to send SIGKILL (after sending %s), %s (%d)%s", + LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv, + _kc_waited_to_string (buf_wait, wait_start_us)); + } + return; + } + sig = SIGKILL; + was_waiting = TRUE; + wait_until = 0; + loop_count = 0; /* reset the loop_count. Now we really expect the process to die quickly. */ + } else { + if (!was_waiting) { + nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": waiting up to %ld milliseconds for process to disappear after sending %s...", + LOG_NAME_ARGS, (long) wait_before_kill_msec, _kc_signal_to_string (sig)); + was_waiting = TRUE; + } + sleep_time = MIN (wait_until - now, sleep_duration_usec); + } + } + + if (loop_count < 20) { + /* At the beginning we expect the process to die fast. + * Limit the sleep time, the limit doubles with every iteration. */ + sleep_time = MIN (sleep_time, (((guint64) 1) << loop_count) * G_USEC_PER_SEC / 2000); + loop_count++; + } + g_usleep (sleep_time); + } +} +#undef LOG_NAME_FMT +#undef LOG_NAME_PROCESS_FMT +#undef LOG_NAME_ARGS + +const char *const NM_PATHS_DEFAULT[] = { + PREFIX "/sbin/", + PREFIX "/bin/", + "/sbin/", + "/usr/sbin/", + "/usr/local/sbin/", + "/usr/bin/", + "/usr/local/bin/", + NULL, +}; + +const char * +nm_utils_find_helper(const char *progname, const char *try_first, GError **error) +{ + return nm_utils_file_search_in_paths (progname, try_first, NM_PATHS_DEFAULT, G_FILE_TEST_IS_EXECUTABLE, NULL, NULL, error); +} + +/******************************************************************************************/ + gboolean nm_match_spec_string (const GSList *specs, const char *match) { @@ -160,15 +843,22 @@ nm_match_spec_string (const GSList *specs, const char *match) gboolean nm_match_spec_hwaddr (const GSList *specs, const char *hwaddr) { - char *hwaddr_match; - gboolean matched; + const GSList *iter; g_return_val_if_fail (hwaddr != NULL, FALSE); - hwaddr_match = g_strdup_printf ("mac:%s", hwaddr); - matched = nm_match_spec_string (specs, hwaddr_match); - g_free (hwaddr_match); - return matched; + for (iter = specs; iter; iter = g_slist_next (iter)) { + const char *spec_str = iter->data; + + if ( !g_ascii_strncasecmp (spec_str, "mac:", 4) + && nm_utils_hwaddr_matches (spec_str + 4, -1, hwaddr, -1)) + return TRUE; + + if (nm_utils_hwaddr_matches (spec_str, -1, hwaddr, -1)) + return TRUE; + } + + return FALSE; } gboolean @@ -179,6 +869,9 @@ nm_match_spec_interface_name (const GSList *specs, const char *interface_name) g_return_val_if_fail (interface_name != NULL, FALSE); + if (nm_match_spec_string (specs, interface_name)) + return TRUE; + iface_match = g_strdup_printf ("interface-name:%s", interface_name); matched = nm_match_spec_string (specs, iface_match); g_free (iface_match); @@ -402,8 +1095,8 @@ value_hash_add_object_property (GHashTable *hash, static char * get_new_connection_name (const GSList *existing, - const char *format, - const char *preferred) + const char *preferred, + const char *fallback_prefix) { GSList *names = NULL; const GSList *iter; @@ -411,6 +1104,8 @@ get_new_connection_name (const GSList *existing, int i = 0; gboolean preferred_found = FALSE; + g_assert (fallback_prefix); + for (iter = existing; iter; iter = g_slist_next (iter)) { NMConnection *candidate = NM_CONNECTION (iter->data); const char *id; @@ -436,7 +1131,12 @@ get_new_connection_name (const GSList *existing, char *temp; gboolean found = FALSE; - temp = g_strdup_printf (format, i); + /* Translators: the first %s is a prefix for the connection id, such + * as "Wired Connection" or "VPN Connection". The %d is a number + * that is combined with the first argument to create a unique + * connection id. */ + temp = g_strdup_printf (C_("connection id fallback", "%s %d"), + fallback_prefix, i); for (iter = names; iter; iter = g_slist_next (iter)) { if (!strcmp (iter->data, temp)) { found = TRUE; @@ -453,68 +1153,38 @@ get_new_connection_name (const GSList *existing, return cname; } -void -nm_utils_normalize_connection (NMConnection *connection, - gboolean default_enable_ipv6) -{ - NMSettingConnection *s_con = nm_connection_get_setting_connection (connection); - const char *default_ip4_method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; - const char *default_ip6_method = - default_enable_ipv6 ? NM_SETTING_IP6_CONFIG_METHOD_AUTO : NM_SETTING_IP6_CONFIG_METHOD_IGNORE; - NMSettingIP4Config *s_ip4; - NMSettingIP6Config *s_ip6; - NMSetting *setting; - const char *method; +static char * +get_new_connection_ifname (const GSList *existing, + const char *prefix) +{ + int i; + char *name; + const GSList *iter; + gboolean found; - s_ip4 = nm_connection_get_setting_ip4_config (connection); - s_ip6 = nm_connection_get_setting_ip6_config (connection); + for (i = 0; i < 500; i++) { + name = g_strdup_printf ("%s%d", prefix, i); - if (nm_setting_connection_get_master (s_con)) { - /* Slave connections don't have IP configuration. */ + if (nm_platform_link_exists (name)) + goto next; - if (s_ip4) { - method = nm_setting_ip4_config_get_method (s_ip4); - if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) != 0) { - nm_log_warn (LOGD_SETTINGS, "ignoring IP4 config on slave '%s'", - nm_connection_get_id (connection)); - } - nm_connection_remove_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); - s_ip4 = NULL; - } + for (iter = existing, found = FALSE; iter; iter = g_slist_next (iter)) { + NMConnection *candidate = iter->data; - if (s_ip6) { - method = nm_setting_ip6_config_get_method (s_ip6); - if (g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) != 0) { - nm_log_warn (LOGD_SETTINGS, "ignoring IP6 config on slave '%s'", - nm_connection_get_id (connection)); + if (g_strcmp0 (nm_connection_get_interface_name (candidate), name) == 0) { + found = TRUE; + break; } - nm_connection_remove_setting (connection, NM_TYPE_SETTING_IP6_CONFIG); - s_ip6 = NULL; } - } else { - /* Ensure all non-slave connections have IP4 and IP6 settings objects. If no - * IP6 setting was specified, then assume that means IP6 config is allowed - * to fail. But if no IP4 setting was specified, assume the caller was just - * being lazy. - */ - if (!s_ip4) { - setting = nm_setting_ip4_config_new (); - nm_connection_add_setting (connection, setting); - g_object_set (setting, - NM_SETTING_IP4_CONFIG_METHOD, default_ip4_method, - NULL); - } - if (!s_ip6) { - setting = nm_setting_ip6_config_new (); - nm_connection_add_setting (connection, setting); - - g_object_set (setting, - NM_SETTING_IP6_CONFIG_METHOD, default_ip6_method, - NM_SETTING_IP6_CONFIG_MAY_FAIL, TRUE, - NULL); - } + if (!found) + return name; + + next: + g_free (name); } + + return NULL; } const char * @@ -522,8 +1192,7 @@ nm_utils_get_ip_config_method (NMConnection *connection, GType ip_setting_type) { NMSettingConnection *s_con; - NMSettingIP4Config *s_ip4; - NMSettingIP6Config *s_ip6; + NMSettingIPConfig *s_ip4, *s_ip6; const char *method; s_con = nm_connection_get_setting_connection (connection); @@ -536,7 +1205,7 @@ nm_utils_get_ip_config_method (NMConnection *connection, else { s_ip4 = nm_connection_get_setting_ip4_config (connection); g_return_val_if_fail (s_ip4 != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO); - method = nm_setting_ip4_config_get_method (s_ip4); + method = nm_setting_ip_config_get_method (s_ip4); g_return_val_if_fail (method != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO); return method; @@ -550,7 +1219,7 @@ nm_utils_get_ip_config_method (NMConnection *connection, else { s_ip6 = nm_connection_get_setting_ip6_config (connection); g_return_val_if_fail (s_ip6 != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO); - method = nm_setting_ip6_config_get_method (s_ip6); + method = nm_setting_ip_config_get_method (s_ip6); g_return_val_if_fail (method != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO); return method; @@ -564,12 +1233,16 @@ void nm_utils_complete_generic (NMConnection *connection, const char *ctype, const GSList *existing, - const char *format, - const char *preferred, + const char *preferred_id, + const char *fallback_id_prefix, + const char *ifname_prefix, gboolean default_enable_ipv6) { NMSettingConnection *s_con; - char *id, *uuid; + char *id, *uuid, *ifname; + GHashTable *parameters; + + g_assert (fallback_id_prefix); s_con = nm_connection_get_setting_connection (connection); if (!s_con) { @@ -586,13 +1259,24 @@ nm_utils_complete_generic (NMConnection *connection, /* Add a connection ID if absent */ if (!nm_setting_connection_get_id (s_con)) { - id = get_new_connection_name (existing, format, preferred); + id = get_new_connection_name (existing, preferred_id, fallback_id_prefix); g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_ID, id, NULL); g_free (id); } + /* Add an interface name, if requested */ + if (ifname_prefix && !nm_setting_connection_get_interface_name (s_con)) { + ifname = get_new_connection_ifname (existing, ifname_prefix); + g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, NULL); + g_free (ifname); + } + /* Normalize */ - nm_utils_normalize_connection (connection, default_enable_ipv6); + parameters = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (parameters, NM_CONNECTION_NORMALIZE_PARAM_IP6_CONFIG_METHOD, + default_enable_ipv6 ? NM_SETTING_IP6_CONFIG_METHOD_AUTO : NM_SETTING_IP6_CONFIG_METHOD_IGNORE); + nm_connection_normalize (connection, parameters, NULL, NULL); + g_hash_table_destroy (parameters); } char * @@ -682,12 +1366,12 @@ check_ip6_method (NMConnection *orig, { GHashTable *props; const char *orig_ip6_method, *candidate_ip6_method; - NMSettingIP6Config *candidate_ip6; + NMSettingIPConfig *candidate_ip6; gboolean allow = FALSE; props = check_property_in_hash (settings, NM_SETTING_IP6_CONFIG_SETTING_NAME, - NM_SETTING_IP6_CONFIG_METHOD); + NM_SETTING_IP_CONFIG_METHOD); if (!props) return TRUE; @@ -703,7 +1387,7 @@ check_ip6_method (NMConnection *orig, if ( strcmp (orig_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0 && strcmp (candidate_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0 - && (!candidate_ip6 || nm_setting_ip6_config_get_may_fail (candidate_ip6))) { + && (!candidate_ip6 || nm_setting_ip_config_get_may_fail (candidate_ip6))) { allow = TRUE; } @@ -720,7 +1404,7 @@ check_ip6_method (NMConnection *orig, if (allow) { remove_from_hash (settings, props, NM_SETTING_IP6_CONFIG_SETTING_NAME, - NM_SETTING_IP6_CONFIG_METHOD); + NM_SETTING_IP_CONFIG_METHOD); } return allow; } @@ -733,11 +1417,11 @@ check_ip4_method (NMConnection *orig, { GHashTable *props; const char *orig_ip4_method, *candidate_ip4_method; - NMSettingIP4Config *candidate_ip4; + NMSettingIPConfig *candidate_ip4; props = check_property_in_hash (settings, NM_SETTING_IP4_CONFIG_SETTING_NAME, - NM_SETTING_IP4_CONFIG_METHOD); + NM_SETTING_IP_CONFIG_METHOD); if (!props) return TRUE; @@ -752,11 +1436,11 @@ check_ip4_method (NMConnection *orig, if ( strcmp (orig_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0 && strcmp (candidate_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0 - && (!candidate_ip4 || nm_setting_ip4_config_get_may_fail (candidate_ip4)) + && (!candidate_ip4 || nm_setting_ip_config_get_may_fail (candidate_ip4)) && (device_has_carrier == FALSE)) { remove_from_hash (settings, props, NM_SETTING_IP4_CONFIG_SETTING_NAME, - NM_SETTING_IP4_CONFIG_METHOD); + NM_SETTING_IP_CONFIG_METHOD); return TRUE; } return FALSE; @@ -798,7 +1482,7 @@ check_connection_mac_address (NMConnection *orig, GHashTable *settings) { GHashTable *props; - const GByteArray *orig_mac = NULL, *cand_mac = NULL; + const char *orig_mac = NULL, *cand_mac = NULL; NMSettingWired *s_wired_orig, *s_wired_cand; props = check_property_in_hash (settings, @@ -933,6 +1617,31 @@ nm_utils_match_connection (GSList *connections, return best_match; } +int +nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnection **b) +{ + NMSettingConnection *a_s_con, *b_s_con; + gboolean a_ac, b_ac; + gint a_ap, b_ap; + + a_s_con = nm_connection_get_setting_connection (*a); + b_s_con = nm_connection_get_setting_connection (*b); + + a_ac = !!nm_setting_connection_get_autoconnect (a_s_con); + b_ac = !!nm_setting_connection_get_autoconnect (b_s_con); + if (a_ac != b_ac) + return ((int) b_ac) - ((int) a_ac); + if (!a_ac) + return 0; + + a_ap = nm_setting_connection_get_autoconnect_priority (a_s_con); + b_ap = nm_setting_connection_get_autoconnect_priority (b_s_con); + if (a_ap != b_ap) + return (a_ap > b_ap) ? -1 : 1; + + return 0; +} + /* nm_utils_ascii_str_to_int64: * * A wrapper for g_ascii_strtoll, that checks whether the whole string @@ -1008,21 +1717,85 @@ nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max return v; } +/** + * nm_utils_uuid_generate_from_strings: + * @string1: a variadic list of strings. Must be NULL terminated. + * + * Returns a variant3 UUID based on the concatenated C strings. + * It does not simply concatenate them, but also includes the + * terminating '\0' character. For example "a", "b", gives + * "a\0b\0\0". + * + * This has the advantage, that the following invocations + * all give different UUIDs: (""), ("",""), ("","a"), ("a",""), + * ("aa"), ("aa", ""), ("", "aa"), ... + */ +char * +nm_utils_uuid_generate_from_strings (const char *string1, ...) +{ + GString *str; + va_list args; + const char *s; + char *uuid; + + if (!string1) + return nm_utils_uuid_generate_from_string (NULL, 0, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS); + + str = g_string_sized_new (120); /* effectively allocates power of 2 (128)*/ + + g_string_append_len (str, string1, strlen (string1) + 1); + + va_start (args, string1); + s = va_arg (args, const char *); + while (s) { + g_string_append_len (str, s, strlen (s) + 1); + s = va_arg (args, const char *); + } + va_end (args); + + uuid = nm_utils_uuid_generate_from_string (str->str, str->len, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS); + + g_string_free (str, TRUE); + return uuid; +} + +/**************************************************************************/ static gint64 monotonic_timestamp_offset_sec; static void monotonic_timestamp_get (struct timespec *tp) { - static gboolean initialized = FALSE; - int err; - - err = clock_gettime (CLOCK_BOOTTIME, tp); + static int clock_mode = 0; + gboolean first_time = FALSE; + int err = 0; + + switch (clock_mode) { + case 0: + /* the clock is not yet initialized (first run) */ + err = clock_gettime (CLOCK_BOOTTIME, tp); + if (err == -1 && errno == EINVAL) { + clock_mode = 2; + err = clock_gettime (CLOCK_MONOTONIC, tp); + } else + clock_mode = 1; + first_time = TRUE; + break; + case 1: + /* default, return CLOCK_BOOTTIME */ + err = clock_gettime (CLOCK_BOOTTIME, tp); + break; + case 2: + /* fallback, return CLOCK_MONOTONIC. Kernels prior to 2.6.39 + * don't support CLOCK_BOOTTIME. */ + err = clock_gettime (CLOCK_MONOTONIC, tp); + break; + } g_assert (err == 0); (void)err; g_assert (tp->tv_nsec >= 0 && tp->tv_nsec < NM_UTILS_NS_PER_SECOND); - if (G_LIKELY (initialized)) + if (G_LIKELY (!first_time)) return; /* Calculate an offset for the time stamp. @@ -1039,7 +1812,6 @@ monotonic_timestamp_get (struct timespec *tp) * wraps (~68 years). **/ monotonic_timestamp_offset_sec = (- ((gint64) tp->tv_sec)) + 1; - initialized = TRUE; if (nm_logging_enabled (LOGL_DEBUG, LOGD_CORE)) { time_t now = time (NULL); @@ -1048,8 +1820,9 @@ monotonic_timestamp_get (struct timespec *tp) strftime (s, sizeof (s), "%Y-%m-%d %H:%M:%S", localtime_r (&now, &tm)); nm_log_dbg (LOGD_CORE, "monotonic timestamp started counting 1.%09ld seconds ago with " - "an offset of %lld.0 seconds to CLOCK_BOOTTIME (local time is %s)", - tp->tv_nsec, (long long) -monotonic_timestamp_offset_sec, s); + "an offset of %lld.0 seconds to %s (local time is %s)", + tp->tv_nsec, (long long) -monotonic_timestamp_offset_sec, + clock_mode == 1 ? "CLOCK_BOOTTIME" : "CLOCK_MONOTONIC", s); } } @@ -1068,7 +1841,7 @@ monotonic_timestamp_get (struct timespec *tp) gint64 nm_utils_get_monotonic_timestamp_ns (void) { - struct timespec tp; + struct timespec tp = { 0 }; monotonic_timestamp_get (&tp); @@ -1095,7 +1868,7 @@ nm_utils_get_monotonic_timestamp_ns (void) gint64 nm_utils_get_monotonic_timestamp_us (void) { - struct timespec tp; + struct timespec tp = { 0 }; monotonic_timestamp_get (&tp); @@ -1122,7 +1895,7 @@ nm_utils_get_monotonic_timestamp_us (void) gint64 nm_utils_get_monotonic_timestamp_ms (void) { - struct timespec tp; + struct timespec tp = { 0 }; monotonic_timestamp_get (&tp); @@ -1149,12 +1922,286 @@ nm_utils_get_monotonic_timestamp_ms (void) gint32 nm_utils_get_monotonic_timestamp_s (void) { - struct timespec tp; + struct timespec tp = { 0 }; monotonic_timestamp_get (&tp); return (((gint64) tp.tv_sec) + monotonic_timestamp_offset_sec); } +typedef struct +{ + const char *name; + NMSetting *setting; + NMSetting *diff_base_setting; + GHashTable *setting_diff; +} LogConnectionSettingData; + +typedef struct +{ + const char *item_name; + NMSettingDiffResult diff_result; +} LogConnectionSettingItem; + +static gint +_log_connection_sort_hashes_fcn (gconstpointer a, gconstpointer b) +{ + const LogConnectionSettingData *v1 = a; + const LogConnectionSettingData *v2 = b; + guint32 p1, p2; + NMSetting *s1, *s2; + + s1 = v1->setting ? v1->setting : v1->diff_base_setting; + s2 = v2->setting ? v2->setting : v2->diff_base_setting; + + g_assert (s1 && s2); + + p1 = _nm_setting_get_setting_priority (s1); + p2 = _nm_setting_get_setting_priority (s2); + + if (p1 != p2) + return p1 > p2 ? 1 : -1; + + return strcmp (v1->name, v2->name); +} + +static GArray * +_log_connection_sort_hashes (NMConnection *connection, NMConnection *diff_base, GHashTable *connection_diff) +{ + GHashTableIter iter; + GArray *sorted_hashes; + LogConnectionSettingData setting_data; + + sorted_hashes = g_array_sized_new (TRUE, FALSE, sizeof (LogConnectionSettingData), g_hash_table_size (connection_diff)); + + g_hash_table_iter_init (&iter, connection_diff); + while (g_hash_table_iter_next (&iter, (gpointer) &setting_data.name, (gpointer) &setting_data.setting_diff)) { + setting_data.setting = nm_connection_get_setting_by_name (connection, setting_data.name); + setting_data.diff_base_setting = diff_base ? nm_connection_get_setting_by_name (diff_base, setting_data.name) : NULL; + g_assert (setting_data.setting || setting_data.diff_base_setting); + g_array_append_val (sorted_hashes, setting_data); + } + + g_array_sort (sorted_hashes, _log_connection_sort_hashes_fcn); + return sorted_hashes; +} + +static gint +_log_connection_sort_names_fcn (gconstpointer a, gconstpointer b) +{ + const LogConnectionSettingItem *v1 = a; + const LogConnectionSettingItem *v2 = b; + + /* we want to first show the items, that disappeared, then the one that changed and + * then the ones that were added. */ + + if ((v1->diff_result & NM_SETTING_DIFF_RESULT_IN_A) != (v2->diff_result & NM_SETTING_DIFF_RESULT_IN_A)) + return (v1->diff_result & NM_SETTING_DIFF_RESULT_IN_A) ? -1 : 1; + if ((v1->diff_result & NM_SETTING_DIFF_RESULT_IN_B) != (v2->diff_result & NM_SETTING_DIFF_RESULT_IN_B)) + return (v1->diff_result & NM_SETTING_DIFF_RESULT_IN_B) ? 1 : -1; + return strcmp (v1->item_name, v2->item_name); +} + +static char * +_log_connection_get_property (NMSetting *setting, const char *name) +{ + GValue val = G_VALUE_INIT; + char *s; + + g_return_val_if_fail (setting, NULL); + + if ( !NM_IS_SETTING_VPN (setting) + && nm_setting_get_secret_flags (setting, name, NULL, NULL)) + return g_strdup ("****"); + + if (!_nm_setting_get_property (setting, name, &val)) + g_return_val_if_reached (FALSE); + + if (G_VALUE_HOLDS_STRING (&val)) { + const char *val_s; + + val_s = g_value_get_string (&val); + if (!val_s) { + /* for NULL, we want to return the unquoted string "NULL". */ + s = g_strdup ("NULL"); + } else { + char *escaped = g_strescape (val_s, "'"); + + s = g_strdup_printf ("'%s'", escaped); + g_free (escaped); + } + } else { + s = g_strdup_value_contents (&val); + if (s == NULL) + s = g_strdup ("NULL"); + else { + char *escaped = g_strescape (s, "'"); + + g_free (s); + s = escaped; + } + } + g_value_unset(&val); + return s; +} + +static void +_log_connection_sort_names (LogConnectionSettingData *setting_data, GArray *sorted_names) +{ + GHashTableIter iter; + LogConnectionSettingItem item; + gpointer p; + + g_array_set_size (sorted_names, 0); + + g_hash_table_iter_init (&iter, setting_data->setting_diff); + while (g_hash_table_iter_next (&iter, (gpointer) &item.item_name, &p)) { + item.diff_result = GPOINTER_TO_UINT (p); + g_array_append_val (sorted_names, item); + } + + g_array_sort (sorted_names, _log_connection_sort_names_fcn); +} + +void +nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base, guint32 level, guint64 domain, const char *name, const char *prefix) +{ + GHashTable *connection_diff = NULL; + GArray *sorted_hashes; + GArray *sorted_names = NULL; + int i, j; + gboolean connection_diff_are_same; + gboolean print_header = TRUE; + gboolean print_setting_header; + GString *str1; + + g_return_if_fail (NM_IS_CONNECTION (connection)); + g_return_if_fail (!diff_base || (NM_IS_CONNECTION (diff_base) && diff_base != connection)); + + /* For VPN setting types, this is broken, because we cannot (generically) print the content of data/secrets. Bummer... */ + + if (!nm_logging_enabled (level, domain)) + return; + + if (!prefix) + prefix = ""; + if (!name) + name = ""; + + connection_diff_are_same = nm_connection_diff (connection, diff_base, NM_SETTING_COMPARE_FLAG_EXACT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT, &connection_diff); + if (connection_diff_are_same) { + if (diff_base) + nm_log (level, domain, "%sconnection '%s' (%p and %p): no difference", prefix, name, connection, diff_base); + else + nm_log (level, domain, "%sconnection '%s' (%p): no properties set", prefix, name, connection); + g_assert (!connection_diff); + return; + } + + /* FIXME: it doesn't nicely show the content of NMSettingVpn, becuase nm_connection_diff() does not + * expand the hash values. */ + + sorted_hashes = _log_connection_sort_hashes (connection, diff_base, connection_diff); + if (sorted_hashes->len <= 0) + goto out; + + sorted_names = g_array_new (FALSE, FALSE, sizeof (LogConnectionSettingItem)); + str1 = g_string_new (NULL); + + for (i = 0; i < sorted_hashes->len; i++) { + LogConnectionSettingData *setting_data = &g_array_index (sorted_hashes, LogConnectionSettingData, i); + + _log_connection_sort_names (setting_data, sorted_names); + print_setting_header = TRUE; + for (j = 0; j < sorted_names->len; j++) { + char *str_conn, *str_diff; + LogConnectionSettingItem *item = &g_array_index (sorted_names, LogConnectionSettingItem, j); + + str_conn = (item->diff_result & NM_SETTING_DIFF_RESULT_IN_A) + ? _log_connection_get_property (setting_data->setting, item->item_name) + : NULL; + str_diff = (item->diff_result & NM_SETTING_DIFF_RESULT_IN_B) + ? _log_connection_get_property (setting_data->diff_base_setting, item->item_name) + : NULL; + + if (print_header) { + GError *err_verify = NULL; + + if (diff_base) + nm_log (level, domain, "%sconnection '%s' (%p < %p):", prefix, name, connection, diff_base); + else + nm_log (level, domain, "%sconnection '%s' (%p):", prefix, name, connection); + print_header = FALSE; + + if (!nm_connection_verify (connection, &err_verify)) { + nm_log (level, domain, "%sconnection %p does not verify: %s", prefix, connection, err_verify->message); + g_clear_error (&err_verify); + } + } +#define _NM_LOG_ALIGN "-25" + if (print_setting_header) { + if (diff_base) { + if (setting_data->setting && setting_data->diff_base_setting) + g_string_printf (str1, "%p < %p", setting_data->setting, setting_data->diff_base_setting); + else if (setting_data->diff_base_setting) + g_string_printf (str1, "*missing* < %p", setting_data->diff_base_setting); + else + g_string_printf (str1, "%p < *missing*", setting_data->setting); + nm_log (level, domain, "%s%"_NM_LOG_ALIGN"s [ %s ]", prefix, setting_data->name, str1->str); + } else + nm_log (level, domain, "%s%"_NM_LOG_ALIGN"s [ %p ]", prefix, setting_data->name, setting_data->setting); + print_setting_header = FALSE; + } + g_string_printf (str1, "%s.%s", setting_data->name, item->item_name); + switch (item->diff_result & (NM_SETTING_DIFF_RESULT_IN_A | NM_SETTING_DIFF_RESULT_IN_B)) { + case NM_SETTING_DIFF_RESULT_IN_B: + nm_log (level, domain, "%s%"_NM_LOG_ALIGN"s < %s", prefix, str1->str, str_diff ? str_diff : "NULL"); + break; + case NM_SETTING_DIFF_RESULT_IN_A: + nm_log (level, domain, "%s%"_NM_LOG_ALIGN"s = %s", prefix, str1->str, str_conn ? str_conn : "NULL"); + break; + default: + nm_log (level, domain, "%s%"_NM_LOG_ALIGN"s = %s < %s", prefix, str1->str, str_conn ? str_conn : "NULL", str_diff ? str_diff : "NULL"); + break; +#undef _NM_LOG_ALIGN + } + g_free (str_conn); + g_free (str_diff); + } + } + + g_array_free (sorted_names, TRUE); + g_string_free (str1, TRUE); +out: + g_hash_table_destroy (connection_diff); + g_array_free (sorted_hashes, TRUE); +} + + +#define IPV6_PROPERTY_DIR "/proc/sys/net/ipv6/conf/" +#define IPV4_PROPERTY_DIR "/proc/sys/net/ipv4/conf/" +G_STATIC_ASSERT (sizeof (IPV4_PROPERTY_DIR) == sizeof (IPV6_PROPERTY_DIR)); + +static const char * +_get_property_path (const char *ifname, + const char *property, + gboolean ipv6) +{ + static char path[sizeof (IPV6_PROPERTY_DIR) + IFNAMSIZ + 32]; + int len; + + ifname = ASSERT_VALID_PATH_COMPONENT (ifname); + property = ASSERT_VALID_PATH_COMPONENT (property); + + len = g_snprintf (path, + sizeof (path), + "%s%s/%s", + ipv6 ? IPV6_PROPERTY_DIR : IPV4_PROPERTY_DIR, + ifname, + property); + g_assert (len < sizeof (path) - 1); + + return path; +} /** * nm_utils_ip6_property_path: @@ -1167,18 +2214,21 @@ nm_utils_get_monotonic_timestamp_s (void) const char * nm_utils_ip6_property_path (const char *ifname, const char *property) { -#define IPV6_PROPERTY_DIR "/proc/sys/net/ipv6/conf/" - static char path[sizeof (IPV6_PROPERTY_DIR) + IFNAMSIZ + 32]; - int len; - - ifname = ASSERT_VALID_PATH_COMPONENT (ifname); - property = ASSERT_VALID_PATH_COMPONENT (property); - - len = g_snprintf (path, sizeof (path), IPV6_PROPERTY_DIR "%s/%s", - ifname, property); - g_assert (len < sizeof (path) - 1); + return _get_property_path (ifname, property, TRUE); +} - return path; +/** + * nm_utils_ip4_property_path: + * @ifname: an interface name + * @property: a property name + * + * Returns the path to IPv4 property @property on @ifname. Note that + * this uses a static buffer. + */ +const char * +nm_utils_ip4_property_path (const char *ifname, const char *property) +{ + return _get_property_path (ifname, property, FALSE); } const char * @@ -1208,5 +2258,291 @@ fail: else nm_log_err (LOGD_CORE, "Failed asserting path component: \"%s\"", name); g_error ("FATAL: Failed asserting path component: %s", name ? name : "(null)"); + g_assert_not_reached (); +} + +gboolean +nm_utils_is_specific_hostname (const char *name) +{ + if (!name) + return FALSE; + if ( strcmp (name, "(none)") + && strcmp (name, "localhost") + && strcmp (name, "localhost6") + && strcmp (name, "localhost.localdomain") + && strcmp (name, "localhost6.localdomain6")) + return TRUE; + return FALSE; +} + +/******************************************************************/ + +/* Returns the "u" (universal/local) bit value for a Modified EUI-64 */ +static gboolean +get_gre_eui64_u_bit (guint32 addr) +{ + static const struct { + guint32 mask; + guint32 result; + } items[] = { + { 0xff000000 }, { 0x7f000000 }, /* IPv4 loopback */ + { 0xf0000000 }, { 0xe0000000 }, /* IPv4 multicast */ + { 0xffffff00 }, { 0xe0000000 }, /* IPv4 local multicast */ + { 0xffffffff }, { INADDR_BROADCAST }, /* limited broadcast */ + { 0xff000000 }, { 0x00000000 }, /* zero net */ + { 0xff000000 }, { 0x0a000000 }, /* private 10 (RFC3330) */ + { 0xfff00000 }, { 0xac100000 }, /* private 172 */ + { 0xffff0000 }, { 0xc0a80000 }, /* private 192 */ + { 0xffff0000 }, { 0xa9fe0000 }, /* IPv4 link-local */ + { 0xffffff00 }, { 0xc0586300 }, /* anycast 6-to-4 */ + { 0xffffff00 }, { 0xc0000200 }, /* test 192 */ + { 0xfffe0000 }, { 0xc6120000 }, /* test 198 */ + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS (items); i++) { + if ((addr & htonl (items[i].mask)) == htonl (items[i].result)) + return 0x00; /* "local" scope */ + } + return 0x02; /* "universal" scope */ +} + +/** + * nm_utils_get_ipv6_interface_identifier: + * @link_type: the hardware link type + * @hwaddr: the hardware address of the interface + * @hwaddr_len: the length (in bytes) of @hwaddr + * @out_iid: on success, filled with the interface identifier; on failure + * zeroed out + * + * Constructs an interface identifier in "Modified EUI-64" format which is + * suitable for constructing IPv6 addresses. Note that the identifier is + * not obscured in any way (eg, RFC3041). + * + * Returns: %TRUE if the interface identifier could be constructed, %FALSE if + * if could not be constructed. + */ +gboolean +nm_utils_get_ipv6_interface_identifier (NMLinkType link_type, + const guint8 *hwaddr, + guint hwaddr_len, + NMUtilsIPv6IfaceId *out_iid) +{ + guint32 addr; + + g_return_val_if_fail (hwaddr != NULL, FALSE); + g_return_val_if_fail (hwaddr_len > 0, FALSE); + g_return_val_if_fail (out_iid != NULL, FALSE); + + out_iid->id = 0; + + switch (link_type) { + case NM_LINK_TYPE_INFINIBAND: + /* Use the port GUID per http://tools.ietf.org/html/rfc4391#section-8, + * making sure to set the 'u' bit to 1. The GUID is the lower 64 bits + * of the IPoIB interface's hardware address. + */ + g_return_val_if_fail (hwaddr_len == INFINIBAND_ALEN, FALSE); + memcpy (out_iid->id_u8, hwaddr + INFINIBAND_ALEN - 8, 8); + out_iid->id_u8[0] |= 0x02; + return TRUE; + case NM_LINK_TYPE_GRE: + case NM_LINK_TYPE_GRETAP: + /* Hardware address is the network-endian IPv4 address */ + g_return_val_if_fail (hwaddr_len == 4, FALSE); + addr = * (guint32 *) hwaddr; + out_iid->id_u8[0] = get_gre_eui64_u_bit (addr); + out_iid->id_u8[1] = 0x00; + out_iid->id_u8[2] = 0x5E; + out_iid->id_u8[3] = 0xFE; + memcpy (out_iid->id_u8 + 4, &addr, 4); + return TRUE; + default: + if (hwaddr_len == ETH_ALEN) { + /* Translate 48-bit MAC address to a 64-bit Modified EUI-64. See + * http://tools.ietf.org/html/rfc4291#appendix-A + */ + out_iid->id_u8[0] = hwaddr[0] ^ 0x02; + out_iid->id_u8[1] = hwaddr[1]; + out_iid->id_u8[2] = hwaddr[2]; + out_iid->id_u8[3] = 0xff; + out_iid->id_u8[4] = 0xfe; + out_iid->id_u8[5] = hwaddr[3]; + out_iid->id_u8[6] = hwaddr[4]; + out_iid->id_u8[7] = hwaddr[5]; + return TRUE; + } + break; + } + return FALSE; +} + +void +nm_utils_ipv6_addr_set_interface_identfier (struct in6_addr *addr, + const NMUtilsIPv6IfaceId iid) +{ + memcpy (addr->s6_addr + 8, &iid.id_u8, 8); +} + +void +nm_utils_ipv6_interface_identfier_get_from_addr (NMUtilsIPv6IfaceId *iid, + const struct in6_addr *addr) +{ + memcpy (iid, addr->s6_addr + 8, 8); +} + +/** + * nm_utils_connection_hash_to_dict: + * @hash: a hashed #NMConnection + * + * Returns: a (floating) #GVariant equivalent to @hash. + */ +GVariant * +nm_utils_connection_hash_to_dict (GHashTable *hash) +{ + GValue val = { 0, }; + GVariant *variant; + + if (!hash) + return NULL; + + g_value_init (&val, DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT); + g_value_set_boxed (&val, hash); + variant = dbus_g_value_build_g_variant (&val); + g_value_unset (&val); + + return variant; +} + +/** + * nm_utils_connection_dict_to_hash: + * @dict: a #GVariant-serialized #NMConnection + * + * Returns: a #GHashTable equivalent to @dict. + */ +GHashTable * +nm_utils_connection_dict_to_hash (GVariant *dict) +{ + GValue val = { 0, }; + + if (!dict) + return NULL; + + dbus_g_value_parse_g_variant (dict, &val); + return g_value_get_boxed (&val); } +GSList * +nm_utils_ip4_routes_from_gvalue (const GValue *value) +{ + GPtrArray *routes; + int i; + GSList *list = NULL; + + routes = (GPtrArray *) g_value_get_boxed (value); + for (i = 0; routes && (i < routes->len); i++) { + GArray *array = (GArray *) g_ptr_array_index (routes, i); + guint32 *array_val = (guint32 *) array->data; + NMIPRoute *route; + GError *error = NULL; + + if (array->len < 4) { + g_warning ("Ignoring invalid IP4 route"); + continue; + } + + route = nm_ip_route_new_binary (AF_INET, + &array_val[0], array_val[1], + &array_val[2], array_val[3], + &error); + if (route) + list = g_slist_prepend (list, route); + else { + g_warning ("Ignoring invalid IP4 route: %s", error->message); + g_clear_error (&error); + } + } + + return g_slist_reverse (list); +} + +static gboolean +_nm_utils_gvalue_array_validate (GValueArray *elements, guint n_expected, ...) +{ + va_list args; + GValue *tmp; + int i; + gboolean valid = FALSE; + + if (n_expected != elements->n_values) + return FALSE; + + va_start (args, n_expected); + for (i = 0; i < n_expected; i++) { + tmp = g_value_array_get_nth (elements, i); + if (G_VALUE_TYPE (tmp) != va_arg (args, GType)) + goto done; + } + valid = TRUE; + +done: + va_end (args); + return valid; +} + +GSList * +nm_utils_ip6_routes_from_gvalue (const GValue *value) +{ + GPtrArray *routes; + int i; + GSList *list = NULL; + + routes = (GPtrArray *) g_value_get_boxed (value); + for (i = 0; routes && (i < routes->len); i++) { + GValueArray *route_values = (GValueArray *) g_ptr_array_index (routes, i); + GByteArray *dest, *next_hop; + guint prefix, metric; + NMIPRoute *route; + GError *error = NULL; + + if (!_nm_utils_gvalue_array_validate (route_values, 4, + DBUS_TYPE_G_UCHAR_ARRAY, + G_TYPE_UINT, + DBUS_TYPE_G_UCHAR_ARRAY, + G_TYPE_UINT)) { + g_warning ("Ignoring invalid IP6 route"); + continue; + } + + dest = g_value_get_boxed (g_value_array_get_nth (route_values, 0)); + if (dest->len != 16) { + g_warning ("%s: ignoring invalid IP6 dest address of length %d", + __func__, dest->len); + continue; + } + + prefix = g_value_get_uint (g_value_array_get_nth (route_values, 1)); + + next_hop = g_value_get_boxed (g_value_array_get_nth (route_values, 2)); + if (next_hop->len != 16) { + g_warning ("%s: ignoring invalid IP6 next_hop address of length %d", + __func__, next_hop->len); + continue; + } + + metric = g_value_get_uint (g_value_array_get_nth (route_values, 3)); + + route = nm_ip_route_new_binary (AF_INET6, + dest->data, prefix, + next_hop->data, metric, + &error); + if (route) + list = g_slist_prepend (list, route); + else { + g_warning ("Ignoring invalid IP6 route: %s", error->message); + g_clear_error (&error); + } + } + + return g_slist_reverse (list); +} |