diff options
Diffstat (limited to 'src/libnm-systemd-shared')
88 files changed, 3784 insertions, 1160 deletions
diff --git a/src/libnm-systemd-shared/meson.build b/src/libnm-systemd-shared/meson.build index b32bd7f6e0..3d3e42709f 100644 --- a/src/libnm-systemd-shared/meson.build +++ b/src/libnm-systemd-shared/meson.build @@ -5,6 +5,7 @@ libnm_systemd_shared = static_library( sources: files( 'nm-sd-utils-shared.c', 'src/basic/alloc-util.c', + 'src/basic/btrfs.c', 'src/basic/env-file.c', 'src/basic/env-util.c', 'src/basic/escape.c', @@ -58,6 +59,7 @@ libnm_systemd_shared = static_library( top_inc, src_inc, ], + c_args: libnm_systemd_common_cflags, dependencies: glib_dep, ) diff --git a/src/libnm-systemd-shared/sd-adapt-shared/netif-util.h b/src/libnm-systemd-shared/sd-adapt-shared/sd-messages.h index 637892c2d6..637892c2d6 100644 --- a/src/libnm-systemd-shared/sd-adapt-shared/netif-util.h +++ b/src/libnm-systemd-shared/sd-adapt-shared/sd-messages.h diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.c b/src/libnm-systemd-shared/src/basic/alloc-util.c index c07ab58942..243ff521a7 100644 --- a/src/libnm-systemd-shared/src/basic/alloc-util.c +++ b/src/libnm-systemd-shared/src/basic/alloc-util.c @@ -105,6 +105,33 @@ void* greedy_realloc0( return q; } +void* greedy_realloc_append( + void **p, + size_t *n_p, + const void *from, + size_t n_from, + size_t size) { + + uint8_t *q; + + assert(p); + assert(n_p); + assert(from || n_from == 0); + + if (n_from > SIZE_MAX - *n_p) + return NULL; + + q = greedy_realloc(p, *n_p + n_from, size); + if (!q) + return NULL; + + memcpy_safe(q + *n_p * size, from, n_from * size); + + *n_p += n_from; + + return q; +} + void *expand_to_usable(void *ptr, size_t newsize _unused_) { return ptr; } diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.h b/src/libnm-systemd-shared/src/basic/alloc-util.h index 9a62381df1..c215c33f4b 100644 --- a/src/libnm-systemd-shared/src/basic/alloc-util.h +++ b/src/libnm-systemd-shared/src/basic/alloc-util.h @@ -15,13 +15,12 @@ typedef void (*free_func_t)(void *p); typedef void* (*mfree_func_t)(void *p); -typedef void (*free_array_func_t)(void *p, size_t n); /* If for some reason more than 4M are allocated on the stack, let's abort immediately. It's better than * proceeding and smashing the stack limits. Note that by default RLIMIT_STACK is 8M on Linux. */ #define ALLOCA_MAX (4U*1024U*1024U) -#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n))) +#define new(t, n) ((t*) malloc_multiply(n, sizeof(t))) #define new0(t, n) ((t*) calloc((n) ?: 1, sizeof(t))) @@ -46,9 +45,9 @@ typedef void (*free_array_func_t)(void *p, size_t n); (t*) alloca0((sizeof(t)*_n_)); \ }) -#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n))) +#define newdup(t, p, n) ((t*) memdup_multiply(p, n, sizeof(t))) -#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, sizeof(t), (n))) +#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, n, sizeof(t))) #define malloc0(n) (calloc(1, (n) ?: 1)) @@ -113,7 +112,7 @@ static inline bool size_multiply_overflow(size_t size, size_t need) { return _unlikely_(need != 0 && size > (SIZE_MAX / need)); } -_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t size, size_t need) { +_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t need, size_t size) { if (size_multiply_overflow(size, need)) return NULL; @@ -129,7 +128,7 @@ _alloc_(2, 3) static inline void *reallocarray(void *p, size_t need, size_t size } #endif -_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, size_t need) { +_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t need, size_t size) { if (size_multiply_overflow(size, need)) return NULL; @@ -138,7 +137,7 @@ _alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, si /* Note that we can't decorate this function with _alloc_() since the returned memory area is one byte larger * than the product of its parameters. */ -static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) { +static inline void *memdup_suffix0_multiply(const void *p, size_t need, size_t size) { if (size_multiply_overflow(size, need)) return NULL; @@ -147,6 +146,7 @@ static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t n void* greedy_realloc(void **p, size_t need, size_t size); void* greedy_realloc0(void **p, size_t need, size_t size); +void* greedy_realloc_append(void **p, size_t *n_p, const void *from, size_t n_from, size_t size); #define GREEDY_REALLOC(array, need) \ greedy_realloc((void**) &(array), (need), sizeof((array)[0])) @@ -154,6 +154,9 @@ void* greedy_realloc0(void **p, size_t need, size_t size); #define GREEDY_REALLOC0(array, need) \ greedy_realloc0((void**) &(array), (need), sizeof((array)[0])) +#define GREEDY_REALLOC_APPEND(array, n_array, from, n_from) \ + greedy_realloc_append((void**) &(array), (size_t*) &(n_array), (from), (n_from), sizeof((array)[0])) + #define alloca0(n) \ ({ \ char *_new_; \ @@ -224,7 +227,6 @@ static inline size_t malloc_sizeof_safe(void **xp) { MALLOC_SIZEOF_SAFE(x)/sizeof((x)[0]), \ VOID_0)) - /* These are like strdupa()/strndupa(), but honour ALLOCA_MAX */ #define strdupa_safe(s) \ ({ \ @@ -235,7 +237,40 @@ static inline size_t malloc_sizeof_safe(void **xp) { #define strndupa_safe(s, n) \ ({ \ const char *_t = (s); \ - (char*) memdupa_suffix0(_t, strnlen(_t, (n))); \ + (char*) memdupa_suffix0(_t, strnlen(_t, n)); \ }) +/* Free every element of the array. */ +static inline void free_many(void **p, size_t n) { + assert(p || n == 0); + + FOREACH_ARRAY(i, p, n) + *i = mfree(*i); +} + +/* Typesafe wrapper for char** rather than void**. Unfortunately C won't implicitly cast this. */ +static inline void free_many_charp(char **c, size_t n) { + free_many((void**) c, n); +} + +_alloc_(2) static inline void *realloc0(void *p, size_t new_size) { + size_t old_size; + void *q; + + /* Like realloc(), but initializes anything appended to zero */ + + old_size = MALLOC_SIZEOF_SAFE(p); + + q = realloc(p, new_size); + if (!q) + return NULL; + + new_size = MALLOC_SIZEOF_SAFE(q); /* Update with actually allocated space */ + + if (new_size > old_size) + memset((uint8_t*) q + old_size, 0, new_size - old_size); + + return q; +} + #include "memory-util.h" diff --git a/src/libnm-systemd-shared/src/basic/arphrd-util.h b/src/libnm-systemd-shared/src/basic/arphrd-util.h new file mode 100644 index 0000000000..33f5694abd --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/arphrd-util.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> +#include <stddef.h> + +const char *arphrd_to_name(int id); +int arphrd_from_name(const char *name); + +size_t arphrd_to_hw_addr_len(uint16_t arphrd); diff --git a/src/libnm-systemd-shared/src/basic/async.h b/src/libnm-systemd-shared/src/basic/async.h deleted file mode 100644 index e0bbaa5658..0000000000 --- a/src/libnm-systemd-shared/src/basic/async.h +++ /dev/null @@ -1,13 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include <sys/types.h> - -#include "macro.h" - -int asynchronous_job(void* (*func)(void *p), void *arg); - -int asynchronous_sync(pid_t *ret_pid); -int asynchronous_close(int fd); - -DEFINE_TRIVIAL_CLEANUP_FUNC(int, asynchronous_close); diff --git a/src/libnm-systemd-shared/src/basic/btrfs.c b/src/libnm-systemd-shared/src/basic/btrfs.c new file mode 100644 index 0000000000..3b23607849 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/btrfs.c @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-shared.h" + +#include <linux/btrfs.h> +#include <sys/ioctl.h> + +#include "btrfs.h" +#include "fd-util.h" +#include "fs-util.h" +#include "path-util.h" + +int btrfs_validate_subvolume_name(const char *name) { + + if (!filename_is_valid(name)) + return -EINVAL; + + if (strlen(name) > BTRFS_SUBVOL_NAME_MAX) + return -E2BIG; + + return 0; +} + +static int extract_subvolume_name(const char *path, char **ret) { + _cleanup_free_ char *fn = NULL; + int r; + + assert(path); + assert(ret); + + r = path_extract_filename(path, &fn); + if (r < 0) + return r; + + r = btrfs_validate_subvolume_name(fn); + if (r < 0) + return r; + + *ret = TAKE_PTR(fn); + return 0; +} + +int btrfs_subvol_make(int dir_fd, const char *path) { + struct btrfs_ioctl_vol_args args = {}; + _cleanup_free_ char *subvolume = NULL, *parent = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(!isempty(path)); + + r = extract_subvolume_name(path, &subvolume); + if (r < 0) + return r; + + r = path_extract_directory(path, &parent); + if (r < 0) { + if (r != -EDESTADDRREQ) /* Propagate error, unless only a filename was specified, which is OK */ + return r; + + dir_fd = fd_reopen_condition(dir_fd, O_CLOEXEC, O_PATH, &fd); /* drop O_PATH if it is set */ + if (dir_fd < 0) + return dir_fd; + } else { + fd = openat(dir_fd, parent, O_DIRECTORY|O_RDONLY|O_CLOEXEC, 0); + if (fd < 0) + return -errno; + + dir_fd = fd; + } + + strncpy(args.name, subvolume, sizeof(args.name)-1); + + return RET_NERRNO(ioctl(dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args)); +} + +int btrfs_subvol_make_fallback(int dir_fd, const char *path, mode_t mode) { + mode_t old, combined; + int r; + + assert(path); + + /* Let's work like mkdir(), i.e. take the specified mode, and mask it with the current umask. */ + old = umask(~mode); + combined = old | ~mode; + if (combined != ~mode) + umask(combined); + r = btrfs_subvol_make(dir_fd, path); + umask(old); + + if (r >= 0) + return 1; /* subvol worked */ + if (!ERRNO_IS_NOT_SUPPORTED(r)) + return r; + + if (mkdirat(dir_fd, path, mode) < 0) + return -errno; + + return 0; /* plain directory */ +} diff --git a/src/libnm-systemd-shared/src/basic/btrfs.h b/src/libnm-systemd-shared/src/basic/btrfs.h new file mode 100644 index 0000000000..38be9d2b3b --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/btrfs.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> + +int btrfs_validate_subvolume_name(const char *name); + +int btrfs_subvol_make(int dir_fd, const char *path); + +int btrfs_subvol_make_fallback(int dir_fd, const char *path, mode_t mode); diff --git a/src/libnm-systemd-shared/src/basic/cgroup-util.h b/src/libnm-systemd-shared/src/basic/cgroup-util.h index 9b30ae0396..244f3b657b 100644 --- a/src/libnm-systemd-shared/src/basic/cgroup-util.h +++ b/src/libnm-systemd-shared/src/basic/cgroup-util.h @@ -10,6 +10,7 @@ #include <sys/types.h> #include "constants.h" +#include "pidref.h" #include "set.h" #define SYSTEMD_CGROUP_CONTROLLER_LEGACY "name=systemd" @@ -35,7 +36,7 @@ typedef enum CGroupController { CGROUP_CONTROLLER_BPF_SOCKET_BIND, CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES, /* The BPF hook implementing RestrictFileSystems= is not defined here. - * It's applied as late as possible in exec_child() so we don't block + * It's applied as late as possible in exec_invoke() so we don't block * our own unit setup code. */ _CGROUP_CONTROLLER_MAX, @@ -66,10 +67,13 @@ typedef enum CGroupMask { /* All real cgroup v2 controllers */ CGROUP_MASK_V2 = CGROUP_MASK_CPU|CGROUP_MASK_CPUSET|CGROUP_MASK_IO|CGROUP_MASK_MEMORY|CGROUP_MASK_PIDS, + /* All controllers we want to delegate in case of Delegate=yes. Which are pretty much the v2 controllers only, as delegation on v1 is not safe, and bpf stuff isn't a real controller */ + CGROUP_MASK_DELEGATE = CGROUP_MASK_V2, + /* All cgroup v2 BPF pseudo-controllers */ CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES|CGROUP_MASK_BPF_FOREIGN|CGROUP_MASK_BPF_SOCKET_BIND|CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES, - _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1 + _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1, } CGroupMask; static inline CGroupMask CGROUP_MASK_EXTEND_JOINED(CGroupMask mask) { @@ -176,13 +180,13 @@ typedef enum CGroupUnified { * generate paths with multiple adjacent / removed. */ -int cg_enumerate_processes(const char *controller, const char *path, FILE **_f); -int cg_read_pid(FILE *f, pid_t *_pid); -int cg_read_event(const char *controller, const char *path, const char *event, - char **val); +int cg_enumerate_processes(const char *controller, const char *path, FILE **ret); +int cg_read_pid(FILE *f, pid_t *ret); +int cg_read_pidref(FILE *f, PidRef *ret); +int cg_read_event(const char *controller, const char *path, const char *event, char **ret); -int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d); -int cg_read_subgroup(DIR *d, char **fn); +int cg_enumerate_subgroups(const char *controller, const char *path, DIR **ret); +int cg_read_subgroup(DIR *d, char **ret); typedef enum CGroupFlags { CGROUP_SIGCONT = 1 << 0, @@ -190,25 +194,31 @@ typedef enum CGroupFlags { CGROUP_REMOVE = 1 << 2, } CGroupFlags; -typedef int (*cg_kill_log_func_t)(pid_t pid, int sig, void *userdata); +typedef int (*cg_kill_log_func_t)(const PidRef *pid, int sig, void *userdata); -int cg_kill(const char *controller, const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata); -int cg_kill_kernel_sigkill(const char *controller, const char *path); -int cg_kill_recursive(const char *controller, const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata); +int cg_kill(const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata); +int cg_kill_kernel_sigkill(const char *path); +int cg_kill_recursive(const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata); int cg_split_spec(const char *spec, char **ret_controller, char **ret_path); -int cg_mangle_path(const char *path, char **result); +int cg_mangle_path(const char *path, char **ret); -int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs); -int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs); +int cg_get_path(const char *controller, const char *path, const char *suffix, char **ret); +int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **ret); -int cg_pid_get_path(const char *controller, pid_t pid, char **path); +int cg_pid_get_path(const char *controller, pid_t pid, char **ret); +int cg_pidref_get_path(const char *controller, const PidRef *pidref, char **ret); int cg_rmdir(const char *controller, const char *path); -int cg_is_threaded(const char *controller, const char *path); +int cg_is_threaded(const char *path); + +int cg_is_delegated(const char *path); +int cg_is_delegated_fd(int fd); + +int cg_has_coredump_receive(const char *path); -typedef enum { +typedef enum { CG_KEY_MODE_GRACEFUL = 1 << 0, } CGroupKeyMode; @@ -239,14 +249,14 @@ int cg_get_attribute_as_uint64(const char *controller, const char *path, const c /* Does a parse_boolean() on the attribute contents and sets ret accordingly */ int cg_get_attribute_as_bool(const char *controller, const char *path, const char *attribute, bool *ret); -int cg_get_owner(const char *controller, const char *path, uid_t *ret_uid); +int cg_get_owner(const char *path, uid_t *ret_uid); -int cg_set_xattr(const char *controller, const char *path, const char *name, const void *value, size_t size, int flags); -int cg_get_xattr(const char *controller, const char *path, const char *name, void *value, size_t size); -int cg_get_xattr_malloc(const char *controller, const char *path, const char *name, char **ret); +int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags); +int cg_get_xattr(const char *path, const char *name, void *value, size_t size); +int cg_get_xattr_malloc(const char *path, const char *name, char **ret); /* Returns negative on error, and 0 or 1 on success for the bool value */ -int cg_get_xattr_bool(const char *controller, const char *path, const char *name); -int cg_remove_xattr(const char *controller, const char *path, const char *name); +int cg_get_xattr_bool(const char *path, const char *name); +int cg_remove_xattr(const char *path, const char *name); int cg_install_release_agent(const char *controller, const char *agent); int cg_uninstall_release_agent(const char *controller); @@ -257,27 +267,28 @@ int cg_is_empty_recursive(const char *controller, const char *path); int cg_get_root_path(char **path); int cg_path_get_cgroupid(const char *path, uint64_t *ret); -int cg_path_get_session(const char *path, char **session); -int cg_path_get_owner_uid(const char *path, uid_t *uid); -int cg_path_get_unit(const char *path, char **unit); -int cg_path_get_unit_path(const char *path, char **unit); -int cg_path_get_user_unit(const char *path, char **unit); -int cg_path_get_machine_name(const char *path, char **machine); -int cg_path_get_slice(const char *path, char **slice); -int cg_path_get_user_slice(const char *path, char **slice); - -int cg_shift_path(const char *cgroup, const char *cached_root, const char **shifted); -int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **cgroup); - -int cg_pid_get_session(pid_t pid, char **session); -int cg_pid_get_owner_uid(pid_t pid, uid_t *uid); -int cg_pid_get_unit(pid_t pid, char **unit); -int cg_pid_get_user_unit(pid_t pid, char **unit); -int cg_pid_get_machine_name(pid_t pid, char **machine); -int cg_pid_get_slice(pid_t pid, char **slice); -int cg_pid_get_user_slice(pid_t pid, char **slice); - -int cg_path_decode_unit(const char *cgroup, char **unit); +int cg_path_get_session(const char *path, char **ret_session); +int cg_path_get_owner_uid(const char *path, uid_t *ret_uid); +int cg_path_get_unit(const char *path, char **ret_unit); +int cg_path_get_unit_path(const char *path, char **ret_unit); +int cg_path_get_user_unit(const char *path, char **ret_unit); +int cg_path_get_machine_name(const char *path, char **ret_machine); +int cg_path_get_slice(const char *path, char **ret_slice); +int cg_path_get_user_slice(const char *path, char **ret_slice); + +int cg_shift_path(const char *cgroup, const char *cached_root, const char **ret_shifted); +int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **ret_cgroup); + +int cg_pid_get_session(pid_t pid, char **ret_session); +int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid); +int cg_pid_get_unit(pid_t pid, char **ret_unit); +int cg_pidref_get_unit(const PidRef *pidref, char **ret); +int cg_pid_get_user_unit(pid_t pid, char **ret_unit); +int cg_pid_get_machine_name(pid_t pid, char **ret_machine); +int cg_pid_get_slice(pid_t pid, char **ret_slice); +int cg_pid_get_user_slice(pid_t pid, char **ret_slice); + +int cg_path_decode_unit(const char *cgroup, char **ret_unit); bool cg_needs_escape(const char *p); int cg_escape(const char *p, char **ret); diff --git a/src/libnm-systemd-shared/src/basic/constants.h b/src/libnm-systemd-shared/src/basic/constants.h index 3f96786da9..6bb5f3c281 100644 --- a/src/libnm-systemd-shared/src/basic/constants.h +++ b/src/libnm-systemd-shared/src/basic/constants.h @@ -59,22 +59,13 @@ #define NOTIFY_FD_MAX 768 #define NOTIFY_BUFFER_MAX PIPE_BUF -#if HAVE_SPLIT_USR -# define _CONF_PATHS_SPLIT_USR_NULSTR(n) "/lib/" n "\0" -# define _CONF_PATHS_SPLIT_USR(n) , "/lib/" n -#else -# define _CONF_PATHS_SPLIT_USR_NULSTR(n) -# define _CONF_PATHS_SPLIT_USR(n) -#endif - /* Return a nulstr for a standard cascade of configuration paths, suitable to pass to * conf_files_list_nulstr() to implement drop-in directories for extending configuration files. */ #define CONF_PATHS_NULSTR(n) \ "/etc/" n "\0" \ "/run/" n "\0" \ "/usr/local/lib/" n "\0" \ - "/usr/lib/" n "\0" \ - _CONF_PATHS_SPLIT_USR_NULSTR(n) + "/usr/lib/" n "\0" #define CONF_PATHS_USR(n) \ "/etc/" n, \ @@ -83,8 +74,7 @@ "/usr/lib/" n #define CONF_PATHS(n) \ - CONF_PATHS_USR(n) \ - _CONF_PATHS_SPLIT_USR(n) + CONF_PATHS_USR(n) #define CONF_PATHS_USR_STRV(n) \ STRV_MAKE(CONF_PATHS_USR(n)) @@ -99,14 +89,9 @@ * in containers so that our children inherit that. */ #define DEFAULT_RLIMIT_MEMLOCK (1024ULL*1024ULL*8ULL) -#define PLYMOUTH_SOCKET { \ - .un.sun_family = AF_UNIX, \ - .un.sun_path = "\0/org/freedesktop/plymouthd", \ - } - /* Path where PID1 listens for varlink subscriptions from systemd-oomd to notify of changes in ManagedOOM settings. */ -#define VARLINK_ADDR_PATH_MANAGED_OOM_SYSTEM "/run/systemd/io.system.ManagedOOM" +#define VARLINK_ADDR_PATH_MANAGED_OOM_SYSTEM "/run/systemd/io.systemd.ManagedOOM" /* Path where systemd-oomd listens for varlink connections from user managers to report changes in ManagedOOM settings. */ -#define VARLINK_ADDR_PATH_MANAGED_OOM_USER "/run/systemd/oom/io.system.ManagedOOM" +#define VARLINK_ADDR_PATH_MANAGED_OOM_USER "/run/systemd/oom/io.systemd.ManagedOOM" #define KERNEL_BASELINE_VERSION "4.15" diff --git a/src/libnm-systemd-shared/src/basic/env-file.c b/src/libnm-systemd-shared/src/basic/env-file.c index db270bedce..75b2febf7d 100644 --- a/src/libnm-systemd-shared/src/basic/env-file.c +++ b/src/libnm-systemd-shared/src/basic/env-file.c @@ -127,7 +127,7 @@ static int parse_env_file_internal( state = VALUE; if (!GREEDY_REALLOC(value, n_value+2)) - return -ENOMEM; + return -ENOMEM; value[n_value++] = c; } @@ -245,7 +245,13 @@ static int parse_env_file_internal( break; case COMMENT_ESCAPE: - state = COMMENT; + log_debug("The line which doesn't begin with \";\" or \"#\", but follows a comment" \ + " line trailing with escape is now treated as a non comment line since v254."); + if (strchr(NEWLINE, c)) { + state = PRE_KEY; + line++; + } else + state = COMMENT; break; } } @@ -522,6 +528,7 @@ static int merge_env_file_push( char ***env = ASSERT_PTR(userdata); char *expanded_value; + int r; assert(key); @@ -536,12 +543,12 @@ static int merge_env_file_push( return 0; } - expanded_value = replace_env(value, *env, - REPLACE_ENV_USE_ENVIRONMENT| - REPLACE_ENV_ALLOW_BRACELESS| - REPLACE_ENV_ALLOW_EXTENDED); - if (!expanded_value) - return -ENOMEM; + r = replace_env(value, + *env, + REPLACE_ENV_USE_ENVIRONMENT|REPLACE_ENV_ALLOW_BRACELESS|REPLACE_ENV_ALLOW_EXTENDED, + &expanded_value); + if (r < 0) + return log_error_errno(r, "%s:%u: Failed to expand variable '%s': %m", strna(filename), line, value); free_and_replace(value, expanded_value); @@ -599,7 +606,7 @@ static void write_env_var(FILE *f, const char *v) { fputc_unlocked('\n', f); } -int write_env_file_at(int dir_fd, const char *fname, char **l) { +int write_env_file(int dir_fd, const char *fname, char **headers, char **l) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *p = NULL; int r; @@ -613,6 +620,12 @@ int write_env_file_at(int dir_fd, const char *fname, char **l) { (void) fchmod_umask(fileno(f), 0644); + STRV_FOREACH(i, headers) { + assert(isempty(*i) || startswith(*i, "#")); + fputs_unlocked(*i, f); + fputc_unlocked('\n', f); + } + STRV_FOREACH(i, l) write_env_var(f, *i); @@ -627,4 +640,12 @@ int write_env_file_at(int dir_fd, const char *fname, char **l) { (void) unlinkat(dir_fd, p, 0); return r; } + +int write_vconsole_conf(int dir_fd, const char *fname, char **l) { + char **headers = STRV_MAKE( + "# Written by systemd-localed(8) or systemd-firstboot(1), read by systemd-localed", + "# and systemd-vconsole-setup(8). Use localectl(1) to update this file."); + + return write_env_file(dir_fd, fname, headers, l); +} #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/env-file.h b/src/libnm-systemd-shared/src/basic/env-file.h index 2465eeddf4..37db30765b 100644 --- a/src/libnm-systemd-shared/src/basic/env-file.h +++ b/src/libnm-systemd-shared/src/basic/env-file.h @@ -19,7 +19,6 @@ int load_env_file_pairs_fd(int fd, const char *fname, char ***ret); int merge_env_file(char ***env, FILE *f, const char *fname); -int write_env_file_at(int dir_fd, const char *fname, char **l); -static inline int write_env_file(const char *fname, char **l) { - return write_env_file_at(AT_FDCWD, fname, l); -} +int write_env_file(int dir_fd, const char *fname, char **headers, char **l); + +int write_vconsole_conf(int dir_fd, const char *fname, char **l); diff --git a/src/libnm-systemd-shared/src/basic/env-util.c b/src/libnm-systemd-shared/src/basic/env-util.c index fa2753bccb..c12caa2e55 100644 --- a/src/libnm-systemd-shared/src/basic/env-util.c +++ b/src/libnm-systemd-shared/src/basic/env-util.c @@ -29,20 +29,21 @@ "_" static bool env_name_is_valid_n(const char *e, size_t n) { - if (!e) - return false; + + if (n == SIZE_MAX) + n = strlen_ptr(e); if (n <= 0) return false; + assert(e); + if (ascii_isdigit(e[0])) return false; - /* POSIX says the overall size of the environment block cannot - * be > ARG_MAX, an individual assignment hence cannot be - * either. Discounting the equal sign and trailing NUL this - * hence leaves ARG_MAX-2 as longest possible variable - * name. */ + /* POSIX says the overall size of the environment block cannot be > ARG_MAX, an individual assignment + * hence cannot be either. Discounting the equal sign and trailing NUL this hence leaves ARG_MAX-2 as + * longest possible variable name. */ if (n > (size_t) sysconf(_SC_ARG_MAX) - 2) return false; @@ -246,9 +247,9 @@ static bool env_match(const char *t, const char *pattern) { return true; if (!strchr(pattern, '=')) { - size_t l = strlen(pattern); + t = startswith(t, pattern); - return strneq(t, pattern, l) && t[l] == '='; + return t && *t == '='; } return false; @@ -460,6 +461,35 @@ int strv_env_assign(char ***l, const char *key, const char *value) { return strv_env_replace_consume(l, p); } +int strv_env_assignf(char ***l, const char *key, const char *valuef, ...) { + int r; + + assert(l); + assert(key); + + if (!env_name_is_valid(key)) + return -EINVAL; + + if (!valuef) { + strv_env_unset(*l, key); + return 0; + } + + _cleanup_free_ char *value = NULL; + va_list ap; + va_start(ap, valuef); + r = vasprintf(&value, valuef, ap); + va_end(ap); + if (r < 0) + return -ENOMEM; + + char *p = strjoin(key, "=", value); + if (!p) + return -ENOMEM; + + return strv_env_replace_consume(l, p); +} + int _strv_env_assign_many(char ***l, ...) { va_list ap; int r; @@ -502,32 +532,31 @@ int _strv_env_assign_many(char ***l, ...) { return 0; } -char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) { +char* strv_env_get_n(char * const *l, const char *name, size_t k, ReplaceEnvFlags flags) { assert(name); + if (k == SIZE_MAX) + k = strlen(name); if (k <= 0) return NULL; STRV_FOREACH_BACKWARDS(i, l) - if (strneq(*i, name, k) && - (*i)[k] == '=') - return *i + k + 1; + if (strneq(*i, name, k) && (*i)[k] == '=') + return (char*) *i + k + 1; if (flags & REPLACE_ENV_USE_ENVIRONMENT) { const char *t; + /* Safety check that the name is not overly long, before we do a stack allocation */ + if (k > (size_t) sysconf(_SC_ARG_MAX) - 2) + return NULL; + t = strndupa_safe(name, k); return getenv(t); }; return NULL; } - -char *strv_env_get(char **l, const char *name) { - assert(name); - - return strv_env_get_n(l, name, strlen(name), 0); -} #endif /* NM_IGNORED */ char *strv_env_pairs_get(char **l, const char *name) { @@ -578,7 +607,61 @@ char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const cha return e; } -char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { +static int strv_extend_with_length(char ***l, const char *s, size_t n) { + char *c; + + c = strndup(s, n); + if (!c) + return -ENOMEM; + + return strv_consume(l, c); +} + +static int strv_env_get_n_validated( + char **env, + const char *name, + size_t l, + ReplaceEnvFlags flags, + char **ret, /* points into the env block! do not free! */ + char ***unset_variables, /* updated in place */ + char ***bad_variables) { /* ditto */ + + char *e; + int r; + + assert(l == 0 || name); + assert(ret); + + if (env_name_is_valid_n(name, l)) { + e = strv_env_get_n(env, name, l, flags); + if (!e && unset_variables) { + r = strv_extend_with_length(unset_variables, name, l); + if (r < 0) + return r; + } + } else { + e = NULL; /* Resolve invalid variable names the same way as unset ones */ + + if (bad_variables) { + r = strv_extend_with_length(bad_variables, name, l); + if (r < 0) + return r; + } + } + + *ret = e; + return !!e; +} + +int replace_env_full( + const char *format, + size_t n, + char **env, + ReplaceEnvFlags flags, + char **ret, + char ***ret_unset_variables, + char ***ret_bad_variables) { + enum { WORD, CURLY, @@ -589,15 +672,22 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { ALTERNATE_VALUE, } state = WORD; + _cleanup_strv_free_ char **unset_variables = NULL, **bad_variables = NULL; const char *e, *word = format, *test_value = NULL; /* test_value is initialized to appease gcc */ - char *k; _cleanup_free_ char *s = NULL; + char ***pu, ***pb, *k; size_t i, len = 0; /* len is initialized to appease gcc */ - int nest = 0; + int nest = 0, r; assert(format); - for (e = format, i = 0; *e && i < n; e ++, i ++) + if (n == SIZE_MAX) + n = strlen(format); + + pu = ret_unset_variables ? &unset_variables : NULL; + pb = ret_bad_variables ? &bad_variables : NULL; + + for (e = format, i = 0; *e && i < n; e++, i++) switch (state) { case WORD: @@ -609,27 +699,28 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { if (*e == '{') { k = strnappend(s, word, e-word-1); if (!k) - return NULL; + return -ENOMEM; free_and_replace(s, k); word = e-1; state = VARIABLE; nest++; + } else if (*e == '$') { k = strnappend(s, word, e-word); if (!k) - return NULL; + return -ENOMEM; free_and_replace(s, k); word = e+1; state = WORD; - } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) { + } else if (FLAGS_SET(flags, REPLACE_ENV_ALLOW_BRACELESS) && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) { k = strnappend(s, word, e-word-1); if (!k) - return NULL; + return -ENOMEM; free_and_replace(s, k); @@ -642,12 +733,14 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { case VARIABLE: if (*e == '}') { - const char *t; + char *t; - t = strv_env_get_n(env, word+2, e-word-2, flags); + r = strv_env_get_n_validated(env, word+2, e-word-2, flags, &t, pu, pb); + if (r < 0) + return r; if (!strextend(&s, t)) - return NULL; + return -ENOMEM; word = e+1; state = WORD; @@ -689,18 +782,37 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { nest--; if (nest == 0) { - const char *t; + _cleanup_strv_free_ char **u = NULL, **b = NULL; _cleanup_free_ char *v = NULL; + char *t = NULL; + + r = strv_env_get_n_validated(env, word+2, len, flags, &t, pu, pb); + if (r < 0) + return r; + + if (t && state == ALTERNATE_VALUE) { + r = replace_env_full(test_value, e-test_value, env, flags, &v, pu ? &u : NULL, pb ? &b : NULL); + if (r < 0) + return r; + + t = v; + } else if (!t && state == DEFAULT_VALUE) { + r = replace_env_full(test_value, e-test_value, env, flags, &v, pu ? &u : NULL, pb ? &b : NULL); + if (r < 0) + return r; - t = strv_env_get_n(env, word+2, len, flags); + t = v; + } - if (t && state == ALTERNATE_VALUE) - t = v = replace_env_n(test_value, e-test_value, env, flags); - else if (!t && state == DEFAULT_VALUE) - t = v = replace_env_n(test_value, e-test_value, env, flags); + r = strv_extend_strv(&unset_variables, u, /* filter_duplicates= */ true); + if (r < 0) + return r; + r = strv_extend_strv(&bad_variables, b, /* filter_duplicates= */ true); + if (r < 0) + return r; if (!strextend(&s, t)) - return NULL; + return -ENOMEM; word = e+1; state = WORD; @@ -711,12 +823,14 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { assert(flags & REPLACE_ENV_ALLOW_BRACELESS); if (!strchr(VALID_BASH_ENV_NAME_CHARS, *e)) { - const char *t; + char *t = NULL; - t = strv_env_get_n(env, word+1, e-word-1, flags); + r = strv_env_get_n_validated(env, word+1, e-word-1, flags, &t, &unset_variables, &bad_variables); + if (r < 0) + return r; if (!strextend(&s, t)) - return NULL; + return -ENOMEM; word = e--; i--; @@ -726,58 +840,83 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { } if (state == VARIABLE_RAW) { - const char *t; + char *t; assert(flags & REPLACE_ENV_ALLOW_BRACELESS); - t = strv_env_get_n(env, word+1, e-word-1, flags); - return strjoin(s, t); - } else - return strnappend(s, word, e-word); + r = strv_env_get_n_validated(env, word+1, e-word-1, flags, &t, &unset_variables, &bad_variables); + if (r < 0) + return r; + + if (!strextend(&s, t)) + return -ENOMEM; + + } else if (!strextendn(&s, word, e-word)) + return -ENOMEM; + + if (ret_unset_variables) + *ret_unset_variables = TAKE_PTR(unset_variables); + if (ret_bad_variables) + *ret_bad_variables = TAKE_PTR(bad_variables); + + if (ret) + *ret = TAKE_PTR(s); + + return 0; } -char **replace_env_argv(char **argv, char **env) { - _cleanup_strv_free_ char **ret = NULL; +int replace_env_argv( + char **argv, + char **env, + char ***ret, + char ***ret_unset_variables, + char ***ret_bad_variables) { + + _cleanup_strv_free_ char **n = NULL, **unset_variables = NULL, **bad_variables = NULL; size_t k = 0, l = 0; + int r; l = strv_length(argv); - ret = new(char*, l+1); - if (!ret) - return NULL; + n = new(char*, l+1); + if (!n) + return -ENOMEM; STRV_FOREACH(i, argv) { + const char *word = *i; /* If $FOO appears as single word, replace it by the split up variable */ - if ((*i)[0] == '$' && !IN_SET((*i)[1], '{', '$')) { - char *e; - char **w; + if (word[0] == '$' && !IN_SET(word[1], '{', '$')) { _cleanup_strv_free_ char **m = NULL; + const char *name = word + 1; + char *e, **w; size_t q; - e = strv_env_get(env, *i+1); - if (e) { - int r; - - r = strv_split_full(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE); - if (r < 0) { - ret[k] = NULL; - return NULL; - } - } + if (env_name_is_valid(name)) { + e = strv_env_get(env, name); + if (e) + r = strv_split_full(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE); + else if (ret_unset_variables) + r = strv_extend(&unset_variables, name); + else + r = 0; + } else if (ret_bad_variables) + r = strv_extend(&bad_variables, name); + else + r = 0; + if (r < 0) + return r; q = strv_length(m); l = l + q - 1; - w = reallocarray(ret, l + 1, sizeof(char *)); - if (!w) { - ret[k] = NULL; - return NULL; - } + w = reallocarray(n, l + 1, sizeof(char*)); + if (!w) + return -ENOMEM; - ret = w; + n = w; if (m) { - memcpy(ret + k, m, q * sizeof(char*)); + memcpy(n + k, m, (q + 1) * sizeof(char*)); m = mfree(m); } @@ -785,15 +924,41 @@ char **replace_env_argv(char **argv, char **env) { continue; } + _cleanup_strv_free_ char **u = NULL, **b = NULL; + /* If ${FOO} appears as part of a word, replace it by the variable as-is */ - ret[k] = replace_env(*i, env, 0); - if (!ret[k]) - return NULL; - k++; + r = replace_env_full( + word, + /* length= */ SIZE_MAX, + env, + /* flags= */ 0, + n + k, + ret_unset_variables ? &u : NULL, + ret_bad_variables ? &b : NULL); + if (r < 0) + return r; + n[++k] = NULL; + + r = strv_extend_strv(&unset_variables, u, /* filter_duplicates= */ true); + if (r < 0) + return r; + + r = strv_extend_strv(&bad_variables, b, /*filter_duplicates= */ true); + if (r < 0) + return r; + } + + if (ret_unset_variables) { + strv_uniq(strv_sort(unset_variables)); + *ret_unset_variables = TAKE_PTR(unset_variables); + } + if (ret_bad_variables) { + strv_uniq(strv_sort(bad_variables)); + *ret_bad_variables = TAKE_PTR(bad_variables); } - ret[k] = NULL; - return TAKE_PTR(ret); + *ret = TAKE_PTR(n); + return 0; } #endif /* NM_IGNORED */ @@ -853,8 +1018,8 @@ int putenv_dup(const char *assignment, bool override) { } int setenv_systemd_exec_pid(bool update_only) { - char str[DECIMAL_STR_MAX(pid_t)]; const char *e; + int r; /* Update $SYSTEMD_EXEC_PID=pid except when '*' is set for the variable. */ @@ -865,10 +1030,9 @@ int setenv_systemd_exec_pid(bool update_only) { if (streq_ptr(e, "*")) return 0; - xsprintf(str, PID_FMT, getpid_cached()); - - if (setenv("SYSTEMD_EXEC_PID", str, 1) < 0) - return -errno; + r = setenvf("SYSTEMD_EXEC_PID", /* overwrite= */ 1, PID_FMT, getpid_cached()); + if (r < 0) + return r; return 1; } @@ -944,4 +1108,45 @@ int getenv_steal_erase(const char *name, char **ret) { return 1; } + +int set_full_environment(char **env) { + int r; + + clearenv(); + + STRV_FOREACH(e, env) { + _cleanup_free_ char *k = NULL, *v = NULL; + + r = split_pair(*e, "=", &k, &v); + if (r < 0) + return r; + + if (setenv(k, v, /* overwrite= */ true) < 0) + return -errno; + } + + return 0; +} + +int setenvf(const char *name, bool overwrite, const char *valuef, ...) { + _cleanup_free_ char *value = NULL; + va_list ap; + int r; + + assert(name); + + if (!valuef) + return RET_NERRNO(unsetenv(name)); + + va_start(ap, valuef); + DISABLE_WARNING_FORMAT_NONLITERAL; + r = vasprintf(&value, valuef, ap); + REENABLE_WARNING; + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return RET_NERRNO(setenv(name, value, overwrite)); +} #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/env-util.h b/src/libnm-systemd-shared/src/basic/env-util.h index b0ff5a11d1..ad127de39f 100644 --- a/src/libnm-systemd-shared/src/basic/env-util.h +++ b/src/libnm-systemd-shared/src/basic/env-util.h @@ -19,19 +19,19 @@ bool env_name_is_valid(const char *e); bool env_value_is_valid(const char *e); bool env_assignment_is_valid(const char *e); -enum { +typedef enum ReplaceEnvFlags { REPLACE_ENV_USE_ENVIRONMENT = 1 << 0, REPLACE_ENV_ALLOW_BRACELESS = 1 << 1, REPLACE_ENV_ALLOW_EXTENDED = 1 << 2, -}; +} ReplaceEnvFlags; -char *replace_env_n(const char *format, size_t n, char **env, unsigned flags); -char **replace_env_argv(char **argv, char **env); - -static inline char *replace_env(const char *format, char **env, unsigned flags) { - return replace_env_n(format, strlen(format), env, flags); +int replace_env_full(const char *format, size_t n, char **env, ReplaceEnvFlags flags, char **ret, char ***ret_unset_variables, char ***ret_bad_variables); +static inline int replace_env(const char *format, char **env, ReplaceEnvFlags flags, char **ret) { + return replace_env_full(format, SIZE_MAX, env, flags, ret, NULL, NULL); } +int replace_env_argv(char **argv, char **env, char ***ret, char ***ret_unset_variables, char ***ret_bad_variables); + bool strv_env_is_valid(char **e); #define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL) char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); @@ -49,11 +49,15 @@ int strv_env_replace_consume(char ***l, char *p); /* In place ... */ int strv_env_replace_strdup(char ***l, const char *assignment); int strv_env_replace_strdup_passthrough(char ***l, const char *assignment); int strv_env_assign(char ***l, const char *key, const char *value); +int strv_env_assignf(char ***l, const char *key, const char *valuef, ...) _printf_(3, 4); int _strv_env_assign_many(char ***l, ...) _sentinel_; #define strv_env_assign_many(l, ...) _strv_env_assign_many(l, __VA_ARGS__, NULL) -char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_; -char *strv_env_get(char **x, const char *n) _pure_; +char* strv_env_get_n(char * const *l, const char *name, size_t k, ReplaceEnvFlags flags); +static inline char* strv_env_get(char * const *x, const char *n) { + return strv_env_get_n(x, n, SIZE_MAX, 0); +} + char *strv_env_pairs_get(char **l, const char *name) _pure_; int getenv_bool(const char *p); @@ -74,3 +78,7 @@ int setenv_systemd_exec_pid(bool update_only); int getenv_path_list(const char *name, char ***ret_paths); int getenv_steal_erase(const char *name, char **ret); + +int set_full_environment(char **env); + +int setenvf(const char *name, bool overwrite, const char *valuef, ...) _printf_(3,4); diff --git a/src/libnm-systemd-shared/src/basic/errno-util.h b/src/libnm-systemd-shared/src/basic/errno-util.h index 091f99c590..27804e6382 100644 --- a/src/libnm-systemd-shared/src/basic/errno-util.h +++ b/src/libnm-systemd-shared/src/basic/errno-util.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include <inttypes.h> #include <stdlib.h> #include <string.h> @@ -73,6 +74,16 @@ static inline int RET_NERRNO(int ret) { return ret; } +/* Collect possible errors in <acc>, so that the first error can be returned. + * Returns (possibly updated) <acc>. */ +#define RET_GATHER(acc, err) \ + ({ \ + int *__a = &(acc), __e = (err); \ + if (*__a >= 0 && __e < 0) \ + *__a = __e; \ + *__a; \ + }) + static inline int errno_or_else(int fallback) { /* To be used when invoking library calls where errno handling is not defined clearly: we return * errno if it is set, and the specified error otherwise. The idea is that the caller initializes @@ -84,12 +95,23 @@ static inline int errno_or_else(int fallback) { return -abs(fallback); } +/* abs(3) says: Trying to take the absolute value of the most negative integer is not defined. */ +#define _DEFINE_ABS_WRAPPER(name) \ + static inline bool ERRNO_IS_##name(intmax_t r) { \ + if (r == INTMAX_MIN) \ + return false; \ + return ERRNO_IS_NEG_##name(-imaxabs(r)); \ + } + +assert_cc(INT_MAX <= INTMAX_MAX); + /* For send()/recv() or read()/write(). */ -static inline bool ERRNO_IS_TRANSIENT(int r) { - return IN_SET(abs(r), - EAGAIN, - EINTR); +static inline bool ERRNO_IS_NEG_TRANSIENT(intmax_t r) { + return IN_SET(r, + -EAGAIN, + -EINTR); } +_DEFINE_ABS_WRAPPER(TRANSIENT); /* Hint #1: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5. * @@ -98,79 +120,87 @@ static inline bool ERRNO_IS_TRANSIENT(int r) { * * Hint #3: When asynchronous connect() on TCP fails because the host never acknowledges a single packet, * kernel tells us that with ETIMEDOUT, see tcp(7). */ -static inline bool ERRNO_IS_DISCONNECT(int r) { - return IN_SET(abs(r), - ECONNABORTED, - ECONNREFUSED, - ECONNRESET, - EHOSTDOWN, - EHOSTUNREACH, - ENETDOWN, - ENETRESET, - ENETUNREACH, - ENONET, - ENOPROTOOPT, - ENOTCONN, - EPIPE, - EPROTO, - ESHUTDOWN, - ETIMEDOUT); +static inline bool ERRNO_IS_NEG_DISCONNECT(intmax_t r) { + return IN_SET(r, + -ECONNABORTED, + -ECONNREFUSED, + -ECONNRESET, + -EHOSTDOWN, + -EHOSTUNREACH, + -ENETDOWN, + -ENETRESET, + -ENETUNREACH, + -ENONET, + -ENOPROTOOPT, + -ENOTCONN, + -EPIPE, + -EPROTO, + -ESHUTDOWN, + -ETIMEDOUT); } +_DEFINE_ABS_WRAPPER(DISCONNECT); /* Transient errors we might get on accept() that we should ignore. As per error handling comment in * the accept(2) man page. */ -static inline bool ERRNO_IS_ACCEPT_AGAIN(int r) { - return ERRNO_IS_DISCONNECT(r) || - ERRNO_IS_TRANSIENT(r) || - abs(r) == EOPNOTSUPP; +static inline bool ERRNO_IS_NEG_ACCEPT_AGAIN(intmax_t r) { + return ERRNO_IS_NEG_DISCONNECT(r) || + ERRNO_IS_NEG_TRANSIENT(r) || + r == -EOPNOTSUPP; } +_DEFINE_ABS_WRAPPER(ACCEPT_AGAIN); /* Resource exhaustion, could be our fault or general system trouble */ -static inline bool ERRNO_IS_RESOURCE(int r) { - return IN_SET(abs(r), - EMFILE, - ENFILE, - ENOMEM); +static inline bool ERRNO_IS_NEG_RESOURCE(intmax_t r) { + return IN_SET(r, + -EMFILE, + -ENFILE, + -ENOMEM); } +_DEFINE_ABS_WRAPPER(RESOURCE); /* Seven different errors for "operation/system call/ioctl/socket feature not supported" */ -static inline bool ERRNO_IS_NOT_SUPPORTED(int r) { - return IN_SET(abs(r), - EOPNOTSUPP, - ENOTTY, - ENOSYS, - EAFNOSUPPORT, - EPFNOSUPPORT, - EPROTONOSUPPORT, - ESOCKTNOSUPPORT); +static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(intmax_t r) { + return IN_SET(r, + -EOPNOTSUPP, + -ENOTTY, + -ENOSYS, + -EAFNOSUPPORT, + -EPFNOSUPPORT, + -EPROTONOSUPPORT, + -ESOCKTNOSUPPORT); } +_DEFINE_ABS_WRAPPER(NOT_SUPPORTED); /* Two different errors for access problems */ -static inline bool ERRNO_IS_PRIVILEGE(int r) { - return IN_SET(abs(r), - EACCES, - EPERM); +static inline bool ERRNO_IS_NEG_PRIVILEGE(intmax_t r) { + return IN_SET(r, + -EACCES, + -EPERM); } +_DEFINE_ABS_WRAPPER(PRIVILEGE); /* Three different errors for "not enough disk space" */ -static inline bool ERRNO_IS_DISK_SPACE(int r) { - return IN_SET(abs(r), - ENOSPC, - EDQUOT, - EFBIG); +static inline bool ERRNO_IS_NEG_DISK_SPACE(intmax_t r) { + return IN_SET(r, + -ENOSPC, + -EDQUOT, + -EFBIG); } +_DEFINE_ABS_WRAPPER(DISK_SPACE); /* Three different errors for "this device does not quite exist" */ -static inline bool ERRNO_IS_DEVICE_ABSENT(int r) { - return IN_SET(abs(r), - ENODEV, - ENXIO, - ENOENT); +static inline bool ERRNO_IS_NEG_DEVICE_ABSENT(intmax_t r) { + return IN_SET(r, + -ENODEV, + -ENXIO, + -ENOENT); } +_DEFINE_ABS_WRAPPER(DEVICE_ABSENT); /* Quite often we want to handle cases where the backing FS doesn't support extended attributes at all and * where it simply doesn't have the requested xattr the same way */ -static inline bool ERRNO_IS_XATTR_ABSENT(int r) { - return abs(r) == ENODATA || - ERRNO_IS_NOT_SUPPORTED(r); +static inline bool ERRNO_IS_NEG_XATTR_ABSENT(intmax_t r) { + return r == -ENODATA || + ERRNO_IS_NEG_NOT_SUPPORTED(r); } +_DEFINE_ABS_WRAPPER(XATTR_ABSENT); diff --git a/src/libnm-systemd-shared/src/basic/escape.c b/src/libnm-systemd-shared/src/basic/escape.c index 6d2c1d4d66..29f8b9cd1b 100644 --- a/src/libnm-systemd-shared/src/basic/escape.c +++ b/src/libnm-systemd-shared/src/basic/escape.c @@ -184,7 +184,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, } case 'u': { - /* C++11 style 16bit unicode */ + /* C++11 style 16-bit unicode */ int a[4]; size_t i; @@ -211,7 +211,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, } case 'U': { - /* C++11 style 32bit unicode */ + /* C++11 style 32-bit unicode */ int a[8]; size_t i; @@ -474,6 +474,33 @@ char* octescape(const char *s, size_t len) { return buf; } +char* decescape(const char *s, const char *bad, size_t len) { + char *buf, *t; + + /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */ + + assert(s || len == 0); + + t = buf = new(char, len * 4 + 1); + if (!buf) + return NULL; + + for (size_t i = 0; i < len; i++) { + uint8_t u = (uint8_t) s[i]; + + if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || strchr(bad, u)) { + *(t++) = '\\'; + *(t++) = '0' + (u / 100); + *(t++) = '0' + ((u / 10) % 10); + *(t++) = '0' + (u % 10); + } else + *(t++) = u; + } + + *t = 0; + return buf; +} + static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) { assert(bad); assert(t); diff --git a/src/libnm-systemd-shared/src/basic/escape.h b/src/libnm-systemd-shared/src/basic/escape.h index 318da6f220..65caf0dbcf 100644 --- a/src/libnm-systemd-shared/src/basic/escape.h +++ b/src/libnm-systemd-shared/src/basic/escape.h @@ -65,6 +65,7 @@ static inline char* xescape(const char *s, const char *bad) { return xescape_full(s, bad, SIZE_MAX, 0); } char* octescape(const char *s, size_t len); +char* decescape(const char *s, const char *bad, size_t len); char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags); char* shell_escape(const char *s, const char *bad); diff --git a/src/libnm-systemd-shared/src/basic/ether-addr-util.c b/src/libnm-systemd-shared/src/basic/ether-addr-util.c index 7984ddf4ad..1eb2e70065 100644 --- a/src/libnm-systemd-shared/src/basic/ether-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.c @@ -61,8 +61,8 @@ void hw_addr_hash_func(const struct hw_addr_data *p, struct siphash *state) { assert(p); assert(state); - siphash24_compress(&p->length, sizeof(p->length), state); - siphash24_compress(p->bytes, p->length, state); + siphash24_compress_typesafe(p->length, state); + siphash24_compress_safe(p->bytes, p->length, state); } DEFINE_HASH_OPS(hw_addr_hash_ops, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare); @@ -108,7 +108,7 @@ int ether_addr_compare(const struct ether_addr *a, const struct ether_addr *b) { } static void ether_addr_hash_func(const struct ether_addr *p, struct siphash *state) { - siphash24_compress(p, sizeof(struct ether_addr), state); + siphash24_compress_typesafe(*p, state); } DEFINE_HASH_OPS(ether_addr_hash_ops, struct ether_addr, ether_addr_hash_func, ether_addr_compare); @@ -272,3 +272,11 @@ int parse_ether_addr(const char *s, struct ether_addr *ret) { *ret = a.ether; return 0; } + +void ether_addr_mark_random(struct ether_addr *addr) { + assert(addr); + + /* see eth_random_addr in the kernel */ + addr->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ + addr->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ +} diff --git a/src/libnm-systemd-shared/src/basic/ether-addr-util.h b/src/libnm-systemd-shared/src/basic/ether-addr-util.h index 83ed77d634..187e4ef583 100644 --- a/src/libnm-systemd-shared/src/basic/ether-addr-util.h +++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.h @@ -113,3 +113,5 @@ static inline bool ether_addr_is_global(const struct ether_addr *addr) { extern const struct hash_ops ether_addr_hash_ops; extern const struct hash_ops ether_addr_hash_ops_free; + +void ether_addr_mark_random(struct ether_addr *addr); diff --git a/src/libnm-systemd-shared/src/basic/extract-word.c b/src/libnm-systemd-shared/src/basic/extract-word.c index 6781fb5560..2910a4cb60 100644 --- a/src/libnm-systemd-shared/src/basic/extract-word.c +++ b/src/libnm-systemd-shared/src/basic/extract-word.c @@ -275,11 +275,7 @@ int extract_many_words(const char **p, const char *separators, unsigned flags, . r = extract_first_word(p, &l[c], separators, flags); if (r < 0) { - int j; - - for (j = 0; j < c; j++) - free(l[j]); - + free_many_charp(l, c); return r; } diff --git a/src/libnm-systemd-shared/src/basic/fd-util.c b/src/libnm-systemd-shared/src/basic/fd-util.c index a0e2f4eb8c..afdc05f2ab 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.c +++ b/src/libnm-systemd-shared/src/basic/fd-util.c @@ -94,11 +94,25 @@ void safe_close_pair(int p[static 2]) { p[1] = safe_close(p[1]); } -void close_many(const int fds[], size_t n_fd) { - assert(fds || n_fd <= 0); +void close_many(const int fds[], size_t n_fds) { + assert(fds || n_fds == 0); - for (size_t i = 0; i < n_fd; i++) - safe_close(fds[i]); + FOREACH_ARRAY(fd, fds, n_fds) + safe_close(*fd); +} + +void close_many_unset(int fds[], size_t n_fds) { + assert(fds || n_fds == 0); + + FOREACH_ARRAY(fd, fds, n_fds) + *fd = safe_close(*fd); +} + +void close_many_and_free(int *fds, size_t n_fds) { + assert(fds || n_fds == 0); + + close_many(fds, n_fds); + free(fds); } int fclose_nointr(FILE *f) { @@ -158,6 +172,19 @@ int fd_nonblock(int fd, bool nonblock) { return RET_NERRNO(fcntl(fd, F_SETFL, nflags)); } +int stdio_disable_nonblock(void) { + int ret = 0; + + /* stdin/stdout/stderr really should have O_NONBLOCK, which would confuse apps if left on, as + * write()s might unexpectedly fail with EAGAIN. */ + + RET_GATHER(ret, fd_nonblock(STDIN_FILENO, false)); + RET_GATHER(ret, fd_nonblock(STDOUT_FILENO, false)); + RET_GATHER(ret, fd_nonblock(STDERR_FILENO, false)); + + return ret; +} + int fd_cloexec(int fd, bool cloexec) { int flags, nflags; @@ -176,32 +203,32 @@ int fd_cloexec(int fd, bool cloexec) { #if 0 /* NM_IGNORED */ int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec) { - int ret = 0, r; + int r = 0; - assert(n_fds == 0 || fds); + assert(fds || n_fds == 0); - for (size_t i = 0; i < n_fds; i++) { - if (fds[i] < 0) /* Skip gracefully over already invalidated fds */ + FOREACH_ARRAY(fd, fds, n_fds) { + if (*fd < 0) /* Skip gracefully over already invalidated fds */ continue; - r = fd_cloexec(fds[i], cloexec); - if (r < 0 && ret >= 0) /* Continue going, but return first error */ - ret = r; - else - ret = 1; /* report if we did anything */ + RET_GATHER(r, fd_cloexec(*fd, cloexec)); + + if (r >= 0) + r = 1; /* report if we did anything */ } - return ret; + return r; } -_pure_ static bool fd_in_set(int fd, const int fdset[], size_t n_fdset) { - assert(n_fdset == 0 || fdset); +static bool fd_in_set(int fd, const int fds[], size_t n_fds) { + assert(fd >= 0); + assert(fds || n_fds == 0); - for (size_t i = 0; i < n_fdset; i++) { - if (fdset[i] < 0) + FOREACH_ARRAY(i, fds, n_fds) { + if (*i < 0) continue; - if (fdset[i] == fd) + if (*i == fd) return true; } @@ -232,7 +259,7 @@ int get_max_fd(void) { static int close_all_fds_frugal(const int except[], size_t n_except) { int max_fd, r = 0; - assert(n_except == 0 || except); + assert(except || n_except == 0); /* This is the inner fallback core of close_all_fds(). This never calls malloc() or opendir() or so * and hence is safe to be called in signal handler context. Most users should call close_all_fds(), @@ -247,8 +274,7 @@ static int close_all_fds_frugal(const int except[], size_t n_except) { * spin the CPU for a long time. */ if (max_fd > MAX_FD_LOOP_LIMIT) return log_debug_errno(SYNTHETIC_ERRNO(EPERM), - "Refusing to loop over %d potential fds.", - max_fd); + "Refusing to loop over %d potential fds.", max_fd); for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -EBADF) { int q; @@ -257,8 +283,8 @@ static int close_all_fds_frugal(const int except[], size_t n_except) { continue; q = close_nointr(fd); - if (q < 0 && q != -EBADF && r >= 0) - r = q; + if (q != -EBADF) + RET_GATHER(r, q); } return r; @@ -589,7 +615,7 @@ int move_fd(int from, int to, int cloexec) { if (fl < 0) return -errno; - cloexec = !!(fl & FD_CLOEXEC); + cloexec = FLAGS_SET(fl, FD_CLOEXEC); } r = dup3(from, to, cloexec ? O_CLOEXEC : 0); @@ -647,7 +673,7 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_ original_output_fd, original_error_fd }, null_fd = -EBADF, /* If we open /dev/null, we store the fd to it here */ - copy_fd[3] = { -EBADF, -EBADF, -EBADF }, /* This contains all fds we duplicate here + copy_fd[3] = EBADF_TRIPLET, /* This contains all fds we duplicate here * temporarily, and hence need to close at the end. */ r; bool null_readable, null_writable; @@ -745,8 +771,7 @@ finish: safe_close_above_stdio(original_error_fd); /* Close the copies we moved > 2 */ - for (int i = 0; i < 3; i++) - safe_close(copy_fd[i]); + close_many(copy_fd, 3); /* Close our null fd, if it's > 2 */ safe_close_above_stdio(null_fd); @@ -756,9 +781,10 @@ finish: #endif /* NM_IGNORED */ int fd_reopen(int fd, int flags) { - int new_fd, r; + int r; assert(fd >= 0 || fd == AT_FDCWD); + assert(!FLAGS_SET(flags, O_CREAT)); /* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to * turn O_RDWR fds into O_RDONLY fds. @@ -782,19 +808,12 @@ int fd_reopen(int fd, int flags) { * the same way as the non-O_DIRECTORY case. */ return -ELOOP; - if (FLAGS_SET(flags, O_DIRECTORY) || fd == AT_FDCWD) { + if (FLAGS_SET(flags, O_DIRECTORY) || fd == AT_FDCWD) /* If we shall reopen the fd as directory we can just go via "." and thus bypass the whole * magic /proc/ directory, and make ourselves independent of that being mounted. */ - new_fd = openat(fd, ".", flags | O_DIRECTORY); - if (new_fd < 0) - return -errno; - - return new_fd; - } + return RET_NERRNO(openat(fd, ".", flags | O_DIRECTORY)); - assert(fd >= 0); - - new_fd = open(FORMAT_PROC_FD_PATH(fd), flags); + int new_fd = open(FORMAT_PROC_FD_PATH(fd), flags); if (new_fd < 0) { if (errno != ENOENT) return -errno; @@ -811,7 +830,6 @@ int fd_reopen(int fd, int flags) { return new_fd; } -#if 0 /* NM_IGNORED */ int fd_reopen_condition( int fd, int flags, @@ -821,6 +839,7 @@ int fd_reopen_condition( int r, new_fd; assert(fd >= 0); + assert(!FLAGS_SET(flags, O_CREAT)); /* Invokes fd_reopen(fd, flags), but only if the existing F_GETFL flags don't match the specified * flags (masked by the specified mask). This is useful for converting O_PATH fds into real fds if @@ -843,6 +862,7 @@ int fd_reopen_condition( return new_fd; } +#if 0 /* NM_IGNORED */ int fd_is_opath(int fd) { int r; @@ -901,75 +921,86 @@ int fd_get_diskseq(int fd, uint64_t *ret) { } int path_is_root_at(int dir_fd, const char *path) { - STRUCT_NEW_STATX_DEFINE(st); - STRUCT_NEW_STATX_DEFINE(pst); - _cleanup_close_ int fd = -EBADF; - int r; + _cleanup_close_ int fd = -EBADF, pfd = -EBADF; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); if (!isempty(path)) { - fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); + fd = openat(dir_fd, path, O_PATH|O_DIRECTORY|O_CLOEXEC); if (fd < 0) - return -errno; + return errno == ENOTDIR ? false : -errno; dir_fd = fd; } - r = statx_fallback(dir_fd, ".", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st.sx); - if (r == -ENOTDIR) - return false; + pfd = openat(dir_fd, "..", O_PATH|O_DIRECTORY|O_CLOEXEC); + if (pfd < 0) + return errno == ENOTDIR ? false : -errno; + + /* Even if the parent directory has the same inode, the fd may not point to the root directory "/", + * and we also need to check that the mount ids are the same. Otherwise, a construct like the + * following could be used to trick us: + * + * $ mkdir /tmp/x /tmp/x/y + * $ mount --bind /tmp/x /tmp/x/y + */ + + return fds_are_same_mount(dir_fd, pfd); +} + +int fds_are_same_mount(int fd1, int fd2) { + STRUCT_NEW_STATX_DEFINE(st1); + STRUCT_NEW_STATX_DEFINE(st2); + int r; + + assert(fd1 >= 0); + assert(fd2 >= 0); + + r = statx_fallback(fd1, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st1.sx); if (r < 0) return r; - r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx); + r = statx_fallback(fd2, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st2.sx); if (r < 0) return r; /* First, compare inode. If these are different, the fd does not point to the root directory "/". */ - if (!statx_inode_same(&st.sx, &pst.sx)) + if (!statx_inode_same(&st1.sx, &st2.sx)) return false; - /* Even if the parent directory has the same inode, the fd may not point to the root directory "/", - * and we also need to check that the mount ids are the same. Otherwise, a construct like the - * following could be used to trick us: - * - * $ mkdir /tmp/x /tmp/x/y - * $ mount --bind /tmp/x /tmp/x/y - * - * Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old - * kernel is used without /proc mounted. In that case, let's assume that we do not have such spurious - * mount points in an early boot stage, and silently skip the following check. */ + /* Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old + * kernel is used. In that case, let's assume that we do not have such spurious mount points in an + * early boot stage, and silently skip the following check. */ - if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) { + if (!FLAGS_SET(st1.nsx.stx_mask, STATX_MNT_ID)) { int mntid; - r = path_get_mnt_id_at(dir_fd, "", &mntid); - if (r == -ENOSYS) + r = path_get_mnt_id_at_fallback(fd1, "", &mntid); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); - st.nsx.stx_mnt_id = mntid; - st.nsx.stx_mask |= STATX_MNT_ID; + st1.nsx.stx_mnt_id = mntid; + st1.nsx.stx_mask |= STATX_MNT_ID; } - if (!FLAGS_SET(pst.nsx.stx_mask, STATX_MNT_ID)) { + if (!FLAGS_SET(st2.nsx.stx_mask, STATX_MNT_ID)) { int mntid; - r = path_get_mnt_id_at(dir_fd, "..", &mntid); - if (r == -ENOSYS) + r = path_get_mnt_id_at_fallback(fd2, "", &mntid); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); - pst.nsx.stx_mnt_id = mntid; - pst.nsx.stx_mask |= STATX_MNT_ID; + st2.nsx.stx_mnt_id = mntid; + st2.nsx.stx_mask |= STATX_MNT_ID; } - return statx_mount_same(&st.nsx, &pst.nsx); + return statx_mount_same(&st1.nsx, &st2.nsx); } const char *accmode_to_string(int flags) { @@ -984,4 +1015,12 @@ const char *accmode_to_string(int flags) { return NULL; } } + +char *format_proc_pid_fd_path(char buf[static PROC_PID_FD_PATH_MAX], pid_t pid, int fd) { + assert(buf); + assert(fd >= 0); + assert(pid >= 0); + assert_se(snprintf_ok(buf, PROC_PID_FD_PATH_MAX, "/proc/" PID_FMT "/fd/%i", pid == 0 ? getpid_cached() : pid, fd)); + return buf; +} #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/fd-util.h b/src/libnm-systemd-shared/src/basic/fd-util.h index c870a1b899..183266513a 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.h +++ b/src/libnm-systemd-shared/src/basic/fd-util.h @@ -16,7 +16,10 @@ /* Make sure we can distinguish fd 0 and NULL */ #define FD_TO_PTR(fd) INT_TO_PTR((fd)+1) #define PTR_TO_FD(p) (PTR_TO_INT(p)-1) -#define PIPE_EBADF { -EBADF, -EBADF } + +/* Useful helpers for initializing pipe(), socketpair() or stdio fd arrays */ +#define EBADF_PAIR { -EBADF, -EBADF } +#define EBADF_TRIPLET { -EBADF, -EBADF, -EBADF } int close_nointr(int fd); int safe_close(int fd); @@ -29,7 +32,9 @@ static inline int safe_close_above_stdio(int fd) { return safe_close(fd); } -void close_many(const int fds[], size_t n_fd); +void close_many(const int fds[], size_t n_fds); +void close_many_unset(int fds[], size_t n_fds); +void close_many_and_free(int *fds, size_t n_fds); int fclose_nointr(FILE *f); FILE* safe_fclose(FILE *f); @@ -47,6 +52,11 @@ static inline void fclosep(FILE **f) { safe_fclose(*f); } +static inline void* close_fd_ptr(void *p) { + safe_close(PTR_TO_FD(p)); + return NULL; +} + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(FILE*, pclose, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL); @@ -57,6 +67,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL); #define _cleanup_close_pair_ _cleanup_(close_pairp) int fd_nonblock(int fd, bool nonblock); +int stdio_disable_nonblock(void); + int fd_cloexec(int fd, bool cloexec); int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec); @@ -102,6 +114,9 @@ int read_nr_open(void); int fd_get_diskseq(int fd, uint64_t *ret); int path_is_root_at(int dir_fd, const char *path); +static inline int path_is_root(const char *path) { + return path_is_root_at(AT_FDCWD, path); +} static inline int dir_fd_is_root(int dir_fd) { return path_is_root_at(dir_fd, NULL); } @@ -109,6 +124,8 @@ static inline int dir_fd_is_root_or_cwd(int dir_fd) { return dir_fd == AT_FDCWD ? true : path_is_root_at(dir_fd, NULL); } +int fds_are_same_mount(int fd1, int fd2); + /* The maximum length a buffer for a /proc/self/fd/<fd> path needs */ #define PROC_FD_PATH_MAX \ (STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)) @@ -123,6 +140,16 @@ static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int f #define FORMAT_PROC_FD_PATH(fd) \ format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd)) +/* The maximum length a buffer for a /proc/<pid>/fd/<fd> path needs */ +#define PROC_PID_FD_PATH_MAX \ + (STRLEN("/proc//fd/") + DECIMAL_STR_MAX(pid_t) + DECIMAL_STR_MAX(int)) + +char *format_proc_pid_fd_path(char buf[static PROC_PID_FD_PATH_MAX], pid_t pid, int fd); + +/* Kinda the same as FORMAT_PROC_FD_PATH(), but goes by PID rather than "self" symlink */ +#define FORMAT_PROC_PID_FD_PATH(pid, fd) \ + format_proc_pid_fd_path((char[PROC_PID_FD_PATH_MAX]) {}, (pid), (fd)) + const char *accmode_to_string(int flags); /* Like ASSERT_PTR, but for fds */ diff --git a/src/libnm-systemd-shared/src/basic/fileio.c b/src/libnm-systemd-shared/src/basic/fileio.c index 908a030911..7ab29816fe 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.c +++ b/src/libnm-systemd-shared/src/basic/fileio.c @@ -30,10 +30,13 @@ #include "stdio-util.h" #include "string-util.h" #include "sync-util.h" +#include "terminal-util.h" #include "tmpfile-util.h" /* The maximum size of the file we'll read in one go in read_full_file() (64M). */ #define READ_FULL_BYTES_MAX (64U*1024U*1024U - 1U) +/* Used when a size is specified for read_full_file() with READ_FULL_FILE_UNBASE64 or _UNHEX */ +#define READ_FULL_FILE_ENCODED_STRING_AMPLIFICATION_BOUNDARY 3 /* The maximum size of virtual files (i.e. procfs, sysfs, and other virtual "API" files) we'll read in one go * in read_virtual_file(). Note that this limit is different (and much lower) than the READ_FULL_BYTES_MAX @@ -200,6 +203,19 @@ int write_string_stream_ts( return 0; } +static mode_t write_string_file_flags_to_mode(WriteStringFileFlags flags) { + + /* We support three different modes, that are the ones that really make sense for text files like this: + * + * → 0600 (i.e. root-only) + * → 0444 (i.e. read-only) + * → 0644 (i.e. writable for root, readable for everyone else) + */ + + return FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : + FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0444) ? 0444 : 0644; +} + static int write_string_file_atomic_at( int dir_fd, const char *fn, @@ -225,7 +241,7 @@ static int write_string_file_atomic_at( if (r < 0) goto fail; - r = fchmod_umask(fileno(f), FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0644); + r = fchmod_umask(fileno(f), write_string_file_flags_to_mode(flags)); if (r < 0) goto fail; @@ -288,7 +304,7 @@ int write_string_file_ts_at( (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) | (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) | (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY), - (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666)); + write_string_file_flags_to_mode(flags)); if (fd < 0) { r = -errno; goto fail; @@ -576,7 +592,7 @@ int read_full_stream_full( size_t *ret_size) { _cleanup_free_ char *buf = NULL; - size_t n, n_next = 0, l; + size_t n, n_next = 0, l, expected_decoded_size = size; int fd, r; assert(f); @@ -587,6 +603,13 @@ int read_full_stream_full( if (offset != UINT64_MAX && offset > LONG_MAX) /* fseek() can only deal with "long" offsets */ return -ERANGE; + if ((flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) != 0) { + if (size <= SIZE_MAX / READ_FULL_FILE_ENCODED_STRING_AMPLIFICATION_BOUNDARY) + size *= READ_FULL_FILE_ENCODED_STRING_AMPLIFICATION_BOUNDARY; + else + size = SIZE_MAX; + } + fd = fileno(f); if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see * fmemopen()), let's optimize our buffering */ @@ -711,6 +734,11 @@ int read_full_stream_full( explicit_bzero_safe(buf, n); free_and_replace(buf, decoded); n = l = decoded_size; + + if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && l > expected_decoded_size) { + r = -E2BIG; + goto finalize; + } } if (!ret_size) { @@ -1057,7 +1085,9 @@ int fdopen_independent(int fd, const char *mode, FILE **ret) { if (mode_flags < 0) return mode_flags; - copy_fd = fd_reopen(fd, mode_flags); + /* Flags returned by fopen_mode_to_flags might contain O_CREAT, but it doesn't make sense for fd_reopen + * since we're working on an existing fd anyway. Let's drop it here to avoid triggering assertion. */ + copy_fd = fd_reopen(fd, mode_flags & ~O_CREAT); if (copy_fd < 0) return copy_fd; @@ -1069,123 +1099,171 @@ int fdopen_independent(int fd, const char *mode, FILE **ret) { return 0; } -static int search_and_fopen_internal( +static int search_and_open_internal( const char *path, - const char *mode, + int mode, /* if ret_fd is NULL this is an [FRWX]_OK mode for access(), otherwise an open mode for open() */ const char *root, char **search, - FILE **ret, + int *ret_fd, char **ret_path) { + int r; + + assert(!ret_fd || !FLAGS_SET(mode, O_CREAT)); /* We don't support O_CREAT for this */ assert(path); - assert(mode); - assert(ret); + + if (path_is_absolute(path)) { + _cleanup_close_ int fd = -EBADF; + + if (ret_fd) + /* We only specify 0777 here to appease static analyzers, it's never used since we + * don't support O_CREAT here */ + r = fd = RET_NERRNO(open(path, mode, 0777)); + else + r = RET_NERRNO(access(path, mode)); + if (r < 0) + return r; + + if (ret_path) { + r = path_simplify_alloc(path, ret_path); + if (r < 0) + return r; + } + + if (ret_fd) + *ret_fd = TAKE_FD(fd); + + return 0; + } if (!path_strv_resolve_uniq(search, root)) return -ENOMEM; STRV_FOREACH(i, search) { + _cleanup_close_ int fd = -EBADF; _cleanup_free_ char *p = NULL; - FILE *f; p = path_join(root, *i, path); if (!p) return -ENOMEM; - f = fopen(p, mode); - if (f) { + if (ret_fd) + /* as above, 0777 is static analyzer appeasement */ + r = fd = RET_NERRNO(open(p, mode, 0777)); + else + r = RET_NERRNO(access(p, F_OK)); + if (r >= 0) { if (ret_path) *ret_path = path_simplify(TAKE_PTR(p)); - *ret = f; + if (ret_fd) + *ret_fd = TAKE_FD(fd); + return 0; } - - if (errno != ENOENT) - return -errno; + if (r != -ENOENT) + return r; } return -ENOENT; } -int search_and_fopen( - const char *filename, - const char *mode, +int search_and_open( + const char *path, + int mode, const char *root, - const char **search, - FILE **ret, + char **search, + int *ret_fd, char **ret_path) { _cleanup_strv_free_ char **copy = NULL; - assert(filename); - assert(mode); - assert(ret); + assert(path); - if (path_is_absolute(filename)) { - _cleanup_fclose_ FILE *f = NULL; + copy = strv_copy((char**) search); + if (!copy) + return -ENOMEM; + + return search_and_open_internal(path, mode, root, copy, ret_fd, ret_path); +} + +static int search_and_fopen_internal( + const char *path, + const char *mode, + const char *root, + char **search, + FILE **ret_file, + char **ret_path) { + + _cleanup_free_ char *found_path = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + assert(path); + assert(mode || !ret_file); + + r = search_and_open( + path, + mode ? fopen_mode_to_flags(mode) : 0, + root, + search, + ret_file ? &fd : NULL, + ret_path ? &found_path : NULL); + if (r < 0) + return r; - f = fopen(filename, mode); + if (ret_file) { + FILE *f = take_fdopen(&fd, mode); if (!f) return -errno; - if (ret_path) { - char *p; + *ret_file = f; + } - p = strdup(filename); - if (!p) - return -ENOMEM; + if (ret_path) + *ret_path = TAKE_PTR(found_path); - *ret_path = path_simplify(p); - } + return 0; +} - *ret = TAKE_PTR(f); - return 0; - } +int search_and_fopen( + const char *path, + const char *mode, + const char *root, + const char **search, + FILE **ret_file, + char **ret_path) { + + _cleanup_strv_free_ char **copy = NULL; + + assert(path); + assert(mode || !ret_file); copy = strv_copy((char**) search); if (!copy) return -ENOMEM; - return search_and_fopen_internal(filename, mode, root, copy, ret, ret_path); + return search_and_fopen_internal(path, mode, root, copy, ret_file, ret_path); } int search_and_fopen_nulstr( - const char *filename, + const char *path, const char *mode, const char *root, const char *search, - FILE **ret, + FILE **ret_file, char **ret_path) { - _cleanup_strv_free_ char **s = NULL; - - if (path_is_absolute(filename)) { - _cleanup_fclose_ FILE *f = NULL; - - f = fopen(filename, mode); - if (!f) - return -errno; - - if (ret_path) { - char *p; - - p = strdup(filename); - if (!p) - return -ENOMEM; - - *ret_path = path_simplify(p); - } + _cleanup_strv_free_ char **l = NULL; - *ret = TAKE_PTR(f); - return 0; - } + assert(path); + assert(mode || !ret_file); - s = strv_split_nulstr(search); - if (!s) + l = strv_split_nulstr(search); + if (!l) return -ENOMEM; - return search_and_fopen_internal(filename, mode, root, s, ret, ret_path); + return search_and_fopen_internal(path, mode, root, l, ret_file, ret_path); } #endif /* NM_IGNORED */ @@ -1259,33 +1337,31 @@ int read_timestamp_file(const char *fn, usec_t *ret) { } #endif /* NM_IGNORED */ -int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) { - int r; - +int fputs_with_separator(FILE *f, const char *s, const char *separator, bool *space) { assert(s); + assert(space); - /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. The *space parameter - * when specified shall initially point to a boolean variable initialized to false. It is set to true after the - * first invocation. This call is supposed to be use in loops, where a separator shall be inserted between each - * element, but not before the first one. */ + /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. + * The *space parameter when specified shall initially point to a boolean variable initialized + * to false. It is set to true after the first invocation. This call is supposed to be use in loops, + * where a separator shall be inserted between each element, but not before the first one. */ if (!f) f = stdout; - if (space) { - if (!separator) - separator = " "; + if (!separator) + separator = " "; - if (*space) { - r = fputs(separator, f); - if (r < 0) - return r; - } + if (*space) + if (fputs(separator, f) < 0) + return -EIO; - *space = true; - } + *space = true; + + if (fputs(s, f) < 0) + return -EIO; - return fputs(s, f); + return 0; } #if 0 /* NM_IGNORED */ @@ -1406,7 +1482,7 @@ int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) { * and don't call isatty() on an invalid fd */ flags |= READ_LINE_NOT_A_TTY; else - flags |= isatty(fd) ? READ_LINE_IS_A_TTY : READ_LINE_NOT_A_TTY; + flags |= isatty_safe(fd) ? READ_LINE_IS_A_TTY : READ_LINE_NOT_A_TTY; } if (FLAGS_SET(flags, READ_LINE_IS_A_TTY)) break; @@ -1437,6 +1513,36 @@ int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) { return (int) count; } +int read_stripped_line(FILE *f, size_t limit, char **ret) { + _cleanup_free_ char *s = NULL; + int r; + + assert(f); + + r = read_line(f, limit, ret ? &s : NULL); + if (r < 0) + return r; + + if (ret) { + const char *p; + + p = strstrip(s); + if (p == s) + *ret = TAKE_PTR(s); + else { + char *copy; + + copy = strdup(p); + if (!copy) + return -ENOMEM; + + *ret = copy; + } + } + + return r; +} + int safe_fgetc(FILE *f, char *ret) { int k; diff --git a/src/libnm-systemd-shared/src/basic/fileio.h b/src/libnm-systemd-shared/src/basic/fileio.h index 769bf394fd..03c3f3ff28 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.h +++ b/src/libnm-systemd-shared/src/basic/fileio.h @@ -26,7 +26,8 @@ typedef enum { WRITE_STRING_FILE_NOFOLLOW = 1 << 8, WRITE_STRING_FILE_MKDIR_0755 = 1 << 9, WRITE_STRING_FILE_MODE_0600 = 1 << 10, - WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 11, + WRITE_STRING_FILE_MODE_0444 = 1 << 11, + WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 12, /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file() @@ -129,8 +130,12 @@ static inline int fopen_unlocked(const char *path, const char *mode, FILE **ret) int fdopen_independent(int fd, const char *mode, FILE **ret); -int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **ret, char **ret_path); -int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **ret, char **ret_path); +int search_and_open(const char *path, int mode, const char *root, char **search, int *ret_fd, char **ret_path); +static inline int search_and_access(const char *path, int mode, const char *root, char**search, char **ret_path) { + return search_and_open(path, mode, root, search, NULL, ret_path); +} +int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **ret_file, char **ret_path); +int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **ret_file, char **ret_path); int fflush_and_check(FILE *f); int fflush_sync_and_check(FILE *f); @@ -138,7 +143,7 @@ int fflush_sync_and_check(FILE *f); int write_timestamp_file_atomic(const char *fn, usec_t n); int read_timestamp_file(const char *fn, usec_t *ret); -int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space); +int fputs_with_separator(FILE *f, const char *s, const char *separator, bool *space); typedef enum ReadLineFlags { READ_LINE_ONLY_NUL = 1 << 0, @@ -162,6 +167,8 @@ static inline int read_nul_string(FILE *f, size_t limit, char **ret) { return read_line_full(f, limit, READ_LINE_ONLY_NUL, ret); } +int read_stripped_line(FILE *f, size_t limit, char **ret); + int safe_fgetc(FILE *f, char *ret); int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line); diff --git a/src/libnm-systemd-shared/src/basic/fs-util.c b/src/libnm-systemd-shared/src/basic/fs-util.c index 32fd849d34..8804c0c1a1 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.c +++ b/src/libnm-systemd-shared/src/basic/fs-util.c @@ -11,6 +11,7 @@ #include <unistd.h> #include "alloc-util.h" +#include "btrfs.h" #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" @@ -292,8 +293,22 @@ int fchmod_umask(int fd, mode_t m) { int fchmod_opath(int fd, mode_t m) { /* This function operates also on fd that might have been opened with - * O_PATH. Indeed fchmodat() doesn't have the AT_EMPTY_PATH flag like - * fchownat() does. */ + * O_PATH. The tool set we have is non-intuitive: + * - fchmod(2) only operates on open files (i. e., fds with an open file description); + * - fchmodat(2) does not have a flag arg like fchownat(2) does, so no way to pass AT_EMPTY_PATH; + * + it should not be confused with the libc fchmodat(3) interface, which adds 4th flag argument, + * but does not support AT_EMPTY_PATH (only supports AT_SYMLINK_NOFOLLOW); + * - fchmodat2(2) supports all the AT_* flags, but is still very recent. + * + * We try to use fchmodat2(), and, if it is not supported, resort + * to the /proc/self/fd dance. */ + + assert(fd >= 0); + + if (fchmodat2(fd, "", m, AT_EMPTY_PATH) >= 0) + return 0; + if (!IN_SET(errno, ENOSYS, EPERM)) /* Some container managers block unknown syscalls with EPERM */ + return -errno; if (chmod(FORMAT_PROC_FD_PATH(fd), m) < 0) { if (errno != ENOENT) @@ -1108,6 +1123,16 @@ int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + /* This is like openat(), but has a few tricks up its sleeves, extending behaviour: + * + * • O_DIRECTORY|O_CREAT is supported, which causes a directory to be created, and immediately + * opened. When used with the XO_SUBVOLUME flag this will even create a btrfs subvolume. + * + * • If O_CREAT is used with XO_LABEL, any created file will be immediately relabelled. + * + * • If the path is specified NULL or empty, behaves like fd_reopen(). + */ + if (isempty(path)) { assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL)); return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW); @@ -1120,7 +1145,10 @@ int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags } if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) { - r = RET_NERRNO(mkdirat(dir_fd, path, mode)); + if (FLAGS_SET(xopen_flags, XO_SUBVOLUME)) + r = btrfs_subvol_make_fallback(dir_fd, path, mode); + else + r = RET_NERRNO(mkdirat(dir_fd, path, mode)); if (r == -EEXIST) { if (FLAGS_SET(open_flags, O_EXCL)) return -EEXIST; @@ -1183,12 +1211,11 @@ int xopenat_lock( int r; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(path); assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH)); /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with * the same error here). */ - if (FLAGS_SET(open_flags, O_DIRECTORY) && locktype != LOCK_BSD) + if (FLAGS_SET(open_flags, O_DIRECTORY) && !IN_SET(locktype, LOCK_BSD, LOCK_NONE)) return -EBADF; for (;;) { diff --git a/src/libnm-systemd-shared/src/basic/fs-util.h b/src/libnm-systemd-shared/src/basic/fs-util.h index a19836d138..1023ab73ca 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.h +++ b/src/libnm-systemd-shared/src/basic/fs-util.h @@ -133,7 +133,8 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode); int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created); typedef enum XOpenFlags { - XO_LABEL = 1 << 0, + XO_LABEL = 1 << 0, + XO_SUBVOLUME = 1 << 1, } XOpenFlags; int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode); diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.c b/src/libnm-systemd-shared/src/basic/glyph-util.c index 1ea8a645b2..1de60e105b 100644 --- a/src/libnm-systemd-shared/src/basic/glyph-util.c +++ b/src/libnm-systemd-shared/src/basic/glyph-util.c @@ -25,7 +25,7 @@ bool emoji_enabled(void) { return cached_emoji_enabled; } -const char *special_glyph(SpecialGlyph code) { +const char *special_glyph_full(SpecialGlyph code, bool force_utf) { /* A list of a number of interesting unicode glyphs we can use to decorate our output. It's probably wise to be * conservative here, and primarily stick to the glyphs defined in the eurlatgr font, so that display still @@ -54,11 +54,12 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_CROSS_MARK] = "-", [SPECIAL_GLYPH_LIGHT_SHADE] = "-", [SPECIAL_GLYPH_DARK_SHADE] = "X", + [SPECIAL_GLYPH_FULL_BLOCK] = "#", [SPECIAL_GLYPH_SIGMA] = "S", - [SPECIAL_GLYPH_ARROW_LEFT] = "<-", - [SPECIAL_GLYPH_ARROW_RIGHT] = "->", [SPECIAL_GLYPH_ARROW_UP] = "^", [SPECIAL_GLYPH_ARROW_DOWN] = "v", + [SPECIAL_GLYPH_ARROW_LEFT] = "<-", + [SPECIAL_GLYPH_ARROW_RIGHT] = "->", [SPECIAL_GLYPH_ELLIPSIS] = "...", [SPECIAL_GLYPH_EXTERNAL_LINK] = "[LNK]", [SPECIAL_GLYPH_ECSTATIC_SMILEY] = ":-]", @@ -73,6 +74,7 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_RECYCLING] = "~", [SPECIAL_GLYPH_DOWNLOAD] = "\\", [SPECIAL_GLYPH_SPARKLES] = "*", + [SPECIAL_GLYPH_LOW_BATTERY] = "!", [SPECIAL_GLYPH_WARNING_SIGN] = "!", }, @@ -98,6 +100,7 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_CROSS_MARK] = u8"✗", /* actually called: BALLOT X */ [SPECIAL_GLYPH_LIGHT_SHADE] = u8"░", [SPECIAL_GLYPH_DARK_SHADE] = u8"▒", + [SPECIAL_GLYPH_FULL_BLOCK] = u8"█", [SPECIAL_GLYPH_SIGMA] = u8"Σ", [SPECIAL_GLYPH_ARROW_UP] = u8"↑", /* actually called: UPWARDS ARROW */ [SPECIAL_GLYPH_ARROW_DOWN] = u8"↓", /* actually called: DOWNWARDS ARROW */ @@ -131,7 +134,10 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_RECYCLING] = u8"♻️", /* actually called: UNIVERSAL RECYCLNG SYMBOL */ [SPECIAL_GLYPH_DOWNLOAD] = u8"⤵️", /* actually called: RIGHT ARROW CURVING DOWN */ [SPECIAL_GLYPH_SPARKLES] = u8"✨", + [SPECIAL_GLYPH_LOW_BATTERY] = u8"🪫", [SPECIAL_GLYPH_WARNING_SIGN] = u8"⚠️", + [SPECIAL_GLYPH_COMPUTER_DISK] = u8"💽", + [SPECIAL_GLYPH_WORLD] = u8"🌍", }, }; @@ -139,5 +145,5 @@ const char *special_glyph(SpecialGlyph code) { return NULL; assert(code < _SPECIAL_GLYPH_MAX); - return draw_table[code >= _SPECIAL_GLYPH_FIRST_EMOJI ? emoji_enabled() : is_locale_utf8()][code]; + return draw_table[force_utf || (code >= _SPECIAL_GLYPH_FIRST_EMOJI ? emoji_enabled() : is_locale_utf8())][code]; } diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.h b/src/libnm-systemd-shared/src/basic/glyph-util.h index b64639622e..a7709976e1 100644 --- a/src/libnm-systemd-shared/src/basic/glyph-util.h +++ b/src/libnm-systemd-shared/src/basic/glyph-util.h @@ -22,14 +22,15 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_MU, SPECIAL_GLYPH_CHECK_MARK, SPECIAL_GLYPH_CROSS_MARK, - SPECIAL_GLYPH_ARROW_LEFT, - SPECIAL_GLYPH_ARROW_RIGHT, - SPECIAL_GLYPH_ARROW_UP, - SPECIAL_GLYPH_ARROW_DOWN, - SPECIAL_GLYPH_ELLIPSIS, SPECIAL_GLYPH_LIGHT_SHADE, SPECIAL_GLYPH_DARK_SHADE, + SPECIAL_GLYPH_FULL_BLOCK, SPECIAL_GLYPH_SIGMA, + SPECIAL_GLYPH_ARROW_UP, + SPECIAL_GLYPH_ARROW_DOWN, + SPECIAL_GLYPH_ARROW_LEFT, + SPECIAL_GLYPH_ARROW_RIGHT, + SPECIAL_GLYPH_ELLIPSIS, SPECIAL_GLYPH_EXTERNAL_LINK, _SPECIAL_GLYPH_FIRST_EMOJI, SPECIAL_GLYPH_ECSTATIC_SMILEY = _SPECIAL_GLYPH_FIRST_EMOJI, @@ -44,15 +45,22 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_RECYCLING, SPECIAL_GLYPH_DOWNLOAD, SPECIAL_GLYPH_SPARKLES, + SPECIAL_GLYPH_LOW_BATTERY, SPECIAL_GLYPH_WARNING_SIGN, + SPECIAL_GLYPH_COMPUTER_DISK, + SPECIAL_GLYPH_WORLD, _SPECIAL_GLYPH_MAX, _SPECIAL_GLYPH_INVALID = -EINVAL, } SpecialGlyph; -const char *special_glyph(SpecialGlyph code) _const_; - bool emoji_enabled(void); +const char *special_glyph_full(SpecialGlyph code, bool force_utf) _const_; + +static inline const char *special_glyph(SpecialGlyph code) { + return special_glyph_full(code, false); +} + static inline const char *special_glyph_check_mark(bool b) { return b ? special_glyph(SPECIAL_GLYPH_CHECK_MARK) : special_glyph(SPECIAL_GLYPH_CROSS_MARK); } diff --git a/src/libnm-systemd-shared/src/basic/hash-funcs.c b/src/libnm-systemd-shared/src/basic/hash-funcs.c index eed30f0944..6dcc2f36d9 100644 --- a/src/libnm-systemd-shared/src/basic/hash-funcs.c +++ b/src/libnm-systemd-shared/src/basic/hash-funcs.c @@ -36,7 +36,7 @@ void path_hash_func(const char *q, struct siphash *state) { /* if path is absolute, add one "/" to the hash. */ if (path_is_absolute(q)) - siphash24_compress("/", 1, state); + siphash24_compress_byte('/', state); for (;;) { const char *e; @@ -71,7 +71,7 @@ DEFINE_HASH_OPS_FULL(path_hash_ops_free_free, #endif /* NM_IGNORED */ void trivial_hash_func(const void *p, struct siphash *state) { - siphash24_compress(&p, sizeof(p), state); + siphash24_compress_typesafe(p, state); } int trivial_compare_func(const void *a, const void *b) { @@ -97,7 +97,7 @@ const struct hash_ops trivial_hash_ops_free_free = { }; void uint64_hash_func(const uint64_t *p, struct siphash *state) { - siphash24_compress(p, sizeof(uint64_t), state); + siphash24_compress_typesafe(*p, state); } int uint64_compare_func(const uint64_t *a, const uint64_t *b) { @@ -109,7 +109,7 @@ DEFINE_HASH_OPS(uint64_hash_ops, uint64_t, uint64_hash_func, uint64_compare_func #if 0 /* NM_IGNORED */ #if SIZEOF_DEV_T != 8 void devt_hash_func(const dev_t *p, struct siphash *state) { - siphash24_compress(p, sizeof(dev_t), state); + siphash24_compress_typesafe(*p, state); } #endif diff --git a/src/libnm-systemd-shared/src/basic/hash-funcs.h b/src/libnm-systemd-shared/src/basic/hash-funcs.h index be64289252..3804e94d98 100644 --- a/src/libnm-systemd-shared/src/basic/hash-funcs.h +++ b/src/libnm-systemd-shared/src/basic/hash-funcs.h @@ -93,14 +93,14 @@ extern const struct hash_ops trivial_hash_ops; extern const struct hash_ops trivial_hash_ops_free; extern const struct hash_ops trivial_hash_ops_free_free; -/* 32bit values we can always just embed in the pointer itself, but in order to support 32bit archs we need store 64bit +/* 32-bit values we can always just embed in the pointer itself, but in order to support 32-bit archs we need store 64-bit * values indirectly, since they don't fit in a pointer. */ void uint64_hash_func(const uint64_t *p, struct siphash *state); int uint64_compare_func(const uint64_t *a, const uint64_t *b) _pure_; extern const struct hash_ops uint64_hash_ops; -/* On some archs dev_t is 32bit, and on others 64bit. And sometimes it's 64bit on 32bit archs, and sometimes 32bit on - * 64bit archs. Yuck! */ +/* On some archs dev_t is 32-bit, and on others 64-bit. And sometimes it's 64-bit on 32-bit archs, and sometimes 32-bit on + * 64-bit archs. Yuck! */ #if SIZEOF_DEV_T != 8 void devt_hash_func(const dev_t *p, struct siphash *state); #else diff --git a/src/libnm-systemd-shared/src/basic/hashmap.c b/src/libnm-systemd-shared/src/basic/hashmap.c index 356200cfcc..28d2efa0e2 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.c +++ b/src/libnm-systemd-shared/src/basic/hashmap.c @@ -23,6 +23,7 @@ #include "random-util.h" #include "set.h" #include "siphash24.h" +#include "sort-util.h" #include "string-util.h" #include "strv.h" @@ -176,9 +177,9 @@ struct _packed_ indirect_storage { }; struct direct_storage { - /* This gives us 39 bytes on 64bit, or 35 bytes on 32bit. - * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64bit, - * or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32bit. */ + /* This gives us 39 bytes on 64-bit, or 35 bytes on 32-bit. + * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64-bit, + * or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32-bit. */ uint8_t storage[sizeof(struct indirect_storage)]; }; @@ -2112,3 +2113,54 @@ bool set_fnmatch(Set *include_patterns, Set *exclude_patterns, const char *needl return set_fnmatch_one(include_patterns, needle); } + +static int hashmap_entry_compare( + struct hashmap_base_entry * const *a, + struct hashmap_base_entry * const *b, + compare_func_t compare) { + + assert(a && *a); + assert(b && *b); + assert(compare); + + return compare((*a)->key, (*b)->key); +} + +int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { + _cleanup_free_ struct hashmap_base_entry **entries = NULL; + Iterator iter; + unsigned idx; + size_t n = 0; + + assert(ret); + + if (_hashmap_size(h) == 0) { + *ret = NULL; + if (ret_n) + *ret_n = 0; + return 0; + } + + /* We append one more element than needed so that the resulting array can be used as a strv. We + * don't count this entry in the returned size. */ + entries = new(struct hashmap_base_entry*, _hashmap_size(h) + 1); + if (!entries) + return -ENOMEM; + + HASHMAP_FOREACH_IDX(idx, h, iter) + entries[n++] = bucket_at(h, idx); + + assert(n == _hashmap_size(h)); + entries[n] = NULL; + + typesafe_qsort_r(entries, n, hashmap_entry_compare, h->hash_ops->compare); + + /* Reuse the array. */ + FOREACH_ARRAY(e, entries, n) + *e = entry_value(h, *e); + + *ret = (void**) TAKE_PTR(entries); + if (ret_n) + *ret_n = n; + return 0; +} diff --git a/src/libnm-systemd-shared/src/basic/hashmap.h b/src/libnm-systemd-shared/src/basic/hashmap.h index 68d9b81cf2..233f1d7a1e 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.h +++ b/src/libnm-systemd-shared/src/basic/hashmap.h @@ -398,12 +398,28 @@ static inline char** ordered_hashmap_get_strv(OrderedHashmap *h) { return _hashmap_get_strv(HASHMAP_BASE(h)); } +int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n); +static inline int hashmap_dump_sorted(Hashmap *h, void ***ret, size_t *ret_n) { + return _hashmap_dump_sorted(HASHMAP_BASE(h), ret, ret_n); +} +static inline int ordered_hashmap_dump_sorted(OrderedHashmap *h, void ***ret, size_t *ret_n) { + return _hashmap_dump_sorted(HASHMAP_BASE(h), ret, ret_n); +} +static inline int set_dump_sorted(Set *h, void ***ret, size_t *ret_n) { + return _hashmap_dump_sorted(HASHMAP_BASE(h), ret, ret_n); +} + /* * Hashmaps are iterated in unpredictable order. * OrderedHashmaps are an exception to this. They are iterated in the order * the entries were inserted. * It is safe to remove the current entry. */ +#define _HASHMAP_BASE_FOREACH(e, h, i) \ + for (Iterator i = ITERATOR_FIRST; _hashmap_iterate((h), &i, (void**)&(e), NULL); ) +#define HASHMAP_BASE_FOREACH(e, h) \ + _HASHMAP_BASE_FOREACH(e, h, UNIQ_T(i, UNIQ)) + #define _HASHMAP_FOREACH(e, h, i) \ for (Iterator i = ITERATOR_FIRST; hashmap_iterate((h), &i, (void**)&(e), NULL); ) #define HASHMAP_FOREACH(e, h) \ @@ -414,6 +430,11 @@ static inline char** ordered_hashmap_get_strv(OrderedHashmap *h) { #define ORDERED_HASHMAP_FOREACH(e, h) \ _ORDERED_HASHMAP_FOREACH(e, h, UNIQ_T(i, UNIQ)) +#define _HASHMAP_BASE_FOREACH_KEY(e, k, h, i) \ + for (Iterator i = ITERATOR_FIRST; _hashmap_iterate((h), &i, (void**)&(e), (const void**) &(k)); ) +#define HASHMAP_BASE_FOREACH_KEY(e, k, h) \ + _HASHMAP_BASE_FOREACH_KEY(e, k, h, UNIQ_T(i, UNIQ)) + #define _HASHMAP_FOREACH_KEY(e, k, h, i) \ for (Iterator i = ITERATOR_FIRST; hashmap_iterate((h), &i, (void**)&(e), (const void**) &(k)); ) #define HASHMAP_FOREACH_KEY(e, k, h) \ diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.c b/src/libnm-systemd-shared/src/basic/hexdecoct.c index d41d2ea079..41228520a5 100644 --- a/src/libnm-systemd-shared/src/basic/hexdecoct.c +++ b/src/libnm-systemd-shared/src/basic/hexdecoct.c @@ -116,7 +116,7 @@ int unhexmem_full( const char *p, size_t l, bool secure, - void **ret, + void **ret_data, size_t *ret_len) { _cleanup_free_ uint8_t *buf = NULL; @@ -157,8 +157,8 @@ int unhexmem_full( if (ret_len) *ret_len = (size_t) (z - buf); - if (ret) - *ret = TAKE_PTR(buf); + if (ret_data) + *ret_data = TAKE_PTR(buf); return 0; } @@ -557,12 +557,12 @@ int unbase64char(char c) { offset += '9' - '0' + 1; - if (c == '+') + if (IN_SET(c, '+', '-')) /* Support both the regular and the URL safe character set (see above) */ return offset; offset++; - if (c == '/') + if (IN_SET(c, '/', '_')) /* ditto */ return offset; return -EINVAL; @@ -772,7 +772,7 @@ int unbase64mem_full( const char *p, size_t l, bool secure, - void **ret, + void **ret_data, size_t *ret_size) { _cleanup_free_ uint8_t *buf = NULL; @@ -860,8 +860,8 @@ int unbase64mem_full( if (ret_size) *ret_size = (size_t) (z - buf); - if (ret) - *ret = TAKE_PTR(buf); + if (ret_data) + *ret_data = TAKE_PTR(buf); return 0; } diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.h b/src/libnm-systemd-shared/src/basic/hexdecoct.h index 319b21a17c..0a10af3e16 100644 --- a/src/libnm-systemd-shared/src/basic/hexdecoct.h +++ b/src/libnm-systemd-shared/src/basic/hexdecoct.h @@ -18,9 +18,9 @@ char hexchar(int x) _const_; int unhexchar(char c) _const_; char *hexmem(const void *p, size_t l); -int unhexmem_full(const char *p, size_t l, bool secure, void **mem, size_t *len); -static inline int unhexmem(const char *p, size_t l, void **mem, size_t *len) { - return unhexmem_full(p, l, false, mem, len); +int unhexmem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size); +static inline int unhexmem(const char *p, void **ret_data, size_t *ret_size) { + return unhexmem_full(p, SIZE_MAX, false, ret_data, ret_size); } char base32hexchar(int x) _const_; @@ -45,9 +45,9 @@ ssize_t base64_append( size_t l, size_t margin, size_t width); -int unbase64mem_full(const char *p, size_t l, bool secure, void **mem, size_t *len); -static inline int unbase64mem(const char *p, size_t l, void **mem, size_t *len) { - return unbase64mem_full(p, l, false, mem, len); +int unbase64mem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size); +static inline int unbase64mem(const char *p, void **ret_data, size_t *ret_size) { + return unbase64mem_full(p, SIZE_MAX, false, ret_data, ret_size); } void hexdump(FILE *f, const void *p, size_t s); diff --git a/src/libnm-systemd-shared/src/basic/in-addr-util.c b/src/libnm-systemd-shared/src/basic/in-addr-util.c index b863aec399..0c3923b0ef 100644 --- a/src/libnm-systemd-shared/src/basic/in-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/in-addr-util.c @@ -736,10 +736,11 @@ int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen) } } -int in4_addr_prefix_covers( +int in4_addr_prefix_covers_full( const struct in_addr *prefix, unsigned char prefixlen, - const struct in_addr *address) { + const struct in_addr *address, + unsigned char address_prefixlen) { struct in_addr masked_prefix, masked_address; int r; @@ -747,6 +748,9 @@ int in4_addr_prefix_covers( assert(prefix); assert(address); + if (prefixlen > address_prefixlen) + return false; + masked_prefix = *prefix; r = in4_addr_mask(&masked_prefix, prefixlen); if (r < 0) @@ -760,10 +764,11 @@ int in4_addr_prefix_covers( return in4_addr_equal(&masked_prefix, &masked_address); } -int in6_addr_prefix_covers( +int in6_addr_prefix_covers_full( const struct in6_addr *prefix, unsigned char prefixlen, - const struct in6_addr *address) { + const struct in6_addr *address, + unsigned char address_prefixlen) { struct in6_addr masked_prefix, masked_address; int r; @@ -771,6 +776,9 @@ int in6_addr_prefix_covers( assert(prefix); assert(address); + if (prefixlen > address_prefixlen) + return false; + masked_prefix = *prefix; r = in6_addr_mask(&masked_prefix, prefixlen); if (r < 0) @@ -784,20 +792,21 @@ int in6_addr_prefix_covers( return in6_addr_equal(&masked_prefix, &masked_address); } -int in_addr_prefix_covers( +int in_addr_prefix_covers_full( int family, const union in_addr_union *prefix, unsigned char prefixlen, - const union in_addr_union *address) { + const union in_addr_union *address, + unsigned char address_prefixlen) { assert(prefix); assert(address); switch (family) { case AF_INET: - return in4_addr_prefix_covers(&prefix->in, prefixlen, &address->in); + return in4_addr_prefix_covers_full(&prefix->in, prefixlen, &address->in, address_prefixlen); case AF_INET6: - return in6_addr_prefix_covers(&prefix->in6, prefixlen, &address->in6); + return in6_addr_prefix_covers_full(&prefix->in6, prefixlen, &address->in6, address_prefixlen); default: return -EAFNOSUPPORT; } @@ -922,12 +931,19 @@ int in_addr_prefix_from_string_auto_internal( } +void in_addr_hash_func(const union in_addr_union *u, int family, struct siphash *state) { + assert(u); + assert(state); + + siphash24_compress(u->bytes, FAMILY_ADDRESS_SIZE(family), state); +} + void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state) { assert(a); assert(state); - siphash24_compress(&a->family, sizeof(a->family), state); - siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state); + siphash24_compress_typesafe(a->family, state); + in_addr_hash_func(&a->address, a->family, state); } int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y) { @@ -960,7 +976,7 @@ void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state) { assert(addr); assert(state); - siphash24_compress(addr, sizeof(*addr), state); + siphash24_compress_typesafe(*addr, state); } int in6_addr_compare_func(const struct in6_addr *a, const struct in6_addr *b) { diff --git a/src/libnm-systemd-shared/src/basic/in-addr-util.h b/src/libnm-systemd-shared/src/basic/in-addr-util.h index 200b9eb69d..9fae3cae45 100644 --- a/src/libnm-systemd-shared/src/basic/in-addr-util.h +++ b/src/libnm-systemd-shared/src/basic/in-addr-util.h @@ -144,9 +144,18 @@ int in4_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mas int in4_addr_mask(struct in_addr *addr, unsigned char prefixlen); int in6_addr_mask(struct in6_addr *addr, unsigned char prefixlen); int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen); -int in4_addr_prefix_covers(const struct in_addr *prefix, unsigned char prefixlen, const struct in_addr *address); -int in6_addr_prefix_covers(const struct in6_addr *prefix, unsigned char prefixlen, const struct in6_addr *address); -int in_addr_prefix_covers(int family, const union in_addr_union *prefix, unsigned char prefixlen, const union in_addr_union *address); +int in4_addr_prefix_covers_full(const struct in_addr *prefix, unsigned char prefixlen, const struct in_addr *address, unsigned char address_prefixlen); +int in6_addr_prefix_covers_full(const struct in6_addr *prefix, unsigned char prefixlen, const struct in6_addr *address, unsigned char address_prefixlen); +int in_addr_prefix_covers_full(int family, const union in_addr_union *prefix, unsigned char prefixlen, const union in_addr_union *address, unsigned char address_prefixlen); +static inline int in4_addr_prefix_covers(const struct in_addr *prefix, unsigned char prefixlen, const struct in_addr *address) { + return in4_addr_prefix_covers_full(prefix, prefixlen, address, 32); +} +static inline int in6_addr_prefix_covers(const struct in6_addr *prefix, unsigned char prefixlen, const struct in6_addr *address) { + return in6_addr_prefix_covers_full(prefix, prefixlen, address, 128); +} +static inline int in_addr_prefix_covers(int family, const union in_addr_union *prefix, unsigned char prefixlen, const union in_addr_union *address) { + return in_addr_prefix_covers_full(family, prefix, prefixlen, address, family == AF_INET ? 32 : family == AF_INET6 ? 128 : 0); +} int in_addr_parse_prefixlen(int family, const char *p, unsigned char *ret); int in_addr_prefix_from_string(const char *p, int family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen); @@ -176,6 +185,7 @@ static inline size_t FAMILY_ADDRESS_SIZE(int family) { * See also oss-fuzz#11344. */ #define IN_ADDR_NULL ((union in_addr_union) { .in6 = {} }) +void in_addr_hash_func(const union in_addr_union *u, int family, struct siphash *state); void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state); int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y); void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state); @@ -186,6 +196,16 @@ extern const struct hash_ops in_addr_data_hash_ops_free; extern const struct hash_ops in6_addr_hash_ops; extern const struct hash_ops in6_addr_hash_ops_free; +static inline void PTR_TO_IN4_ADDR(const void *p, struct in_addr *ret) { + assert(ret); + ret->s_addr = (uint32_t) ((uintptr_t) p); +} + +static inline void* IN4_ADDR_TO_PTR(const struct in_addr *a) { + assert(a); + return (void*) ((uintptr_t) a->s_addr); +} + #define IPV4_ADDRESS_FMT_STR "%u.%u.%u.%u" #define IPV4_ADDRESS_FMT_VAL(address) \ be32toh((address).s_addr) >> 24, \ diff --git a/src/libnm-systemd-shared/src/basic/inotify-util.c b/src/libnm-systemd-shared/src/basic/inotify-util.c index 59e03e620e..c748bf1bf0 100644 --- a/src/libnm-systemd-shared/src/basic/inotify-util.c +++ b/src/libnm-systemd-shared/src/basic/inotify-util.c @@ -6,6 +6,43 @@ #include "inotify-util.h" #include "stat-util.h" +bool inotify_event_next( + union inotify_event_buffer *buffer, + size_t size, + struct inotify_event **iterator, + int log_level) { + + struct inotify_event *e; + size_t offset = 0; + + assert(buffer); + assert(iterator); + + if (*iterator) { + assert((uint8_t*) *iterator >= buffer->raw); + offset = (uint8_t*) *iterator - buffer->raw; + offset += offsetof(struct inotify_event, name) + (*iterator)->len; + } + + if (size == offset) + return false; /* reached end of list */ + + if (size < offset || + size - offset < offsetof(struct inotify_event, name)) { + log_full(log_level, "Received invalid inotify event, ignoring."); + return false; + } + + e = CAST_ALIGN_PTR(struct inotify_event, buffer->raw + offset); + if (size - offset - offsetof(struct inotify_event, name) < e->len) { + log_full(log_level, "Received invalid inotify event, ignoring."); + return false; + } + + *iterator = e; + return true; +} + int inotify_add_watch_fd(int fd, int what, uint32_t mask) { int wd, r; diff --git a/src/libnm-systemd-shared/src/basic/inotify-util.h b/src/libnm-systemd-shared/src/basic/inotify-util.h index 61951ff3e3..665fdacaa6 100644 --- a/src/libnm-systemd-shared/src/basic/inotify-util.h +++ b/src/libnm-systemd-shared/src/basic/inotify-util.h @@ -10,29 +10,27 @@ #define INOTIFY_EVENT_MAX (offsetof(struct inotify_event, name) + NAME_MAX + 1) -#define _FOREACH_INOTIFY_EVENT(e, buffer, sz, log_level, start, end) \ - for (struct inotify_event \ - *start = &((buffer).ev), \ - *end = (struct inotify_event*) ((uint8_t*) start + (sz)), \ - *e = start; \ - (size_t) ((uint8_t*) end - (uint8_t*) e) >= sizeof(struct inotify_event) && \ - ((size_t) ((uint8_t*) end - (uint8_t*) e) >= sizeof(struct inotify_event) + e->len || \ - (log_full(log_level, "Received invalid inotify event, ignoring."), false)); \ - e = (struct inotify_event*) ((uint8_t*) e + sizeof(struct inotify_event) + e->len)) - -#define _FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, log_level) \ - _FOREACH_INOTIFY_EVENT(e, buffer, sz, log_level, UNIQ_T(start, UNIQ), UNIQ_T(end, UNIQ)) +/* This evaluates arguments multiple times */ +#define FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, log_level) \ + for (struct inotify_event *e = NULL; \ + inotify_event_next(&buffer, sz, &e, log_level); ) #define FOREACH_INOTIFY_EVENT(e, buffer, sz) \ - _FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, LOG_DEBUG) + FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, LOG_DEBUG) #define FOREACH_INOTIFY_EVENT_WARN(e, buffer, sz) \ - _FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, LOG_WARNING) + FOREACH_INOTIFY_EVENT_FULL(e, buffer, sz, LOG_WARNING) union inotify_event_buffer { struct inotify_event ev; uint8_t raw[INOTIFY_EVENT_MAX]; }; +bool inotify_event_next( + union inotify_event_buffer *buffer, + size_t size, + struct inotify_event **iterator, + int log_level); + int inotify_add_watch_fd(int fd, int what, uint32_t mask); int inotify_add_watch_and_warn(int fd, const char *pathname, uint32_t mask); diff --git a/src/libnm-systemd-shared/src/basic/io-util.c b/src/libnm-systemd-shared/src/basic/io-util.c index 0c480091b2..abe61ed56c 100644 --- a/src/libnm-systemd-shared/src/basic/io-util.c +++ b/src/libnm-systemd-shared/src/basic/io-util.c @@ -7,7 +7,9 @@ #include <stdio.h> #include <unistd.h> +#include "errno-util.h" #include "io-util.h" +#include "iovec-util.h" #include "string-util.h" #include "time-util.h" @@ -58,8 +60,7 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { assert(fd >= 0); - /* If called with nbytes == 0, let's call read() at least - * once, to validate the operation */ + /* If called with nbytes == 0, let's call read() at least once, to validate the operation */ if (nbytes > (size_t) SSIZE_MAX) return -EINVAL; @@ -111,13 +112,29 @@ int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) { } #if 0 /* NM_IGNORED */ -int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { - const uint8_t *p = ASSERT_PTR(buf); +int loop_write_full(int fd, const void *buf, size_t nbytes, usec_t timeout) { + const uint8_t *p; + usec_t end; + int r; assert(fd >= 0); + assert(buf || nbytes == 0); + + if (nbytes == 0) { + static const dummy_t dummy[0]; + assert_cc(sizeof(dummy) == 0); + p = (const void*) dummy; /* Some valid pointer, in case NULL was specified */ + } else { + if (nbytes == SIZE_MAX) + nbytes = strlen(buf); + else if (_unlikely_(nbytes > (size_t) SSIZE_MAX)) + return -EINVAL; + + p = buf; + } - if (_unlikely_(nbytes > (size_t) SSIZE_MAX)) - return -EINVAL; + /* When timeout is 0 or USEC_INFINITY this is not used. But we initialize it to a sensible value. */ + end = timestamp_is_set(timeout) ? usec_add(now(CLOCK_MONOTONIC), timeout) : USEC_INFINITY; do { ssize_t k; @@ -127,16 +144,31 @@ int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { if (errno == EINTR) continue; - if (errno == EAGAIN && do_poll) { - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via write() */ + if (errno != EAGAIN || timeout == 0) + return -errno; - (void) fd_wait_for_event(fd, POLLOUT, USEC_INFINITY); - continue; + usec_t wait_for; + + if (timeout == USEC_INFINITY) + wait_for = USEC_INFINITY; + else { + usec_t t = now(CLOCK_MONOTONIC); + if (t >= end) + return -ETIME; + + wait_for = usec_sub_unsigned(end, t); } - return -errno; + r = fd_wait_for_event(fd, POLLOUT, wait_for); + if (timeout == USEC_INFINITY || ERRNO_IS_NEG_TRANSIENT(r)) + /* If timeout == USEC_INFINITY we knowingly ignore any return value + * here, and expect that any error/EOF is reported via write() */ + continue; + if (r < 0) + return r; + if (r == 0) + return -ETIME; + continue; } if (_unlikely_(nbytes > 0 && k == 0)) /* Can't really happen */ @@ -260,7 +292,7 @@ ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) { return -EIO; } - if (lseek(fd, n, SEEK_CUR) == (off_t) -1) + if (lseek(fd, n, SEEK_CUR) < 0) return -errno; q += n; @@ -281,102 +313,4 @@ ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) { return q - (const uint8_t*) p; } - -char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value) { - char *x; - - x = strjoin(field, value); - if (x) - iovec[(*n_iovec)++] = IOVEC_MAKE_STRING(x); - return x; -} - -char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value) { - char *x; - - x = set_iovec_string_field(iovec, n_iovec, field, value); - free(value); - return x; -} - -struct iovec_wrapper *iovw_new(void) { - return malloc0(sizeof(struct iovec_wrapper)); -} - -void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors) { - if (free_vectors) - for (size_t i = 0; i < iovw->count; i++) - free(iovw->iovec[i].iov_base); - - iovw->iovec = mfree(iovw->iovec); - iovw->count = 0; -} - -struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw) { - iovw_free_contents(iovw, true); - - return mfree(iovw); -} - -struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw) { - iovw_free_contents(iovw, false); - - return mfree(iovw); -} - -int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { - if (iovw->count >= IOV_MAX) - return -E2BIG; - - if (!GREEDY_REALLOC(iovw->iovec, iovw->count + 1)) - return -ENOMEM; - - iovw->iovec[iovw->count++] = IOVEC_MAKE(data, len); - return 0; -} - -int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value) { - _cleanup_free_ char *x = NULL; - int r; - - x = strjoin(field, value); - if (!x) - return -ENOMEM; - - r = iovw_put(iovw, x, strlen(x)); - if (r >= 0) - TAKE_PTR(x); - - return r; -} - -int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value) { - _cleanup_free_ _unused_ char *free_ptr = value; - - return iovw_put_string_field(iovw, field, value); -} - -void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) { - for (size_t i = 0; i < iovw->count; i++) - iovw->iovec[i].iov_base = (char *)iovw->iovec[i].iov_base - old + new; -} - -size_t iovw_size(struct iovec_wrapper *iovw) { - size_t n = 0; - - for (size_t i = 0; i < iovw->count; i++) - n += iovw->iovec[i].iov_len; - - return n; -} - -void iovec_array_free(struct iovec *iov, size_t n) { - if (!iov) - return; - - for (size_t i = 0; i < n; i++) - free(iov[i].iov_base); - - free(iov); -} #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/io-util.h b/src/libnm-systemd-shared/src/basic/io-util.h index 3ad8267962..e027c1a878 100644 --- a/src/libnm-systemd-shared/src/basic/io-util.h +++ b/src/libnm-systemd-shared/src/basic/io-util.h @@ -6,7 +6,6 @@ #include <stddef.h> #include <stdint.h> #include <sys/types.h> -#include <sys/uio.h> #include "macro.h" #include "time-util.h" @@ -15,7 +14,11 @@ int flush_fd(int fd); ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll); int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll); -int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll); + +int loop_write_full(int fd, const void *buf, size_t nbytes, usec_t timeout); +static inline int loop_write(int fd, const void *buf, size_t nbytes) { + return loop_write_full(fd, buf, nbytes, 0); +} int pipe_eof(int fd); @@ -24,38 +27,6 @@ int fd_wait_for_event(int fd, int event, usec_t timeout); ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length); -static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, size_t n) { - size_t r = 0; - - for (size_t j = 0; j < n; j++) - r += i[j].iov_len; - - return r; -} - -static inline bool IOVEC_INCREMENT(struct iovec *i, size_t n, size_t k) { - /* Returns true if there is nothing else to send (bytes written cover all of the iovec), - * false if there's still work to do. */ - - for (size_t j = 0; j < n; j++) { - size_t sub; - - if (i[j].iov_len == 0) - continue; - if (k == 0) - return false; - - sub = MIN(i[j].iov_len, k); - i[j].iov_len -= sub; - i[j].iov_base = (uint8_t*) i[j].iov_base + sub; - k -= sub; - } - - assert(k == 0); /* Anything else would mean that we wrote more bytes than available, - * or the kernel reported writing more bytes than sent. */ - return true; -} - static inline bool FILE_SIZE_VALID(uint64_t l) { /* ftruncate() and friends take an unsigned file size, but actually cannot deal with file sizes larger than * 2^63 since the kernel internally handles it as signed value. This call allows checking for this early. */ @@ -73,40 +44,3 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) { return FILE_SIZE_VALID(l); } - -#define IOVEC_NULL (struct iovec) {} -#define IOVEC_MAKE(base, len) (struct iovec) { .iov_base = (base), .iov_len = (len) } -#define IOVEC_MAKE_STRING(string) \ - ({ \ - char *_s = (char*) (string); \ - IOVEC_MAKE(_s, strlen(_s)); \ - }) - -char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value); -char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value); - -struct iovec_wrapper { - struct iovec *iovec; - size_t count; -}; - -struct iovec_wrapper *iovw_new(void); -struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw); -struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw); -void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors); - -int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); -static inline int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { - /* Move data into iovw or free on error */ - int r = iovw_put(iovw, data, len); - if (r < 0) - free(data); - return r; -} - -int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value); -int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value); -void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new); -size_t iovw_size(struct iovec_wrapper *iovw); - -void iovec_array_free(struct iovec *iov, size_t n); diff --git a/src/libnm-systemd-shared/src/basic/iovec-util.h b/src/libnm-systemd-shared/src/basic/iovec-util.h new file mode 100644 index 0000000000..8cfa5717dc --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/iovec-util.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdbool.h> +#include <sys/types.h> +#include <sys/uio.h> + +#include "alloc-util.h" +#include "macro.h" + +/* An iovec pointing to a single NUL byte */ +#define IOVEC_NUL_BYTE (const struct iovec) { \ + .iov_base = (void*) (const uint8_t[1]) { 0 }, \ + .iov_len = 1, \ + } + +size_t iovec_total_size(const struct iovec *iovec, size_t n); + +bool iovec_increment(struct iovec *iovec, size_t n, size_t k); + +/* This accepts both const and non-const pointers */ +#define IOVEC_MAKE(base, len) \ + (struct iovec) { \ + .iov_base = (void*) (base), \ + .iov_len = (len), \ + } + +static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s) { + assert(iovec); + /* We don't use strlen_ptr() here, because we don't want to include string-util.h for now */ + *iovec = IOVEC_MAKE(s, s ? strlen(s) : 0); + return iovec; +} + +#define IOVEC_MAKE_STRING(s) \ + *iovec_make_string(&(struct iovec) {}, s) + +#define CONST_IOVEC_MAKE_STRING(s) \ + (const struct iovec) { \ + .iov_base = (char*) s, \ + .iov_len = STRLEN(s), \ + } + +static inline void iovec_done(struct iovec *iovec) { + /* A _cleanup_() helper that frees the iov_base in the iovec */ + assert(iovec); + + iovec->iov_base = mfree(iovec->iov_base); + iovec->iov_len = 0; +} + +static inline void iovec_done_erase(struct iovec *iovec) { + assert(iovec); + + iovec->iov_base = erase_and_free(iovec->iov_base); + iovec->iov_len = 0; +} + +static inline bool iovec_is_set(const struct iovec *iovec) { + /* Checks if the iovec points to a non-empty chunk of memory */ + return iovec && iovec->iov_len > 0 && iovec->iov_base; +} + +static inline bool iovec_is_valid(const struct iovec *iovec) { + /* Checks if the iovec is either NULL, empty or points to a valid bit of memory */ + return !iovec || (iovec->iov_base || iovec->iov_len == 0); +} + +char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value); +char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value); + +void iovec_array_free(struct iovec *iovec, size_t n_iovec); + +static inline int iovec_memcmp(const struct iovec *a, const struct iovec *b) { + + if (a == b) + return 0; + + return memcmp_nn(a ? a->iov_base : NULL, + a ? a->iov_len : 0, + b ? b->iov_base : NULL, + b ? b->iov_len : 0); +} + +static inline struct iovec *iovec_memdup(const struct iovec *source, struct iovec *ret) { + assert(ret); + + if (!iovec_is_set(source)) + *ret = (struct iovec) {}; + else { + void *p = memdup(source->iov_base, source->iov_len); + if (!p) + return NULL; + + *ret = IOVEC_MAKE(p, source->iov_len); + } + + return ret; +} diff --git a/src/libnm-systemd-shared/src/basic/list.h b/src/libnm-systemd-shared/src/basic/list.h index e4e5dff3ea..10e69541d4 100644 --- a/src/libnm-systemd-shared/src/basic/list.h +++ b/src/libnm-systemd-shared/src/basic/list.h @@ -192,6 +192,18 @@ _p; \ }) +#define LIST_CLEAR(name, head, free_func) \ + _LIST_CLEAR(name, head, free_func, UNIQ_T(elem, UNIQ)) + +/* Clear the list, destroying each element with free_func */ +#define _LIST_CLEAR(name, head, free_func, elem) \ + ({ \ + typeof(head) elem; \ + while ((elem = LIST_POP(name, head))) \ + free_func(elem); \ + head; \ + }) + /* Now include "macro.h", because we want our definition of assert() which the macros above use. We include * it down here instead of up top, since macro.h pulls in log.h which in turn needs our own definitions. */ #include "macro.h" diff --git a/src/libnm-systemd-shared/src/basic/locale-util.c b/src/libnm-systemd-shared/src/basic/locale-util.c index 2f3701eba6..78564a74a0 100644 --- a/src/libnm-systemd-shared/src/basic/locale-util.c +++ b/src/libnm-systemd-shared/src/basic/locale-util.c @@ -19,6 +19,7 @@ #include "fileio.h" #include "hashmap.h" #include "locale-util.h" +#include "missing_syscall.h" #include "path-util.h" #include "set.h" #include "string-table.h" @@ -223,7 +224,7 @@ int get_locales(char ***ret) { locales = set_free(locales); r = getenv_bool("SYSTEMD_LIST_NON_UTF8_LOCALES"); - if (r == -ENXIO || r == 0) { + if (IN_SET(r, -ENXIO, 0)) { char **a, **b; /* Filter out non-UTF-8 locales, because it's 2019, by default */ @@ -282,11 +283,6 @@ int locale_is_installed(const char *name) { return true; } - -void init_gettext(void) { - setlocale(LC_ALL, ""); - textdomain(GETTEXT_PACKAGE); -} #endif /* NM_IGNORED */ bool is_locale_utf8(void) { @@ -307,6 +303,12 @@ bool is_locale_utf8(void) { } else if (r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_UTF8, ignoring: %m"); + /* This function may be called from libsystemd, and setlocale() is not thread safe. Assuming yes. */ + if (gettid() != raw_getpid()) { + cached_answer = true; + goto out; + } + if (!setlocale(LC_ALL, "")) { cached_answer = true; goto out; @@ -345,11 +347,7 @@ out: #if 0 /* NM_IGNORED */ void locale_variables_free(char *l[_VARIABLE_LC_MAX]) { - if (!l) - return; - - for (LocaleVariable i = 0; i < _VARIABLE_LC_MAX; i++) - l[i] = mfree(l[i]); + free_many_charp(l, _VARIABLE_LC_MAX); } void locale_variables_simplify(char *l[_VARIABLE_LC_MAX]) { diff --git a/src/libnm-systemd-shared/src/basic/locale-util.h b/src/libnm-systemd-shared/src/basic/locale-util.h index 8990cb6a75..81fe8d1084 100644 --- a/src/libnm-systemd-shared/src/basic/locale-util.h +++ b/src/libnm-systemd-shared/src/basic/locale-util.h @@ -33,9 +33,8 @@ int get_locales(char ***l); bool locale_is_valid(const char *name); int locale_is_installed(const char *name); -#define _(String) gettext(String) +#define _(String) dgettext(GETTEXT_PACKAGE, String) #define N_(String) String -void init_gettext(void); bool is_locale_utf8(void); diff --git a/src/libnm-systemd-shared/src/basic/lock-util.h b/src/libnm-systemd-shared/src/basic/lock-util.h index e7744476bb..91b332f803 100644 --- a/src/libnm-systemd-shared/src/basic/lock-util.h +++ b/src/libnm-systemd-shared/src/basic/lock-util.h @@ -34,9 +34,12 @@ void unposix_unlockpp(int **fd); _cleanup_(unposix_unlockpp) _unused_ int *CONCATENATE(_cleanup_unposix_unlock_, UNIQ) = &(fd) typedef enum LockType { + LOCK_NONE, /* Don't lock the file descriptor. Useful if you need to conditionally lock a file. */ LOCK_BSD, LOCK_POSIX, LOCK_UNPOSIX, } LockType; int lock_generic(int fd, LockType type, int operation); + +int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeout); diff --git a/src/libnm-systemd-shared/src/basic/log.h b/src/libnm-systemd-shared/src/basic/log.h index eb7b51cb82..cc59b0775c 100644 --- a/src/libnm-systemd-shared/src/basic/log.h +++ b/src/libnm-systemd-shared/src/basic/log.h @@ -467,6 +467,9 @@ void log_set_open_when_needed(bool b); * stderr, the console or kmsg */ void log_set_prohibit_ipc(bool b); +void log_set_assert_return_is_critical(bool b); +bool log_get_assert_return_is_critical(void) _pure_; + int log_dup_console(void); #if 0 /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/macro.h b/src/libnm-systemd-shared/src/basic/macro.h index ce7350cb0e..eec8cba673 100644 --- a/src/libnm-systemd-shared/src/basic/macro.h +++ b/src/libnm-systemd-shared/src/basic/macro.h @@ -14,7 +14,7 @@ /* Note: on GCC "no_sanitize_address" is a function attribute only, on llvm it may also be applied to global * variables. We define a specific macro which knows this. Note that on GCC we don't need this decorator so much, since - * our primary usecase for this attribute is registration structures placed in named ELF sections which shall not be + * our primary use case for this attribute is registration structures placed in named ELF sections which shall not be * padded, but GCC doesn't pad those anyway if AddressSanitizer is enabled. */ #if HAS_FEATURE_ADDRESS_SANITIZER && defined(__clang__) #define _variable_no_sanitize_address_ __attribute__((__no_sanitize_address__)) @@ -84,7 +84,7 @@ #define REENABLE_WARNING #endif -/* automake test harness */ +/* test harness */ #define EXIT_TEST_SKIP 77 /* builtins */ @@ -96,6 +96,13 @@ #error "neither int nor long are four bytes long?!?" #endif +static inline uint64_t u64_multiply_safe(uint64_t a, uint64_t b) { + if (_unlikely_(a != 0 && b > (UINT64_MAX / a))) + return 0; /* overflow */ + + return a * b; +} + /* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */ static inline unsigned long ALIGN_POWER2(unsigned long u) { @@ -198,7 +205,7 @@ static inline int __coverity_check_and_return__(int condition) { /* We override the glibc assert() here. */ #undef assert #ifdef NDEBUG -#define assert(expr) do {} while (false) +#define assert(expr) ({ if (!(expr)) __builtin_unreachable(); }) #else #define assert(expr) assert_message_se(expr, #expr) #endif @@ -304,12 +311,6 @@ static inline int __coverity_check_and_return__(int condition) { /* Pointers range from NULL to POINTER_MAX */ #define POINTER_MAX ((void*) UINTPTR_MAX) -/* Iterates through a specified list of pointers. Accepts NULL pointers, but uses POINTER_MAX as internal marker for EOL. */ -#define FOREACH_POINTER(p, x, ...) \ - for (typeof(p) *_l = (typeof(p)[]) { ({ p = x; }), ##__VA_ARGS__, POINTER_MAX }; \ - p != (typeof(p)) POINTER_MAX; \ - p = *(++_l)) - #define _FOREACH_ARRAY(i, array, num, m, end) \ for (typeof(array[0]) *i = (array), *end = ({ \ typeof(num) m = (num); \ @@ -319,31 +320,6 @@ static inline int __coverity_check_and_return__(int condition) { #define FOREACH_ARRAY(i, array, num) \ _FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ)) -#define DEFINE_TRIVIAL_DESTRUCTOR(name, type, func) \ - static inline void name(type *p) { \ - func(p); \ - } - -/* When func() returns the void value (NULL, -1, …) of the appropriate type */ -#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ - static inline void func##p(type *p) { \ - if (*p) \ - *p = func(*p); \ - } - -/* When func() doesn't return the appropriate type, set variable to empty afterwards. - * The func() may be provided by a dynamically loaded shared library, hence add an assertion. */ -#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(type, func, empty) \ - static inline void func##p(type *p) { \ - if (*p != (empty)) { \ - DISABLE_WARNING_ADDRESS; \ - assert(func); \ - REENABLE_WARNING; \ - func(*p); \ - *p = (empty); \ - } \ - } - #define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \ scope type *name##_ref(type *p) { \ if (!p) \ @@ -443,13 +419,13 @@ assert_cc(sizeof(dummy_t) == 0); _q && _q > (base) ? &_q[-1] : NULL; \ }) -/* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly +/* Iterate through each argument passed. All must be the same type as 'entry' or must be implicitly * convertible. The iteration variable 'entry' must already be defined. */ -#define VA_ARGS_FOREACH(entry, ...) \ - _VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), ##__VA_ARGS__) -#define _VA_ARGS_FOREACH(entry, _entries_, _current_, ...) \ - for (typeof(entry) _entries_[] = { __VA_ARGS__ }, *_current_ = _entries_; \ - ((long)(_current_ - _entries_) < (long)ELEMENTSOF(_entries_)) && ({ entry = *_current_; true; }); \ +#define FOREACH_ARGUMENT(entry, ...) \ + _FOREACH_ARGUMENT(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), UNIQ_T(_va_sentinel_, UNIQ), ##__VA_ARGS__) +#define _FOREACH_ARGUMENT(entry, _entries_, _current_, _va_sentinel_, ...) \ + for (typeof(entry) _va_sentinel_[1] = {}, _entries_[] = { __VA_ARGS__ __VA_OPT__(,) _va_sentinel_[0] }, *_current_ = _entries_; \ + ((long)(_current_ - _entries_) < (long)(ELEMENTSOF(_entries_) - 1)) && ({ entry = *_current_; true; }); \ _current_++) #include "log.h" diff --git a/src/libnm-systemd-shared/src/basic/memory-util.c b/src/libnm-systemd-shared/src/basic/memory-util.c index c1e0a742b1..789e96a9c3 100644 --- a/src/libnm-systemd-shared/src/basic/memory-util.c +++ b/src/libnm-systemd-shared/src/basic/memory-util.c @@ -41,3 +41,19 @@ bool memeqbyte(uint8_t byte, const void *data, size_t length) { /* Now we know first 16 bytes match, memcmp() with self. */ return memcmp(data, p + 16, length) == 0; } + +void *memdup_reverse(const void *mem, size_t size) { + assert(mem); + assert(size != 0); + + void *p = malloc(size); + if (!p) + return NULL; + + uint8_t *p_dst = p; + const uint8_t *p_src = mem; + for (size_t i = 0, k = size; i < size; i++, k--) + p_dst[i] = p_src[k-1]; + + return p; +} diff --git a/src/libnm-systemd-shared/src/basic/memory-util.h b/src/libnm-systemd-shared/src/basic/memory-util.h index d26a0918e1..294aed67df 100644 --- a/src/libnm-systemd-shared/src/basic/memory-util.h +++ b/src/libnm-systemd-shared/src/basic/memory-util.h @@ -12,9 +12,12 @@ #include "memory-util-fundamental.h" size_t page_size(void) _pure_; -#define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) -#define PAGE_ALIGN_DOWN(l) ((l) & ~(page_size() - 1)) -#define PAGE_OFFSET(l) ((l) & (page_size() - 1)) +#define PAGE_ALIGN(l) ALIGN_TO(l, page_size()) +#define PAGE_ALIGN_U64(l) ALIGN_TO_U64(l, page_size()) +#define PAGE_ALIGN_DOWN(l) ALIGN_DOWN(l, page_size()) +#define PAGE_ALIGN_DOWN_U64(l) ALIGN_DOWN_U64(l, page_size()) +#define PAGE_OFFSET(l) ALIGN_OFFSET(l, page_size()) +#define PAGE_OFFSET_U64(l) ALIGN_OFFSET_U64(l, page_size()) /* Normal memcpy() requires src to be nonnull. We do nothing if n is 0. */ static inline void *memcpy_safe(void *dst, const void *src, size_t n) { @@ -47,13 +50,6 @@ static inline int memcmp_nn(const void *s1, size_t n1, const void *s2, size_t n2 ?: CMP(n1, n2); } -#define memzero(x,l) \ - ({ \ - size_t _l_ = (l); \ - if (_l_ > 0) \ - memset(x, 0, _l_); \ - }) - #define zero(x) (memzero(&(x), sizeof(x))) bool memeqbyte(uint8_t byte, const void *data, size_t length); @@ -112,36 +108,5 @@ static inline void erase_char(char *p) { explicit_bzero_safe(p, sizeof(char)); } -/* An automatic _cleanup_-like logic for destroy arrays (i.e. pointers + size) when leaving scope */ -typedef struct ArrayCleanup { - void **parray; - size_t *pn; - free_array_func_t pfunc; -} ArrayCleanup; - -static inline void array_cleanup(const ArrayCleanup *c) { - assert(c); - - assert(!c->parray == !c->pn); - - if (!c->parray) - return; - - if (*c->parray) { - assert(c->pfunc); - c->pfunc(*c->parray, *c->pn); - *c->parray = NULL; - } - - *c->pn = 0; -} - -#define CLEANUP_ARRAY(array, n, func) \ - _cleanup_(array_cleanup) _unused_ const ArrayCleanup CONCATENATE(_cleanup_array_, UNIQ) = { \ - .parray = (void**) &(array), \ - .pn = &(n), \ - .pfunc = (free_array_func_t) ({ \ - void (*_f)(typeof(array[0]) *a, size_t b) = func; \ - _f; \ - }), \ - } +/* Makes a copy of the buffer with reversed order of bytes */ +void *memdup_reverse(const void *mem, size_t size); diff --git a/src/libnm-systemd-shared/src/basic/missing_socket.h b/src/libnm-systemd-shared/src/basic/missing_socket.h index a4f6836fd4..ffda7cc684 100644 --- a/src/libnm-systemd-shared/src/basic/missing_socket.h +++ b/src/libnm-systemd-shared/src/basic/missing_socket.h @@ -7,7 +7,6 @@ #if HAVE_LINUX_VM_SOCKETS_H #include <linux/vm_sockets.h> #else -#define VMADDR_CID_ANY -1U struct sockaddr_vm { unsigned short svm_family; unsigned short svm_reserved1; @@ -22,6 +21,26 @@ struct sockaddr_vm { #endif /* !HAVE_LINUX_VM_SOCKETS_H */ #endif /* NM_IGNORED */ +#ifndef VMADDR_CID_ANY +#define VMADDR_CID_ANY -1U +#endif + +#ifndef VMADDR_CID_HYPERVISOR +#define VMADDR_CID_HYPERVISOR 0U +#endif + +#ifndef VMADDR_CID_LOCAL +#define VMADDR_CID_LOCAL 1U +#endif + +#ifndef VMADDR_CID_HOST +#define VMADDR_CID_HOST 2U +#endif + +#ifndef VMADDR_PORT_ANY +#define VMADDR_PORT_ANY -1U +#endif + #ifndef AF_VSOCK #define AF_VSOCK 40 #endif @@ -34,6 +53,10 @@ struct sockaddr_vm { #define SO_PEERGROUPS 59 #endif +#ifndef SO_PEERPIDFD +#define SO_PEERPIDFD 77 +#endif + #ifndef SO_BINDTOIFINDEX #define SO_BINDTOIFINDEX 62 #endif diff --git a/src/libnm-systemd-shared/src/basic/missing_stat.h b/src/libnm-systemd-shared/src/basic/missing_stat.h index 8b39d4f44f..18a15ab00a 100644 --- a/src/libnm-systemd-shared/src/basic/missing_stat.h +++ b/src/libnm-systemd-shared/src/basic/missing_stat.h @@ -9,7 +9,7 @@ #include <linux/stat.h> #endif -/* Thew newest definition we are aware of (fa2fcf4f1df1559a0a4ee0f46915b496cc2ebf60; 5.8) */ +/* The newest definition we are aware of (fa2fcf4f1df1559a0a4ee0f46915b496cc2ebf60; 5.8) */ #define STATX_DEFINITION { \ __u32 stx_mask; \ __u32 stx_blksize; \ diff --git a/src/libnm-systemd-shared/src/basic/missing_syscall.h b/src/libnm-systemd-shared/src/basic/missing_syscall.h index 610a7cef2e..a04633510d 100644 --- a/src/libnm-systemd-shared/src/basic/missing_syscall.h +++ b/src/libnm-systemd-shared/src/basic/missing_syscall.h @@ -34,6 +34,21 @@ /* ======================================================================= */ +#if !HAVE_FCHMODAT2 +static inline int missing_fchmodat2(int dirfd, const char *path, mode_t mode, int flags) { +# ifdef __NR_fchmodat2 + return syscall(__NR_fchmodat2, dirfd, path, mode, flags); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define fchmodat2 missing_fchmodat2 +#endif + +/* ======================================================================= */ + #if !HAVE_PIVOT_ROOT static inline int missing_pivot_root(const char *new_root, const char *put_old) { return syscall(__NR_pivot_root, new_root, put_old); @@ -546,6 +561,10 @@ static inline int missing_open_tree( /* ======================================================================= */ +#ifndef MOVE_MOUNT_BENEATH +#define MOVE_MOUNT_BENEATH 0x00000200 +#endif + #if !HAVE_MOVE_MOUNT #ifndef MOVE_MOUNT_F_EMPTY_PATH @@ -662,3 +681,17 @@ static inline ssize_t missing_getdents64(int fd, void *buffer, size_t length) { # define getdents64 missing_getdents64 #endif #endif /* NM_IGNORED */ + +/* ======================================================================= */ + +/* glibc does not provide clone() on ia64, only clone2(). Not only that, but it also doesn't provide a + * prototype, only the symbol in the shared library (it provides a prototype for clone(), but not the + * symbol in the shared library). */ +#if defined(__ia64__) +int __clone2(int (*fn)(void *), void *stack_base, size_t stack_size, int flags, void *arg); +#define HAVE_CLONE 0 +#else +/* We know that everywhere else clone() is available, so we don't bother with a meson check (that takes time + * at build time) and just define it. Once the kernel drops ia64 support, we can drop this too. */ +#define HAVE_CLONE 1 +#endif diff --git a/src/libnm-systemd-shared/src/basic/namespace-util.h b/src/libnm-systemd-shared/src/basic/namespace-util.h new file mode 100644 index 0000000000..d1d015612f --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/namespace-util.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <sys/types.h> + +typedef enum NamespaceType { + NAMESPACE_CGROUP, + NAMESPACE_IPC, + NAMESPACE_NET, + NAMESPACE_MOUNT, + NAMESPACE_PID, + NAMESPACE_USER, + NAMESPACE_UTS, + NAMESPACE_TIME, + _NAMESPACE_TYPE_MAX, + _NAMESPACE_TYPE_INVALID = -EINVAL, +} NamespaceType; + +extern const struct namespace_info { + const char *proc_name; + const char *proc_path; + unsigned int clone_flag; +} namespace_info[_NAMESPACE_TYPE_MAX + 1]; + +int namespace_open( + pid_t pid, + int *ret_pidns_fd, + int *ret_mntns_fd, + int *ret_netns_fd, + int *ret_userns_fd, + int *ret_root_fd); +int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd); + +int fd_is_ns(int fd, unsigned long nsflag); + +int detach_mount_namespace(void); + +static inline bool userns_shift_range_valid(uid_t shift, uid_t range) { + /* Checks that the specified userns range makes sense, i.e. contains at least one UID, and the end + * doesn't overflow uid_t. */ + + assert_cc((uid_t) -1 > 0); /* verify that uid_t is unsigned */ + + if (range <= 0) + return false; + + if (shift > (uid_t) -1 - range) + return false; + + return true; +} + +int userns_acquire(const char *uid_map, const char *gid_map); +int netns_acquire(void); +int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type); diff --git a/src/libnm-systemd-shared/src/basic/ordered-set.c b/src/libnm-systemd-shared/src/basic/ordered-set.c index f402bb5b0c..ae50070f17 100644 --- a/src/libnm-systemd-shared/src/basic/ordered-set.c +++ b/src/libnm-systemd-shared/src/basic/ordered-set.c @@ -93,13 +93,16 @@ void ordered_set_print(FILE *f, const char *field, OrderedSet *s) { bool space = false; char *p; + assert(f); + assert(field); + if (ordered_set_isempty(s)) return; fputs(field, f); ORDERED_SET_FOREACH(p, s) - fputs_with_space(f, p, NULL, &space); + fputs_with_separator(f, p, NULL, &space); fputc('\n', f); } diff --git a/src/libnm-systemd-shared/src/basic/parse-util.c b/src/libnm-systemd-shared/src/basic/parse-util.c index 2b22039c1c..34a5375f85 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.c +++ b/src/libnm-systemd-shared/src/basic/parse-util.c @@ -47,6 +47,24 @@ int parse_boolean(const char *v) { } #if 0 /* NM_IGNORED */ +int parse_tristate_full(const char *v, const char *third, int *ret) { + int r; + + if (isempty(v) || streq_ptr(v, third)) { /* Empty string is always taken as the third/invalid/auto state */ + if (ret) + *ret = -1; + } else { + r = parse_boolean(v); + if (r < 0) + return r; + + if (ret) + *ret = r; + } + + return 0; +} + int parse_pid(const char *s, pid_t* ret_pid) { unsigned long ul = 0; pid_t pid; @@ -110,8 +128,7 @@ int parse_ifindex(const char *s) { #if 0 /* NM_IGNORED */ int parse_mtu(int family, const char *s, uint32_t *ret) { - uint64_t u; - size_t m; + uint64_t u, m; int r; r = parse_size(s, 1024, &u); @@ -121,10 +138,16 @@ int parse_mtu(int family, const char *s, uint32_t *ret) { if (u > UINT32_MAX) return -ERANGE; - if (family == AF_INET6) + switch (family) { + case AF_INET: + m = IPV4_MIN_MTU; /* This is 68 */ + break; + case AF_INET6: m = IPV6_MIN_MTU; /* This is 1280 */ - else - m = IPV4_MIN_MTU; /* For all other protocols, including 'unspecified' we assume the IPv4 minimal MTU */ + break; + default: + m = 0; + } if (u < m) return -ERANGE; @@ -431,6 +454,21 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { return 0; } +int safe_atou_bounded(const char *s, unsigned min, unsigned max, unsigned *ret) { + unsigned v; + int r; + + r = safe_atou(s, &v); + if (r < 0) + return r; + + if (v < min || v > max) + return -ERANGE; + + *ret = v; + return 0; +} + int safe_atoi(const char *s, int *ret_i) { unsigned base = 0; char *x = NULL; @@ -660,7 +698,7 @@ int parse_ip_port(const char *s, uint16_t *ret) { return 0; } -int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) { +int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow_zero) { unsigned l, h; int r; @@ -668,7 +706,10 @@ int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) { if (r < 0) return r; - if (l <= 0 || l > 65535 || h <= 0 || h > 65535) + if (l > 65535 || h > 65535) + return -EINVAL; + + if (!allow_zero && (l == 0 || h == 0)) return -EINVAL; if (h < l) @@ -754,4 +795,23 @@ int parse_loadavg_fixed_point(const char *s, loadavg_t *ret) { return store_loadavg_fixed_point(i, f, ret); } + +/* Limitations are described in https://www.netfilter.org/projects/nftables/manpage.html and + * https://bugzilla.netfilter.org/show_bug.cgi?id=1175 */ +bool nft_identifier_valid(const char *id) { + if (!id) + return false; + + size_t len = strlen(id); + if (len == 0 || len > 31) + return false; + + if (!ascii_isalpha(id[0])) + return false; + + for (size_t i = 1; i < len; i++) + if (!ascii_isalpha(id[i]) && !ascii_isdigit(id[i]) && !IN_SET(id[i], '/', '\\', '_', '.')) + return false; + return true; +} #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/parse-util.h b/src/libnm-systemd-shared/src/basic/parse-util.h index c480407c2a..c12988ef20 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.h +++ b/src/libnm-systemd-shared/src/basic/parse-util.h @@ -12,6 +12,10 @@ typedef unsigned long loadavg_t; int parse_boolean(const char *v) _pure_; +int parse_tristate_full(const char *v, const char *third, int *ret); +static inline int parse_tristate(const char *v, int *ret) { + return parse_tristate_full(v, NULL, ret); +} int parse_pid(const char *s, pid_t* ret_pid); int parse_mode(const char *s, mode_t *ret); int parse_ifindex(const char *s); @@ -30,11 +34,12 @@ int parse_fd(const char *t); #define SAFE_ATO_MASK_FLAGS(base) ((base) & ~SAFE_ATO_ALL_FLAGS) int safe_atou_full(const char *s, unsigned base, unsigned *ret_u); - static inline int safe_atou(const char *s, unsigned *ret_u) { return safe_atou_full(s, 0, ret_u); } +int safe_atou_bounded(const char *s, unsigned min, unsigned max, unsigned *ret); + int safe_atoi(const char *s, int *ret_i); int safe_atolli(const char *s, long long int *ret_i); @@ -134,7 +139,7 @@ int parse_fractional_part_u(const char **s, size_t digits, unsigned *res); int parse_nice(const char *p, int *ret); int parse_ip_port(const char *s, uint16_t *ret); -int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high); +int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow_zero); int parse_ip_prefix_length(const char *s, int *ret); @@ -152,3 +157,5 @@ int parse_oom_score_adjust(const char *s, int *ret); * to a loadavg_t. */ int store_loadavg_fixed_point(unsigned long i, unsigned long f, loadavg_t *ret); int parse_loadavg_fixed_point(const char *s, loadavg_t *ret); + +bool nft_identifier_valid(const char *id); diff --git a/src/libnm-systemd-shared/src/basic/path-util.c b/src/libnm-systemd-shared/src/basic/path-util.c index a2af9e0ce2..638e4860a6 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.c +++ b/src/libnm-systemd-shared/src/basic/path-util.c @@ -43,7 +43,7 @@ int path_split_and_make_absolute(const char *p, char ***ret) { return r; } -char *path_make_absolute(const char *p, const char *prefix) { +char* path_make_absolute(const char *p, const char *prefix) { assert(p); /* Makes every item in the list an absolute path by prepending @@ -135,11 +135,9 @@ int path_make_relative(const char *from, const char *to, char **ret) { return -ENOMEM; } else { /* 'to' is inside of 'from'. */ - result = strdup(t); - if (!result) - return -ENOMEM; - - path_simplify(result); + r = path_simplify_alloc(t, &result); + if (r < 0) + return r; if (!path_is_valid(result)) return -EINVAL; @@ -255,7 +253,7 @@ int path_strv_make_absolute_cwd(char **l) { return 0; } -char **path_strv_resolve(char **l, const char *root) { +char** path_strv_resolve(char **l, const char *root) { unsigned k = 0; bool enomem = false; int r; @@ -336,7 +334,7 @@ char **path_strv_resolve(char **l, const char *root) { return l; } -char **path_strv_resolve_uniq(char **l, const char *root) { +char** path_strv_resolve_uniq(char **l, const char *root) { if (strv_isempty(l)) return l; @@ -348,9 +346,9 @@ char **path_strv_resolve_uniq(char **l, const char *root) { } #endif /* NM_IGNORED */ -char *path_simplify(char *path) { - bool add_slash = false; - char *f = ASSERT_PTR(path); +char* path_simplify_full(char *path, PathSimplifyFlags flags) { + bool add_slash = false, keep_trailing_slash, absolute, beginning = true; + char *f = path; int r; /* Removes redundant inner and trailing slashes. Also removes unnecessary dots. @@ -358,13 +356,17 @@ char *path_simplify(char *path) { * * ///foo//./bar/. becomes /foo/bar * .//./foo//./bar/. becomes foo/bar + * /../foo/bar becomes /foo/bar + * /../foo/bar/.. becomes /foo/bar/.. */ if (isempty(path)) return path; - if (path_is_absolute(path)) - f++; + keep_trailing_slash = FLAGS_SET(flags, PATH_SIMPLIFY_KEEP_TRAILING_SLASH) && endswith(path, "/"); + + absolute = path_is_absolute(path); + f += absolute; /* Keep leading /, if present. */ for (const char *p = f;;) { const char *e; @@ -373,11 +375,17 @@ char *path_simplify(char *path) { if (r == 0) break; + if (r > 0 && absolute && beginning && path_startswith(e, "..")) + /* If we're at the beginning of an absolute path, we can safely skip ".." */ + continue; + + beginning = false; + if (add_slash) *f++ = '/'; if (r < 0) { - /* if path is invalid, then refuse to simplify remaining part. */ + /* if path is invalid, then refuse to simplify the remaining part. */ memmove(f, p, strlen(p) + 1); return path; } @@ -392,11 +400,14 @@ char *path_simplify(char *path) { if (f == path) *f++ = '.'; + if (*(f-1) != '/' && keep_trailing_slash) + *f++ = '/'; + *f = '\0'; return path; } -char *path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) { +char* path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) { assert(path); assert(prefix); @@ -489,10 +500,6 @@ int path_compare(const char *a, const char *b) { } } -bool path_equal_or_inode_same(const char *a, const char *b, int flags) { - return path_equal(a, b) || inode_same(a, b, flags) > 0; -} - int path_compare_filename(const char *a, const char *b) { _cleanup_free_ char *fa = NULL, *fb = NULL; int r, j, k; @@ -661,7 +668,14 @@ static int find_executable_impl(const char *name, const char *root, char **ret_f return 0; } -int find_executable_full(const char *name, const char *root, char **exec_search_path, bool use_path_envvar, char **ret_filename, int *ret_fd) { +int find_executable_full( + const char *name, + const char *root, + char **exec_search_path, + bool use_path_envvar, + char **ret_filename, + int *ret_fd) { + int last_error = -ENOENT, r = 0; const char *p = NULL; @@ -812,7 +826,7 @@ int fsck_exists_for_fstype(const char *fstype) { } #endif /* NM_IGNORED */ -static const char *skip_slash_or_dot(const char *p) { +static const char* skip_slash_or_dot(const char *p) { for (; !isempty(p); p++) { if (*p == '/') continue; @@ -896,7 +910,7 @@ int path_find_first_component(const char **p, bool accept_dot_dot, const char ** return len; } -static const char *skip_slash_or_dot_backward(const char *path, const char *q) { +static const char* skip_slash_or_dot_backward(const char *path, const char *q) { assert(path); assert(!q || q >= path); @@ -1005,7 +1019,7 @@ int path_find_last_component(const char *path, bool accept_dot_dot, const char * return len; } -const char *last_path_component(const char *path) { +const char* last_path_component(const char *path) { /* Finds the last component of the path, preserving the optional trailing slash that signifies a directory. * @@ -1126,17 +1140,19 @@ int path_extract_directory(const char *path, char **ret) { if (!path_is_valid(a)) return -EINVAL; - *ret = TAKE_PTR(a); + if (ret) + *ret = TAKE_PTR(a); + return 0; } -bool filename_is_valid(const char *p) { +bool filename_part_is_valid(const char *p) { const char *e; - if (isempty(p)) - return false; + /* Checks f the specified string is OK to be *part* of a filename. This is different from + * filename_is_valid() as "." and ".." and "" are OK by this call, but not by filename_is_valid(). */ - if (dot_or_dot_dot(p)) /* Yes, in this context we consider "." and ".." invalid */ + if (!p) return false; e = strchrnul(p, '/'); @@ -1149,6 +1165,17 @@ bool filename_is_valid(const char *p) { return true; } +bool filename_is_valid(const char *p) { + + if (isempty(p)) + return false; + + if (dot_or_dot_dot(p)) /* Yes, in this context we consider "." and ".." invalid */ + return false; + + return filename_part_is_valid(p); +} + bool path_is_valid_full(const char *p, bool accept_dot_dot) { if (isempty(p)) return false; @@ -1265,9 +1292,16 @@ bool hidden_or_backup_file(const char *filename) { bool is_device_path(const char *path) { /* Returns true for paths that likely refer to a device, either by path in sysfs or to something in - * /dev. */ + * /dev. This accepts any path that starts with /dev/ or /sys/ and has something after that prefix. + * It does not actually resolve the path. + * + * Examples: + * /dev/sda, /dev/sda/foo, /sys/class, /dev/.., /sys/.., /./dev/foo → yes. + * /../dev/sda, /dev, /sys, /usr/path, /usr/../dev/sda → no. + */ - return PATH_STARTSWITH_SET(path, "/dev/", "/sys/"); + const char *p = PATH_STARTSWITH_SET(ASSERT_PTR(path), "/dev/", "/sys/"); + return !isempty(p); } bool valid_device_node_path(const char *path) { diff --git a/src/libnm-systemd-shared/src/basic/path-util.h b/src/libnm-systemd-shared/src/basic/path-util.h index fee6e8ee49..5bb51ff599 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.h +++ b/src/libnm-systemd-shared/src/basic/path-util.h @@ -6,6 +6,7 @@ #include <stddef.h> #include "macro.h" +#include "stat-util.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -25,24 +26,14 @@ # define PATH_SBIN_BIN_NULSTR(x) PATH_NORMAL_SBIN_BIN_NULSTR(x) #endif -#define DEFAULT_PATH_NORMAL PATH_SBIN_BIN("/usr/local/") ":" PATH_SBIN_BIN("/usr/") -#define DEFAULT_PATH_NORMAL_NULSTR PATH_SBIN_BIN_NULSTR("/usr/local/") PATH_SBIN_BIN_NULSTR("/usr/") -#define DEFAULT_PATH_SPLIT_USR DEFAULT_PATH_NORMAL ":" PATH_SBIN_BIN("/") -#define DEFAULT_PATH_SPLIT_USR_NULSTR DEFAULT_PATH_NORMAL_NULSTR PATH_SBIN_BIN_NULSTR("/") +#define DEFAULT_PATH PATH_SBIN_BIN("/usr/local/") ":" PATH_SBIN_BIN("/usr/") +#define DEFAULT_PATH_NULSTR PATH_SBIN_BIN_NULSTR("/usr/local/") PATH_SBIN_BIN_NULSTR("/usr/") #define DEFAULT_PATH_COMPAT PATH_SPLIT_SBIN_BIN("/usr/local/") ":" PATH_SPLIT_SBIN_BIN("/usr/") ":" PATH_SPLIT_SBIN_BIN("/") -#if HAVE_SPLIT_USR -# define DEFAULT_PATH DEFAULT_PATH_SPLIT_USR -# define DEFAULT_PATH_NULSTR DEFAULT_PATH_SPLIT_USR_NULSTR -#else -# define DEFAULT_PATH DEFAULT_PATH_NORMAL -# define DEFAULT_PATH_NULSTR DEFAULT_PATH_NORMAL_NULSTR -#endif -#endif /* NM_IGNORED */ - #ifndef DEFAULT_USER_PATH # define DEFAULT_USER_PATH DEFAULT_PATH #endif +#endif /* NM_IGNORED */ static inline bool is_path(const char *p) { if (!p) /* A NULL pointer is definitely not a path */ @@ -64,7 +55,7 @@ int safe_getcwd(char **ret); int path_make_absolute_cwd(const char *p, char **ret); int path_make_relative(const char *from, const char *to, char **ret); int path_make_relative_parent(const char *from_child, const char *to, char **ret); -char *path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_; +char* path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_; static inline char* path_startswith(const char *path, const char *prefix) { return path_startswith_full(path, prefix, true); } @@ -79,13 +70,38 @@ static inline bool path_equal_filename(const char *a, const char *b) { return path_compare_filename(a, b) == 0; } -bool path_equal_or_inode_same(const char *a, const char *b, int flags); +static inline bool path_equal_or_inode_same(const char *a, const char *b, int flags) { + return path_equal(a, b) || inode_same(a, b, flags) > 0; +} char* path_extend_internal(char **x, ...); #define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX) #define path_join(...) path_extend_internal(NULL, __VA_ARGS__, POINTER_MAX) -char* path_simplify(char *path); +typedef enum PathSimplifyFlags { + PATH_SIMPLIFY_KEEP_TRAILING_SLASH = 1 << 0, +} PathSimplifyFlags; + +char* path_simplify_full(char *path, PathSimplifyFlags flags); +static inline char* path_simplify(char *path) { + return path_simplify_full(path, 0); +} + +static inline int path_simplify_alloc(const char *path, char **ret) { + assert(ret); + + if (!path) { + *ret = NULL; + return 0; + } + + char *t = strdup(path); + if (!t) + return -ENOMEM; + + *ret = path_simplify(t); + return 0; +} static inline bool path_equal_ptr(const char *a, const char *b) { return !!a == !!b && (!a || path_equal(a, b)); @@ -142,7 +158,7 @@ int fsck_exists_for_fstype(const char *fstype); char *_p, *_n; \ size_t _l; \ while (_path[0] == '/' && _path[1] == '/') \ - _path ++; \ + _path++; \ if (isempty(_root)) \ _ret = _path; \ else { \ @@ -161,10 +177,11 @@ int fsck_exists_for_fstype(const char *fstype); int path_find_first_component(const char **p, bool accept_dot_dot, const char **ret); int path_find_last_component(const char *path, bool accept_dot_dot, const char **next, const char **ret); -const char *last_path_component(const char *path); +const char* last_path_component(const char *path); int path_extract_filename(const char *path, char **ret); int path_extract_directory(const char *path, char **ret); +bool filename_part_is_valid(const char *p) _pure_; bool filename_is_valid(const char *p) _pure_; bool path_is_valid_full(const char *p, bool accept_dot_dot) _pure_; static inline bool path_is_valid(const char *p) { @@ -197,7 +214,7 @@ static inline const char *skip_dev_prefix(const char *p) { } bool empty_or_root(const char *path); -static inline const char *empty_to_root(const char *path) { +static inline const char* empty_to_root(const char *path) { return isempty(path) ? "/" : path; } diff --git a/src/libnm-systemd-shared/src/basic/pidref.h b/src/libnm-systemd-shared/src/basic/pidref.h new file mode 100644 index 0000000000..c440c8b0e0 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/pidref.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "macro.h" + +/* An embeddable structure carrying a reference to a process. Supposed to be used when tracking processes continuously. */ +typedef struct PidRef { + pid_t pid; /* always valid */ + int fd; /* only valid if pidfd are available in the kernel, and we manage to get an fd */ +} PidRef; + +#define PIDREF_NULL (const PidRef) { .fd = -EBADF } + +/* Turns a pid_t into a PidRef structure on-the-fly *without* acquiring a pidfd for it. (As opposed to + * pidref_set_pid() which does so *with* acquiring one, see below) */ +#define PIDREF_MAKE_FROM_PID(x) (PidRef) { .pid = (x), .fd = -EBADF } + +static inline bool pidref_is_set(const PidRef *pidref) { + return pidref && pidref->pid > 0; +} + +static inline bool pidref_equal(const PidRef *a, const PidRef *b) { + + if (pidref_is_set(a)) { + if (!pidref_is_set(b)) + return false; + + return a->pid == b->pid; + } + + return !pidref_is_set(b); +} + +/* This turns a pid_t into a PidRef structure, and acquires a pidfd for it, if possible. (As opposed to + * PIDREF_MAKE_FROM_PID() above, which does not acquire a pidfd.) */ +int pidref_set_pid(PidRef *pidref, pid_t pid); +int pidref_set_pidstr(PidRef *pidref, const char *pid); +int pidref_set_pidfd(PidRef *pidref, int fd); +int pidref_set_pidfd_take(PidRef *pidref, int fd); /* takes ownership of the passed pidfd on success*/ +int pidref_set_pidfd_consume(PidRef *pidref, int fd); /* takes ownership of the passed pidfd in both success and failure */ +int pidref_set_parent(PidRef *ret); +static inline int pidref_set_self(PidRef *pidref) { + return pidref_set_pid(pidref, 0); +} + +bool pidref_is_self(const PidRef *pidref); + +void pidref_done(PidRef *pidref); +PidRef *pidref_free(PidRef *pidref); +DEFINE_TRIVIAL_CLEANUP_FUNC(PidRef*, pidref_free); + +int pidref_copy(const PidRef *pidref, PidRef *dest); +int pidref_dup(const PidRef *pidref, PidRef **ret); + +int pidref_new_from_pid(pid_t pid, PidRef **ret); + +int pidref_kill(const PidRef *pidref, int sig); +int pidref_kill_and_sigcont(const PidRef *pidref, int sig); +int pidref_sigqueue(const PidRef *pidref, int sig, int value); + +int pidref_wait(const PidRef *pidref, siginfo_t *siginfo, int options); +int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret); + +static inline void pidref_done_sigkill_wait(PidRef *pidref) { + if (!pidref_is_set(pidref)) + return; + + (void) pidref_kill(pidref, SIGKILL); + (void) pidref_wait_for_terminate(pidref, NULL); + pidref_done(pidref); +} + +int pidref_verify(const PidRef *pidref); + +#define TAKE_PIDREF(p) TAKE_GENERIC((p), PidRef, PIDREF_NULL) + +extern const struct hash_ops pidref_hash_ops; +extern const struct hash_ops pidref_hash_ops_free; /* Has destructor call for pidref_free(), i.e. expects heap allocated PidRef as keys */ diff --git a/src/libnm-systemd-shared/src/basic/prioq.c b/src/libnm-systemd-shared/src/basic/prioq.c index 0af84bd273..b05b08da4a 100644 --- a/src/libnm-systemd-shared/src/basic/prioq.c +++ b/src/libnm-systemd-shared/src/basic/prioq.c @@ -215,7 +215,7 @@ static void remove_item(Prioq *q, struct prioq_item *i) { } } -_pure_ static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) { +static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) { struct prioq_item *i; assert(q); diff --git a/src/libnm-systemd-shared/src/basic/process-util.c b/src/libnm-systemd-shared/src/basic/process-util.c index 8601e0da54..0f1894b3af 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.c +++ b/src/libnm-systemd-shared/src/basic/process-util.c @@ -7,6 +7,7 @@ #include <limits.h> #include <linux/oom.h> #include <pthread.h> +#include <spawn.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> @@ -26,6 +27,7 @@ #include "alloc-util.h" #include "architecture.h" #include "argv-util.h" +#include "dirent-util.h" #include "env-file.h" #include "env-util.h" #include "errno-util.h" @@ -97,7 +99,7 @@ static int get_process_state(pid_t pid) { return (unsigned char) state; } -int get_process_comm(pid_t pid, char **ret) { +int pid_get_comm(pid_t pid, char **ret) { _cleanup_free_ char *escaped = NULL, *comm = NULL; int r; @@ -135,15 +137,35 @@ int get_process_comm(pid_t pid, char **ret) { return 0; } -static int get_process_cmdline_nulstr( +int pidref_get_comm(const PidRef *pid, char **ret) { + _cleanup_free_ char *comm = NULL; + int r; + + if (!pidref_is_set(pid)) + return -ESRCH; + + r = pid_get_comm(pid->pid, &comm); + if (r < 0) + return r; + + r = pidref_verify(pid); + if (r < 0) + return r; + + if (ret) + *ret = TAKE_PTR(comm); + return 0; +} + +static int pid_get_cmdline_nulstr( pid_t pid, size_t max_size, ProcessCmdlineFlags flags, char **ret, size_t *ret_size) { + _cleanup_free_ char *t = NULL; const char *p; - char *t; size_t k; int r; @@ -167,18 +189,17 @@ static int get_process_cmdline_nulstr( return r; if (k == 0) { - t = mfree(t); - if (!(flags & PROCESS_CMDLINE_COMM_FALLBACK)) return -ENOENT; /* Kernel threads have no argv[] */ _cleanup_free_ char *comm = NULL; - r = get_process_comm(pid, &comm); + r = pid_get_comm(pid, &comm); if (r < 0) return r; + free(t); t = strjoin("[", comm, "]"); if (!t) return -ENOMEM; @@ -189,12 +210,15 @@ static int get_process_cmdline_nulstr( t[max_size] = '\0'; } - *ret = t; - *ret_size = k; + if (ret) + *ret = TAKE_PTR(t); + if (ret_size) + *ret_size = k; + return r; } -int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret) { +int pid_get_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret) { _cleanup_free_ char *t = NULL; size_t k; char *ans; @@ -202,7 +226,7 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags assert(pid >= 0); assert(ret); - /* Retrieve and format a commandline. See above for discussion of retrieval options. + /* Retrieve and format a command line. See above for discussion of retrieval options. * * There are two main formatting modes: * @@ -216,7 +240,7 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and * PROCESS_CMDLINE_COMM_FALLBACK is not specified). Returns 0 and sets *line otherwise. */ - int full = get_process_cmdline_nulstr(pid, max_columns, flags, &t, &k); + int full = pid_get_cmdline_nulstr(pid, max_columns, flags, &t, &k); if (full < 0) return full; @@ -259,7 +283,27 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags return 0; } -int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) { +int pidref_get_cmdline(const PidRef *pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret) { + _cleanup_free_ char *s = NULL; + int r; + + if (!pidref_is_set(pid)) + return -ESRCH; + + r = pid_get_cmdline(pid->pid, max_columns, flags, &s); + if (r < 0) + return r; + + r = pidref_verify(pid); + if (r < 0) + return r; + + if (ret) + *ret = TAKE_PTR(s); + return 0; +} + +int pid_get_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) { _cleanup_free_ char *t = NULL; char **args; size_t k; @@ -269,7 +313,7 @@ int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) assert((flags & ~PROCESS_CMDLINE_COMM_FALLBACK) == 0); assert(ret); - r = get_process_cmdline_nulstr(pid, SIZE_MAX, flags, &t, &k); + r = pid_get_cmdline_nulstr(pid, SIZE_MAX, flags, &t, &k); if (r < 0) return r; @@ -281,6 +325,27 @@ int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) return 0; } +int pidref_get_cmdline_strv(const PidRef *pid, ProcessCmdlineFlags flags, char ***ret) { + _cleanup_strv_free_ char **args = NULL; + int r; + + if (!pidref_is_set(pid)) + return -ESRCH; + + r = pid_get_cmdline_strv(pid->pid, flags, &args); + if (r < 0) + return r; + + r = pidref_verify(pid); + if (r < 0) + return r; + + if (ret) + *ret = TAKE_PTR(args); + + return 0; +} + int container_get_leader(const char *machine, pid_t *pid) { _cleanup_free_ char *s = NULL, *class = NULL; const char *p; @@ -322,7 +387,34 @@ int container_get_leader(const char *machine, pid_t *pid) { return 0; } -int is_kernel_thread(pid_t pid) { +int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret) { + int r; + + assert(ret); + + for (;;) { + pid_t ppid; + + r = get_process_ppid(pid, &ppid); + if (r < 0) + return r; + + r = in_same_namespace(pid, ppid, type); + if (r < 0) + return r; + if (r == 0) { + /* If the parent and the child are not in the same + * namespace, then the child is the leader we are + * looking for. */ + *ret = pid; + return 0; + } + + pid = ppid; + } +} + +int pid_is_kernel_thread(pid_t pid) { _cleanup_free_ char *line = NULL; unsigned long long flags; size_t l, i; @@ -380,6 +472,23 @@ int is_kernel_thread(pid_t pid) { return !!(flags & PF_KTHREAD); } +int pidref_is_kernel_thread(const PidRef *pid) { + int result, r; + + if (!pidref_is_set(pid)) + return -ESRCH; + + result = pid_is_kernel_thread(pid->pid); + if (result < 0) + return result; + + r = pidref_verify(pid); /* Verify that the PID wasn't reused since */ + if (r < 0) + return r; + + return result; +} + int get_process_capeff(pid_t pid, char **ret) { const char *p; int r; @@ -449,16 +558,14 @@ static int get_process_id(pid_t pid, const char *field, uid_t *ret) { _cleanup_free_ char *line = NULL; char *l; - r = read_line(f, LONG_LINE_MAX, &line); + r = read_stripped_line(f, LONG_LINE_MAX, &line); if (r < 0) return r; if (r == 0) break; - l = strstrip(line); - - if (startswith(l, field)) { - l += strlen(field); + l = startswith(line, field); + if (l) { l += strspn(l, WHITESPACE); l[strcspn(l, WHITESPACE)] = 0; @@ -470,7 +577,8 @@ static int get_process_id(pid_t pid, const char *field, uid_t *ret) { return -EIO; } -int get_process_uid(pid_t pid, uid_t *ret) { +int pid_get_uid(pid_t pid, uid_t *ret) { + assert(ret); if (pid == 0 || pid == getpid_cached()) { *ret = getuid(); @@ -480,6 +588,26 @@ int get_process_uid(pid_t pid, uid_t *ret) { return get_process_id(pid, "Uid:", ret); } +int pidref_get_uid(const PidRef *pid, uid_t *ret) { + uid_t uid; + int r; + + if (!pidref_is_set(pid)) + return -ESRCH; + + r = pid_get_uid(pid->pid, &uid); + if (r < 0) + return r; + + r = pidref_verify(pid); + if (r < 0) + return r; + + if (ret) + *ret = uid; + return 0; +} + int get_process_gid(pid_t pid, gid_t *ret) { if (pid == 0 || pid == getpid_cached()) { @@ -606,6 +734,82 @@ int get_process_ppid(pid_t pid, pid_t *ret) { return 0; } +int pid_get_start_time(pid_t pid, uint64_t *ret) { + _cleanup_free_ char *line = NULL; + const char *p; + int r; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "stat"); + r = read_one_line_file(p, &line); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + /* Let's skip the pid and comm fields. The latter is enclosed in () but does not escape any () in its + * value, so let's skip over it manually */ + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + unsigned long llu; + + if (sscanf(p, " " + "%*c " /* state */ + "%*u " /* ppid */ + "%*u " /* pgrp */ + "%*u " /* session */ + "%*u " /* tty_nr */ + "%*u " /* tpgid */ + "%*u " /* flags */ + "%*u " /* minflt */ + "%*u " /* cminflt */ + "%*u " /* majflt */ + "%*u " /* cmajflt */ + "%*u " /* utime */ + "%*u " /* stime */ + "%*u " /* cutime */ + "%*u " /* cstime */ + "%*i " /* priority */ + "%*i " /* nice */ + "%*u " /* num_threads */ + "%*u " /* itrealvalue */ + "%lu ", /* starttime */ + &llu) != 1) + return -EIO; + + if (ret) + *ret = llu; + + return 0; +} + +int pidref_get_start_time(const PidRef *pid, uint64_t *ret) { + uint64_t t; + int r; + + if (!pidref_is_set(pid)) + return -ESRCH; + + r = pid_get_start_time(pid->pid, ret ? &t : NULL); + if (r < 0) + return r; + + r = pidref_verify(pid); + if (r < 0) + return r; + + if (ret) + *ret = t; + + return 0; +} + int get_process_umask(pid_t pid, mode_t *ret) { _cleanup_free_ char *m = NULL; const char *p; @@ -670,7 +874,7 @@ int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags) { assert(pid > 1); if (!name) { - r = get_process_comm(pid, &buffer); + r = pid_get_comm(pid, &buffer); if (r < 0) log_debug_errno(r, "Failed to acquire process name of " PID_FMT ", ignoring: %m", pid); else @@ -824,7 +1028,7 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { _cleanup_fclose_ FILE *f = NULL; char *value = NULL; const char *path; - size_t l, sum = 0; + size_t sum = 0; int r; assert(pid >= 0); @@ -859,9 +1063,9 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { if (r < 0) return r; - l = strlen(field); for (;;) { _cleanup_free_ char *line = NULL; + const char *match; if (sum > ENVIRONMENT_BLOCK_MAX) /* Give up searching eventually */ return -ENOBUFS; @@ -874,8 +1078,9 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { sum += r; - if (strneq(line, field, l) && line[l] == '=') { - value = strdup(line + l + 1); + match = startswith(line, field); + if (match && *match == '=') { + value = strdup(match + 1); if (!value) return -ENOMEM; @@ -892,6 +1097,9 @@ int pid_is_my_child(pid_t pid) { pid_t ppid; int r; + if (pid < 0) + return -ESRCH; + if (pid <= 1) return false; @@ -902,11 +1110,28 @@ int pid_is_my_child(pid_t pid) { return ppid == getpid_cached(); } -bool pid_is_unwaited(pid_t pid) { +int pidref_is_my_child(const PidRef *pid) { + int r, result; + + if (!pidref_is_set(pid)) + return -ESRCH; + + result = pid_is_my_child(pid->pid); + if (result < 0) + return result; + + r = pidref_verify(pid); + if (r < 0) + return r; + + return result; +} + +int pid_is_unwaited(pid_t pid) { /* Checks whether a PID is still valid at all, including a zombie */ if (pid < 0) - return false; + return -ESRCH; if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */ return true; @@ -920,13 +1145,31 @@ bool pid_is_unwaited(pid_t pid) { return errno != ESRCH; } -bool pid_is_alive(pid_t pid) { +int pidref_is_unwaited(const PidRef *pid) { + int r; + + if (!pidref_is_set(pid)) + return -ESRCH; + + if (pid->pid == 1 || pidref_is_self(pid)) + return true; + + r = pidref_kill(pid, 0); + if (r == -ESRCH) + return false; + if (r < 0) + return r; + + return true; +} + +int pid_is_alive(pid_t pid) { int r; /* Checks whether a PID is still valid and not a zombie */ if (pid < 0) - return false; + return -ESRCH; if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */ return true; @@ -935,10 +1178,33 @@ bool pid_is_alive(pid_t pid) { return true; r = get_process_state(pid); - if (IN_SET(r, -ESRCH, 'Z')) + if (r == -ESRCH) return false; + if (r < 0) + return r; - return true; + return r != 'Z'; +} + +int pidref_is_alive(const PidRef *pidref) { + int r, result; + + if (!pidref_is_set(pidref)) + return -ESRCH; + + result = pid_is_alive(pidref->pid); + if (result < 0) { + assert(result != -ESRCH); + return result; + } + + r = pidref_verify(pidref); + if (r == -ESRCH) + return false; + if (r < 0) + return r; + + return result; } int pid_from_same_root_fs(pid_t pid) { @@ -1057,7 +1323,10 @@ void valgrind_summary_hack(void) { pid_t pid; pid = raw_clone(SIGCHLD); if (pid < 0) - log_emergency_errno(errno, "Failed to fork off valgrind helper: %m"); + log_struct_errno( + LOG_EMERG, errno, + "MESSAGE_ID=" SD_MESSAGE_VALGRIND_HELPER_FORK_STR, + LOG_MESSAGE( "Failed to fork off valgrind helper: %m")); else if (pid == 0) exit(EXIT_SUCCESS); else { @@ -1104,7 +1373,7 @@ pid_t getpid_cached(void) { * https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=c579f48edba88380635ab98cb612030e3ed8691e */ - __atomic_compare_exchange_n( + (void) __atomic_compare_exchange_n( &cached_pid, ¤t_value, CACHED_PID_BUSY, @@ -1159,6 +1428,51 @@ static void restore_sigsetp(sigset_t **ssp) { (void) sigprocmask(SIG_SETMASK, *ssp, NULL); } +pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata) { + size_t ps; + pid_t pid; + void *mystack; + + /* A wrapper around glibc's clone() call that automatically sets up a "nested" stack. Only supports + * invocations without CLONE_VM, so that we can continue to use the parent's stack mapping. + * + * Note: glibc's clone() wrapper does not synchronize malloc() locks. This means that if the parent + * is threaded these locks will be in an undefined state in the child, and hence memory allocations + * are likely going to run into deadlocks. Hence: if you use this function make sure your parent is + * strictly single-threaded or your child never calls malloc(). */ + + assert((flags & (CLONE_VM|CLONE_PARENT_SETTID|CLONE_CHILD_SETTID| + CLONE_CHILD_CLEARTID|CLONE_SETTLS)) == 0); + + /* We allocate some space on the stack to use as the stack for the child (hence "nested"). Note that + * the net effect is that the child will have the start of its stack inside the stack of the parent, + * but since they are a CoW copy of each other that's fine. We allocate one page-aligned page. But + * since we don't want to deal with differences between systems where the stack grows backwards or + * forwards we'll allocate one more and place the stack address in the middle. Except that we also + * want it page aligned, hence we'll allocate one page more. Makes 3. */ + + ps = page_size(); + mystack = alloca(ps*3); + mystack = (uint8_t*) mystack + ps; /* move pointer one page ahead since stacks usually grow backwards */ + mystack = (void*) ALIGN_TO((uintptr_t) mystack, ps); /* align to page size (moving things further ahead) */ + +#if HAVE_CLONE + pid = clone(fn, mystack, flags, userdata); +#else + pid = __clone2(fn, mystack, ps, flags, userdata); +#endif + if (pid < 0) + return -errno; + + return pid; +} + +static int fork_flags_to_signal(ForkFlags flags) { + return (flags & FORK_DEATHSIG_SIGTERM) ? SIGTERM : + (flags & FORK_DEATHSIG_SIGINT) ? SIGINT : + SIGKILL; +} + int safe_fork_full( const char *name, const int stdio_fds[3], @@ -1170,9 +1484,12 @@ int safe_fork_full( pid_t original_pid, pid; sigset_t saved_ss, ss; _unused_ _cleanup_(restore_sigsetp) sigset_t *saved_ssp = NULL; - bool block_signals = false, block_all = false; + bool block_signals = false, block_all = false, intermediary = false; int prio, r; + assert(!FLAGS_SET(flags, FORK_DETACH) || !ret_pid); + assert(!FLAGS_SET(flags, FORK_DETACH|FORK_WAIT)); + /* A wrapper around fork(), that does a couple of important initializations in addition to mere forking. Always * returns the child's PID in *ret_pid. Returns == 0 in the child, and > 0 in the parent. */ @@ -1185,9 +1502,10 @@ int safe_fork_full( fflush(stderr); /* This one shouldn't be necessary, stderr should be unbuffered anyway, but let's better be safe than sorry */ } - if (flags & (FORK_RESET_SIGNALS|FORK_DEATHSIG)) { - /* We temporarily block all signals, so that the new child has them blocked initially. This way, we can - * be sure that SIGTERMs are not lost we might send to the child. */ + if (flags & (FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGINT)) { + /* We temporarily block all signals, so that the new child has them blocked initially. This + * way, we can be sure that SIGTERMs are not lost we might send to the child. (Note that for + * FORK_DEATHSIG_SIGKILL we don't bother, since it cannot be blocked anyway.) */ assert_se(sigfillset(&ss) >= 0); block_signals = block_all = true; @@ -1206,17 +1524,47 @@ int safe_fork_full( saved_ssp = &saved_ss; } - if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS)) != 0) + if (FLAGS_SET(flags, FORK_DETACH)) { + assert(!FLAGS_SET(flags, FORK_WAIT)); + assert(!ret_pid); + + /* Fork off intermediary child if needed */ + + r = is_reaper_process(); + if (r < 0) + return log_full_errno(prio, r, "Failed to determine if we are a reaper process: %m"); + + if (!r) { + /* Not a reaper process, hence do a double fork() so we are reparented to one */ + + pid = fork(); + if (pid < 0) + return log_full_errno(prio, errno, "Failed to fork off '%s': %m", strna(name)); + if (pid > 0) { + log_debug("Successfully forked off intermediary '%s' as PID " PID_FMT ".", strna(name), pid); + return 1; /* return in the parent */ + } + + intermediary = true; + } + } + + if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS|FORK_NEW_NETNS)) != 0) pid = raw_clone(SIGCHLD| (FLAGS_SET(flags, FORK_NEW_MOUNTNS) ? CLONE_NEWNS : 0) | - (FLAGS_SET(flags, FORK_NEW_USERNS) ? CLONE_NEWUSER : 0)); + (FLAGS_SET(flags, FORK_NEW_USERNS) ? CLONE_NEWUSER : 0) | + (FLAGS_SET(flags, FORK_NEW_NETNS) ? CLONE_NEWNET : 0)); else pid = fork(); if (pid < 0) return log_full_errno(prio, errno, "Failed to fork off '%s': %m", strna(name)); if (pid > 0) { - /* We are in the parent process */ + /* If we are in the intermediary process, exit now */ + if (intermediary) + _exit(EXIT_SUCCESS); + + /* We are in the parent process */ log_debug("Successfully forked off '%s' as PID " PID_FMT ".", strna(name), pid); if (flags & FORK_WAIT) { @@ -1259,8 +1607,8 @@ int safe_fork_full( r, "Failed to rename process, ignoring: %m"); } - if (flags & (FORK_DEATHSIG|FORK_DEATHSIG_SIGINT)) - if (prctl(PR_SET_PDEATHSIG, (flags & FORK_DEATHSIG_SIGINT) ? SIGINT : SIGTERM) < 0) { + if (flags & (FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGINT|FORK_DEATHSIG_SIGKILL)) + if (prctl(PR_SET_PDEATHSIG, fork_flags_to_signal(flags)) < 0) { log_full_errno(prio, errno, "Failed to set death signal: %m"); _exit(EXIT_FAILURE); } @@ -1285,7 +1633,7 @@ int safe_fork_full( } } - if (flags & FORK_DEATHSIG) { + if (flags & (FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGKILL|FORK_DEATHSIG_SIGINT)) { pid_t ppid; /* Let's see if the parent PID is still the one we started from? If not, then the parent * already died by the time we set PR_SET_PDEATHSIG, hence let's emulate the effect */ @@ -1294,8 +1642,9 @@ int safe_fork_full( if (ppid == 0) /* Parent is in a different PID namespace. */; else if (ppid != original_pid) { - log_debug("Parent died early, raising SIGTERM."); - (void) raise(SIGTERM); + int sig = fork_flags_to_signal(flags); + log_debug("Parent died early, raising %s.", signal_to_string(sig)); + (void) raise(sig); _exit(EXIT_FAILURE); } } @@ -1328,6 +1677,9 @@ int safe_fork_full( log_full_errno(prio, r, "Failed to rearrange stdio fds: %m"); _exit(EXIT_FAILURE); } + + /* Turn off O_NONBLOCK on the fdio fds, in case it was left on */ + stdio_disable_nonblock(); } else { r = make_null_stdio(); if (r < 0) { @@ -1389,6 +1741,30 @@ int safe_fork_full( return 0; } +int pidref_safe_fork_full( + const char *name, + const int stdio_fds[3], + const int except_fds[], + size_t n_except_fds, + ForkFlags flags, + PidRef *ret_pid) { + + pid_t pid; + int r, q; + + assert(!FLAGS_SET(flags, FORK_WAIT)); + + r = safe_fork_full(name, stdio_fds, except_fds, n_except_fds, flags, &pid); + if (r < 0) + return r; + + q = pidref_set_pid(ret_pid, pid); + if (q < 0) /* Let's not fail for this, no matter what, the process exists after all, and that's key */ + *ret_pid = PIDREF_MAKE_FROM_PID(pid); + + return r; +} + int namespace_fork( const char *outer_name, const char *inner_name, @@ -1411,7 +1787,7 @@ int namespace_fork( r = safe_fork_full(outer_name, NULL, except_fds, n_except_fds, - (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid); + (flags|FORK_DEATHSIG_SIGINT|FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGKILL) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid); if (r < 0) return r; if (r == 0) { @@ -1633,6 +2009,212 @@ int get_process_threads(pid_t pid) { return n; } +int is_reaper_process(void) { + int b = 0; + + /* Checks if we are running in a reaper process, i.e. if we are expected to deal with processes + * reparented to us. This simply checks if we are PID 1 or if PR_SET_CHILD_SUBREAPER was called. */ + + if (getpid_cached() == 1) + return true; + + if (prctl(PR_GET_CHILD_SUBREAPER, (unsigned long) &b, 0UL, 0UL, 0UL) < 0) + return -errno; + + return b != 0; +} + +int make_reaper_process(bool b) { + + if (getpid_cached() == 1) { + + if (!b) + return -EINVAL; + + return 0; + } + + /* Some prctl()s insist that all 5 arguments are specified, others do not. Let's always specify all, + * to avoid any ambiguities */ + if (prctl(PR_SET_CHILD_SUBREAPER, (unsigned long) b, 0UL, 0UL, 0UL) < 0) + return -errno; + + return 0; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(posix_spawnattr_t*, posix_spawnattr_destroy, NULL); + +int posix_spawn_wrapper( + const char *path, + char * const *argv, + char * const *envp, + const char *cgroup, + PidRef *ret_pidref) { + + short flags = POSIX_SPAWN_SETSIGMASK|POSIX_SPAWN_SETSIGDEF; + posix_spawnattr_t attr; + sigset_t mask; + int r; + + /* Forks and invokes 'path' with 'argv' and 'envp' using CLONE_VM and CLONE_VFORK, which means the + * caller will be blocked until the child either exits or exec's. The memory of the child will be + * fully shared with the memory of the parent, so that there are no copy-on-write or memory.max + * issues. + * + * Also, move the newly-created process into 'cgroup' through POSIX_SPAWN_SETCGROUP (clone3()) + * if available. Note that CLONE_INTO_CGROUP is only supported on cgroup v2. + * returns 1: We're already in the right cgroup + * 0: 'cgroup' not specified or POSIX_SPAWN_SETCGROUP is not supported. The caller + * needs to call 'cg_attach' on their own */ + + assert(path); + assert(argv); + assert(ret_pidref); + + assert_se(sigfillset(&mask) >= 0); + + r = posix_spawnattr_init(&attr); + if (r != 0) + return -r; /* These functions return a positive errno on failure */ + + /* Initialization needs to succeed before we can set up a destructor. */ + _unused_ _cleanup_(posix_spawnattr_destroyp) posix_spawnattr_t *attr_destructor = &attr; + +#if HAVE_PIDFD_SPAWN + _cleanup_close_ int cgroup_fd = -EBADF; + + if (cgroup) { + _cleanup_free_ char *resolved_cgroup = NULL; + + r = cg_get_path_and_check( + SYSTEMD_CGROUP_CONTROLLER, + cgroup, + /* suffix= */ NULL, + &resolved_cgroup); + if (r < 0) + return r; + + cgroup_fd = open(resolved_cgroup, O_PATH|O_DIRECTORY|O_CLOEXEC); + if (cgroup_fd < 0) + return -errno; + + r = posix_spawnattr_setcgroup_np(&attr, cgroup_fd); + if (r != 0) + return -r; + + flags |= POSIX_SPAWN_SETCGROUP; + } +#endif + + r = posix_spawnattr_setflags(&attr, flags); + if (r != 0) + return -r; + r = posix_spawnattr_setsigmask(&attr, &mask); + if (r != 0) + return -r; + +#if HAVE_PIDFD_SPAWN + _cleanup_close_ int pidfd = -EBADF; + + r = pidfd_spawn(&pidfd, path, NULL, &attr, argv, envp); + if (r == 0) { + r = pidref_set_pidfd_consume(ret_pidref, TAKE_FD(pidfd)); + if (r < 0) + return r; + + return FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP); + } + if (!(ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_PRIVILEGE(r))) + return -r; + + /* Compiled on a newer host, or seccomp&friends blocking clone3()? Fallback, but need to change the + * flags to remove the cgroup one, which is what redirects to clone3() */ + flags &= ~POSIX_SPAWN_SETCGROUP; + r = posix_spawnattr_setflags(&attr, flags); + if (r != 0) + return -r; +#endif + + pid_t pid; + r = posix_spawn(&pid, path, NULL, &attr, argv, envp); + if (r != 0) + return -r; + + r = pidref_set_pid(ret_pidref, pid); + if (r < 0) + return r; + + return 0; /* We did not use CLONE_INTO_CGROUP so return 0, the caller will have to move the child */ +} + +int proc_dir_open(DIR **ret) { + DIR *d; + + assert(ret); + + d = opendir("/proc"); + if (!d) + return -errno; + + *ret = d; + return 0; +} + +int proc_dir_read(DIR *d, pid_t *ret) { + assert(d); + + for (;;) { + struct dirent *de; + + errno = 0; + de = readdir_no_dot(d); + if (!de) { + if (errno != 0) + return -errno; + + break; + } + + if (!IN_SET(de->d_type, DT_DIR, DT_UNKNOWN)) + continue; + + if (parse_pid(de->d_name, ret) >= 0) + return 1; + } + + if (ret) + *ret = 0; + return 0; +} + +int proc_dir_read_pidref(DIR *d, PidRef *ret) { + int r; + + assert(d); + + for (;;) { + pid_t pid; + + r = proc_dir_read(d, &pid); + if (r < 0) + return r; + if (r == 0) + break; + + r = pidref_set_pid(ret, pid); + if (r == -ESRCH) /* gone by now? skip it */ + continue; + if (r < 0) + return r; + + return 1; + } + + if (ret) + *ret = PIDREF_NULL; + return 0; +} + static const char *const sigchld_code_table[] = { [CLD_EXITED] = "exited", [CLD_KILLED] = "killed", diff --git a/src/libnm-systemd-shared/src/basic/process-util.h b/src/libnm-systemd-shared/src/basic/process-util.h index 5cf5c7c6ec..a75c44cfad 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.h +++ b/src/libnm-systemd-shared/src/basic/process-util.h @@ -14,6 +14,7 @@ #include "alloc-util.h" #include "format-util.h" #include "macro.h" +#include "namespace-util.h" #include "time-util.h" #define procfs_file_alloca(pid, field) \ @@ -38,21 +39,29 @@ typedef enum ProcessCmdlineFlags { PROCESS_CMDLINE_QUOTE_POSIX = 1 << 3, } ProcessCmdlineFlags; -int get_process_comm(pid_t pid, char **ret); -int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret); -int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret); +int pid_get_comm(pid_t pid, char **ret); +int pidref_get_comm(const PidRef *pid, char **ret); +int pid_get_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret); +int pidref_get_cmdline(const PidRef *pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret); +int pid_get_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret); +int pidref_get_cmdline_strv(const PidRef *pid, ProcessCmdlineFlags flags, char ***ret); int get_process_exe(pid_t pid, char **ret); -int get_process_uid(pid_t pid, uid_t *ret); +int pid_get_uid(pid_t pid, uid_t *ret); +int pidref_get_uid(const PidRef *pid, uid_t *ret); int get_process_gid(pid_t pid, gid_t *ret); int get_process_capeff(pid_t pid, char **ret); int get_process_cwd(pid_t pid, char **ret); int get_process_root(pid_t pid, char **ret); int get_process_environ(pid_t pid, char **ret); int get_process_ppid(pid_t pid, pid_t *ret); +int pid_get_start_time(pid_t pid, uint64_t *ret); +int pidref_get_start_time(const PidRef* pid, uint64_t *ret); int get_process_umask(pid_t pid, mode_t *ret); int container_get_leader(const char *machine, pid_t *pid); +int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret); + int wait_for_terminate(pid_t pid, siginfo_t *status); typedef enum WaitFlags { @@ -74,13 +83,17 @@ void sigkill_nowaitp(pid_t *pid); int kill_and_sigcont(pid_t pid, int sig); -int is_kernel_thread(pid_t pid); +int pid_is_kernel_thread(pid_t pid); +int pidref_is_kernel_thread(const PidRef *pid); int getenv_for_pid(pid_t pid, const char *field, char **_value); -bool pid_is_alive(pid_t pid); -bool pid_is_unwaited(pid_t pid); +int pid_is_alive(pid_t pid); +int pidref_is_alive(const PidRef *pidref); +int pid_is_unwaited(pid_t pid); +int pidref_is_unwaited(const PidRef *pidref); int pid_is_my_child(pid_t pid); +int pidref_is_my_child(const PidRef *pidref); int pid_from_same_root_fs(pid_t pid); bool is_main_thread(void); @@ -141,24 +154,34 @@ void reset_cached_pid(void); int must_be_root(void); +pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata); + +/* 💣 Note that FORK_NEW_USERNS, FORK_NEW_MOUNTNS, or FORK_NEW_NETNS should not be called in threaded + * programs, because they cause us to use raw_clone() which does not synchronize the glibc malloc() locks, + * and thus will cause deadlocks if the parent uses threads and the child does memory allocations. Hence: if + * the parent is threaded these flags may not be used. These flags cannot be used if the parent uses threads + * or the child uses malloc(). 💣 */ typedef enum ForkFlags { FORK_RESET_SIGNALS = 1 << 0, /* Reset all signal handlers and signal mask */ FORK_CLOSE_ALL_FDS = 1 << 1, /* Close all open file descriptors in the child, except for 0,1,2 */ - FORK_DEATHSIG = 1 << 2, /* Set PR_DEATHSIG in the child to SIGTERM */ + FORK_DEATHSIG_SIGTERM = 1 << 2, /* Set PR_DEATHSIG in the child to SIGTERM */ FORK_DEATHSIG_SIGINT = 1 << 3, /* Set PR_DEATHSIG in the child to SIGINT */ - FORK_REARRANGE_STDIO = 1 << 4, /* Connect 0,1,2 to specified fds or /dev/null */ - FORK_REOPEN_LOG = 1 << 5, /* Reopen log connection */ - FORK_LOG = 1 << 6, /* Log above LOG_DEBUG log level about failures */ - FORK_WAIT = 1 << 7, /* Wait until child exited */ - FORK_NEW_MOUNTNS = 1 << 8, /* Run child in its own mount namespace */ - FORK_MOUNTNS_SLAVE = 1 << 9, /* Make child's mount namespace MS_SLAVE */ - FORK_PRIVATE_TMP = 1 << 10, /* Mount new /tmp/ in the child (combine with FORK_NEW_MOUNTNS!) */ - FORK_RLIMIT_NOFILE_SAFE = 1 << 11, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */ - FORK_STDOUT_TO_STDERR = 1 << 12, /* Make stdout a copy of stderr */ - FORK_FLUSH_STDIO = 1 << 13, /* fflush() stdout (and stderr) before forking */ - FORK_NEW_USERNS = 1 << 14, /* Run child in its own user namespace */ - FORK_CLOEXEC_OFF = 1 << 15, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */ - FORK_KEEP_NOTIFY_SOCKET = 1 << 16, /* Unless this specified, $NOTIFY_SOCKET will be unset. */ + FORK_DEATHSIG_SIGKILL = 1 << 4, /* Set PR_DEATHSIG in the child to SIGKILL */ + FORK_REARRANGE_STDIO = 1 << 5, /* Connect 0,1,2 to specified fds or /dev/null */ + FORK_REOPEN_LOG = 1 << 6, /* Reopen log connection */ + FORK_LOG = 1 << 7, /* Log above LOG_DEBUG log level about failures */ + FORK_WAIT = 1 << 8, /* Wait until child exited */ + FORK_NEW_MOUNTNS = 1 << 9, /* Run child in its own mount namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ + FORK_MOUNTNS_SLAVE = 1 << 10, /* Make child's mount namespace MS_SLAVE */ + FORK_PRIVATE_TMP = 1 << 11, /* Mount new /tmp/ in the child (combine with FORK_NEW_MOUNTNS!) */ + FORK_RLIMIT_NOFILE_SAFE = 1 << 12, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */ + FORK_STDOUT_TO_STDERR = 1 << 13, /* Make stdout a copy of stderr */ + FORK_FLUSH_STDIO = 1 << 14, /* fflush() stdout (and stderr) before forking */ + FORK_NEW_USERNS = 1 << 15, /* Run child in its own user namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ + FORK_CLOEXEC_OFF = 1 << 16, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */ + FORK_KEEP_NOTIFY_SOCKET = 1 << 17, /* Unless this specified, $NOTIFY_SOCKET will be unset. */ + FORK_DETACH = 1 << 18, /* Double fork if needed to ensure PID1/subreaper is parent */ + FORK_NEW_NETNS = 1 << 19, /* Run child in its own network namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ } ForkFlags; int safe_fork_full( @@ -173,6 +196,18 @@ static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) { return safe_fork_full(name, NULL, NULL, 0, flags, ret_pid); } +int pidref_safe_fork_full( + const char *name, + const int stdio_fds[3], + const int except_fds[], + size_t n_except_fds, + ForkFlags flags, + PidRef *ret_pid); + +static inline int pidref_safe_fork(const char *name, ForkFlags flags, PidRef *ret_pid) { + return pidref_safe_fork_full(name, NULL, NULL, 0, flags, ret_pid); +} + int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid); int set_oom_score_adjust(int value); @@ -201,3 +236,17 @@ int setpriority_closest(int priority); _noreturn_ void freeze(void); int get_process_threads(pid_t pid); + +int is_reaper_process(void); +int make_reaper_process(bool b); + +int posix_spawn_wrapper( + const char *path, + char * const *argv, + char * const *envp, + const char *cgroup, + PidRef *ret_pidref); + +int proc_dir_open(DIR **ret); +int proc_dir_read(DIR *d, pid_t *ret); +int proc_dir_read_pidref(DIR *d, PidRef *ret); diff --git a/src/libnm-systemd-shared/src/basic/random-util.c b/src/libnm-systemd-shared/src/basic/random-util.c index 934d5e2531..c7b9551646 100644 --- a/src/libnm-systemd-shared/src/basic/random-util.c +++ b/src/libnm-systemd-shared/src/basic/random-util.c @@ -6,7 +6,6 @@ #include <errno.h> #include <fcntl.h> #include <linux/random.h> -#include <pthread.h> #include <stdbool.h> #include <stdint.h> #include <stdlib.h> @@ -28,6 +27,7 @@ #include "missing_syscall.h" #include "missing_threads.h" #include "parse-util.h" +#include "process-util.h" #include "random-util.h" #include "sha256.h" #include "time-util.h" @@ -50,7 +50,7 @@ static void fallback_random_bytes(void *p, size_t n) { .call_id = fallback_counter++, .stamp_mono = now(CLOCK_MONOTONIC), .stamp_real = now(CLOCK_REALTIME), - .pid = getpid(), + .pid = getpid_cached(), .tid = gettid(), }; @@ -226,7 +226,7 @@ int random_write_entropy(int fd, const void *seed, size_t size, bool credit) { if (ioctl(fd, RNDADDENTROPY, info) < 0) return -errno; } else { - r = loop_write(fd, seed, size, false); + r = loop_write(fd, seed, size); if (r < 0) return r; } diff --git a/src/libnm-systemd-shared/src/basic/ratelimit.h b/src/libnm-systemd-shared/src/basic/ratelimit.h index bb7160a895..492ea3b48d 100644 --- a/src/libnm-systemd-shared/src/basic/ratelimit.h +++ b/src/libnm-systemd-shared/src/basic/ratelimit.h @@ -12,6 +12,8 @@ typedef struct RateLimit { usec_t begin; } RateLimit; +#define RATELIMIT_OFF (const RateLimit) { .interval = USEC_INFINITY, .burst = UINT_MAX } + static inline void ratelimit_reset(RateLimit *rl) { rl->num = rl->begin = 0; } diff --git a/src/libnm-systemd-shared/src/basic/signal-util.c b/src/libnm-systemd-shared/src/basic/signal-util.c index 270d397d50..4354edca7e 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.c +++ b/src/libnm-systemd-shared/src/basic/signal-util.c @@ -123,39 +123,39 @@ int sigprocmask_many(int how, sigset_t *old, ...) { } static const char *const static_signal_table[] = { - [SIGHUP] = "HUP", - [SIGINT] = "INT", - [SIGQUIT] = "QUIT", - [SIGILL] = "ILL", - [SIGTRAP] = "TRAP", - [SIGABRT] = "ABRT", - [SIGBUS] = "BUS", - [SIGFPE] = "FPE", - [SIGKILL] = "KILL", - [SIGUSR1] = "USR1", - [SIGSEGV] = "SEGV", - [SIGUSR2] = "USR2", - [SIGPIPE] = "PIPE", - [SIGALRM] = "ALRM", - [SIGTERM] = "TERM", + [SIGHUP] = "HUP", + [SIGINT] = "INT", + [SIGQUIT] = "QUIT", + [SIGILL] = "ILL", + [SIGTRAP] = "TRAP", + [SIGABRT] = "ABRT", + [SIGBUS] = "BUS", + [SIGFPE] = "FPE", + [SIGKILL] = "KILL", + [SIGUSR1] = "USR1", + [SIGSEGV] = "SEGV", + [SIGUSR2] = "USR2", + [SIGPIPE] = "PIPE", + [SIGALRM] = "ALRM", + [SIGTERM] = "TERM", #ifdef SIGSTKFLT [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ #endif - [SIGCHLD] = "CHLD", - [SIGCONT] = "CONT", - [SIGSTOP] = "STOP", - [SIGTSTP] = "TSTP", - [SIGTTIN] = "TTIN", - [SIGTTOU] = "TTOU", - [SIGURG] = "URG", - [SIGXCPU] = "XCPU", - [SIGXFSZ] = "XFSZ", + [SIGCHLD] = "CHLD", + [SIGCONT] = "CONT", + [SIGSTOP] = "STOP", + [SIGTSTP] = "TSTP", + [SIGTTIN] = "TTIN", + [SIGTTOU] = "TTOU", + [SIGURG] = "URG", + [SIGXCPU] = "XCPU", + [SIGXFSZ] = "XFSZ", [SIGVTALRM] = "VTALRM", - [SIGPROF] = "PROF", - [SIGWINCH] = "WINCH", - [SIGIO] = "IO", - [SIGPWR] = "PWR", - [SIGSYS] = "SYS" + [SIGPROF] = "PROF", + [SIGWINCH] = "WINCH", + [SIGIO] = "IO", + [SIGPWR] = "PWR", + [SIGSYS] = "SYS" }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP(static_signal, int); diff --git a/src/libnm-systemd-shared/src/basic/siphash24.h b/src/libnm-systemd-shared/src/basic/siphash24.h index e46f3cc5be..f9867630af 100644 --- a/src/libnm-systemd-shared/src/basic/siphash24.h +++ b/src/libnm-systemd-shared/src/basic/siphash24.h @@ -52,15 +52,16 @@ siphash24 (const void *in, size_t inlen, const uint8_t k[16]) void siphash24_init(struct siphash *state, const uint8_t k[static 16]); void siphash24_compress(const void *in, size_t inlen, struct siphash *state); #define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state)) +#define siphash24_compress_typesafe(in, state) \ + siphash24_compress(&(in), sizeof(typeof(in)), (state)) static inline void siphash24_compress_boolean(bool in, struct siphash *state) { - uint8_t i = in; - - siphash24_compress(&i, sizeof i, state); + siphash24_compress_byte(in, state); } static inline void siphash24_compress_usec_t(usec_t in, struct siphash *state) { - siphash24_compress(&in, sizeof in, state); + uint64_t u = htole64(in); + siphash24_compress_typesafe(u, state); } static inline void siphash24_compress_safe(const void *in, size_t inlen, struct siphash *state) { diff --git a/src/libnm-systemd-shared/src/basic/socket-util.c b/src/libnm-systemd-shared/src/basic/socket-util.c index 9b411e07a2..df3e2c17c2 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.c +++ b/src/libnm-systemd-shared/src/basic/socket-util.c @@ -46,6 +46,11 @@ # define IDN_FLAGS 0 #endif +/* From the kernel's include/net/scm.h */ +#ifndef SCM_MAX_FD +# define SCM_MAX_FD 253 +#endif + static const char* const socket_address_type_table[] = { [SOCK_STREAM] = "Stream", [SOCK_DGRAM] = "Datagram", @@ -547,7 +552,7 @@ int sockaddr_pretty( } else { if (path[path_len - 1] == '\0') /* We expect a terminating NUL and don't print it */ - path_len --; + path_len--; p = cescape_length(path, path_len); } @@ -628,28 +633,33 @@ int getsockname_pretty(int fd, char **ret) { return sockaddr_pretty(&sa.sa, salen, false, true, ret); } -int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) { +int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **ret) { + char host[NI_MAXHOST]; int r; - char host[NI_MAXHOST], *ret; - assert(_ret); + assert(sa); + assert(salen > sizeof(sa_family_t)); - r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, IDN_FLAGS); + r = getnameinfo(sa, salen, host, sizeof(host), /* service= */ NULL, /* service_len= */ 0, IDN_FLAGS); if (r != 0) { - int saved_errno = errno; + if (r == EAI_MEMORY) + return log_oom_debug(); + if (r == EAI_SYSTEM) + log_debug_errno(errno, "getnameinfo() failed, ignoring: %m"); + else + log_debug("getnameinfo() failed, ignoring: %s", gai_strerror(r)); - r = sockaddr_pretty(&sa->sa, salen, true, true, &ret); - if (r < 0) - return r; + return sockaddr_pretty(sa, salen, /* translate_ipv6= */ true, /* include_port= */ true, ret); + } - log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret); - } else { - ret = strdup(host); - if (!ret) + if (ret) { + char *copy = strdup(host); + if (!copy) return -ENOMEM; + + *ret = copy; } - *_ret = ret; return 0; } @@ -874,13 +884,11 @@ bool address_label_valid(const char *p) { int getpeercred(int fd, struct ucred *ucred) { socklen_t n = sizeof(struct ucred); struct ucred u; - int r; assert(fd >= 0); assert(ucred); - r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n); - if (r < 0) + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n) < 0) return -errno; if (n != sizeof(struct ucred)) @@ -909,8 +917,10 @@ int getpeersec(int fd, char **ret) { if (!s) return -ENOMEM; - if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0) + if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0) { + s[n] = 0; break; + } if (errno != ERANGE) return -errno; @@ -927,12 +937,16 @@ int getpeersec(int fd, char **ret) { } int getpeergroups(int fd, gid_t **ret) { - socklen_t n = sizeof(gid_t) * 64; + socklen_t n = sizeof(gid_t) * 64U; _cleanup_free_ gid_t *d = NULL; assert(fd >= 0); assert(ret); + long ngroups_max = sysconf(_SC_NGROUPS_MAX); + if (ngroups_max > 0) + n = MAX(n, sizeof(gid_t) * (socklen_t) ngroups_max); + for (;;) { d = malloc(n); if (!d) @@ -950,7 +964,7 @@ int getpeergroups(int fd, gid_t **ret) { assert_se(n % sizeof(gid_t) == 0); n /= sizeof(gid_t); - if ((socklen_t) (int) n != n) + if (n > INT_MAX) return -E2BIG; *ret = TAKE_PTR(d); @@ -958,6 +972,68 @@ int getpeergroups(int fd, gid_t **ret) { return (int) n; } +int getpeerpidfd(int fd) { + socklen_t n = sizeof(int); + int pidfd = -EBADF; + + assert(fd >= 0); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERPIDFD, &pidfd, &n) < 0) + return -errno; + + if (n != sizeof(int)) + return -EIO; + + return pidfd; +} + +ssize_t send_many_fds_iov_sa( + int transport_fd, + int *fds_array, size_t n_fds_array, + const struct iovec *iov, size_t iovlen, + const struct sockaddr *sa, socklen_t len, + int flags) { + + _cleanup_free_ struct cmsghdr *cmsg = NULL; + struct msghdr mh = { + .msg_name = (struct sockaddr*) sa, + .msg_namelen = len, + .msg_iov = (struct iovec *)iov, + .msg_iovlen = iovlen, + }; + ssize_t k; + + assert(transport_fd >= 0); + assert(fds_array || n_fds_array == 0); + + /* The kernel will reject sending more than SCM_MAX_FD FDs at once */ + if (n_fds_array > SCM_MAX_FD) + return -E2BIG; + + /* We need either an FD array or data to send. If there's nothing, return an error. */ + if (n_fds_array == 0 && !iov) + return -EINVAL; + + if (n_fds_array > 0) { + mh.msg_controllen = CMSG_SPACE(sizeof(int) * n_fds_array); + mh.msg_control = cmsg = malloc(mh.msg_controllen); + if (!cmsg) + return -ENOMEM; + + *cmsg = (struct cmsghdr) { + .cmsg_len = CMSG_LEN(sizeof(int) * n_fds_array), + .cmsg_level = SOL_SOCKET, + .cmsg_type = SCM_RIGHTS, + }; + memcpy(CMSG_DATA(cmsg), fds_array, sizeof(int) * n_fds_array); + } + k = sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags); + if (k < 0) + return (ssize_t) -errno; + + return k; +} + ssize_t send_one_fd_iov_sa( int transport_fd, int fd, @@ -1013,6 +1089,78 @@ int send_one_fd_sa( return (int) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, sa, len, flags); } +ssize_t receive_many_fds_iov( + int transport_fd, + struct iovec *iov, size_t iovlen, + int **ret_fds_array, size_t *ret_n_fds_array, + int flags) { + + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int) * SCM_MAX_FD)) control; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_iov = iov, + .msg_iovlen = iovlen, + }; + _cleanup_free_ int *fds_array = NULL; + size_t n_fds_array = 0; + struct cmsghdr *cmsg; + ssize_t k; + + assert(transport_fd >= 0); + assert(ret_fds_array); + assert(ret_n_fds_array); + + /* + * Receive many FDs via @transport_fd. We don't care for the transport-type. We retrieve all the FDs + * at once. This is best used in combination with send_many_fds(). + */ + + k = recvmsg_safe(transport_fd, &mh, MSG_CMSG_CLOEXEC | flags); + if (k < 0) + return k; + + CMSG_FOREACH(cmsg, &mh) + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + size_t n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + fds_array = GREEDY_REALLOC(fds_array, n_fds_array + n); + if (!fds_array) { + cmsg_close_all(&mh); + return -ENOMEM; + } + + memcpy(fds_array + n_fds_array, CMSG_TYPED_DATA(cmsg, int), sizeof(int) * n); + n_fds_array += n; + } + + if (n_fds_array == 0) { + cmsg_close_all(&mh); + + /* If didn't receive an FD or any data, return an error. */ + if (k == 0) + return -EIO; + } + + *ret_fds_array = TAKE_PTR(fds_array); + *ret_n_fds_array = n_fds_array; + + return k; +} + +int receive_many_fds(int transport_fd, int **ret_fds_array, size_t *ret_n_fds_array, int flags) { + ssize_t k; + + k = receive_many_fds_iov(transport_fd, NULL, 0, ret_fds_array, ret_n_fds_array, flags); + if (k == 0) + return 0; + + /* k must be negative, since receive_many_fds_iov() only returns a positive value if data was received + * through the iov. */ + assert(k < 0); + return (int) k; +} + ssize_t receive_one_fd_iov( int transport_fd, struct iovec *iov, size_t iovlen, @@ -1189,7 +1337,7 @@ void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, assert(buf_len > 0); /* This is similar to cmsg_find_data(), but copy the found data to buf. This should be typically used - * when reading possibly unaligned data such as timestamp, as time_t is 64bit and size_t is 32bit on + * when reading possibly unaligned data such as timestamp, as time_t is 64-bit and size_t is 32-bit on * RISCV32. See issue #27241. */ cmsg = cmsg_find(mh, level, type, CMSG_LEN(buf_len)); @@ -1532,6 +1680,50 @@ int socket_address_parse_unix(SocketAddress *ret_address, const char *s) { } #if 0 /* NM_IGNORED */ +int vsock_parse_port(const char *s, unsigned *ret) { + int r; + + assert(ret); + + if (!s) + return -EINVAL; + + unsigned u; + r = safe_atou(s, &u); + if (r < 0) + return r; + + /* Port 0 is apparently valid and not special in AF_VSOCK (unlike on IP). But VMADDR_PORT_ANY + * (UINT32_MAX) is. Hence refuse that. */ + + if (u == VMADDR_PORT_ANY) + return -EINVAL; + + *ret = u; + return 0; +} + +int vsock_parse_cid(const char *s, unsigned *ret) { + assert(ret); + + if (!s) + return -EINVAL; + + /* Parsed an AF_VSOCK "CID". This is a 32bit entity, and the usual type is "unsigned". We recognize + * the three special CIDs as strings, and otherwise parse the numeric CIDs. */ + + if (streq(s, "hypervisor")) + *ret = VMADDR_CID_HYPERVISOR; + else if (streq(s, "local")) + *ret = VMADDR_CID_LOCAL; + else if (streq(s, "host")) + *ret = VMADDR_CID_HOST; + else + return safe_atou(s, ret); + + return 0; +} + int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { /* AF_VSOCK socket in vsock:cid:port notation */ _cleanup_free_ char *n = NULL; @@ -1557,7 +1749,7 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { if (!e) return -EINVAL; - r = safe_atou(e+1, &port); + r = vsock_parse_port(e+1, &port); if (r < 0) return r; @@ -1568,15 +1760,15 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { if (isempty(n)) cid = VMADDR_CID_ANY; else { - r = safe_atou(n, &cid); + r = vsock_parse_cid(n, &cid); if (r < 0) return r; } *ret_address = (SocketAddress) { .sockaddr.vm = { - .svm_cid = cid, .svm_family = AF_VSOCK, + .svm_cid = cid, .svm_port = port, }, .type = type, @@ -1585,4 +1777,19 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { return 0; } + +int vsock_get_local_cid(unsigned *ret) { + _cleanup_close_ int vsock_fd = -EBADF; + + assert(ret); + + vsock_fd = open("/dev/vsock", O_RDONLY|O_CLOEXEC); + if (vsock_fd < 0) + return log_debug_errno(errno, "Failed to open /dev/vsock: %m"); + + if (ioctl(vsock_fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, ret) < 0) + return log_debug_errno(errno, "Failed to query local AF_VSOCK CID: %m"); + + return 0; +} #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/socket-util.h b/src/libnm-systemd-shared/src/basic/socket-util.h index 26f9636fa6..15c7d1c52f 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.h +++ b/src/libnm-systemd-shared/src/basic/socket-util.h @@ -115,7 +115,7 @@ int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ int getpeername_pretty(int fd, bool include_port, char **ret); int getsockname_pretty(int fd, char **ret); -int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret); +int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **_ret); const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_; SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_; @@ -154,7 +154,30 @@ bool address_label_valid(const char *p); int getpeercred(int fd, struct ucred *ucred); int getpeersec(int fd, char **ret); int getpeergroups(int fd, gid_t **ret); +int getpeerpidfd(int fd); +ssize_t send_many_fds_iov_sa( + int transport_fd, + int *fds_array, size_t n_fds_array, + const struct iovec *iov, size_t iovlen, + const struct sockaddr *sa, socklen_t len, + int flags); +static inline ssize_t send_many_fds_iov( + int transport_fd, + int *fds_array, size_t n_fds_array, + const struct iovec *iov, size_t iovlen, + int flags) { + + return send_many_fds_iov_sa(transport_fd, fds_array, n_fds_array, iov, iovlen, NULL, 0, flags); +} +static inline int send_many_fds( + int transport_fd, + int *fds_array, + size_t n_fds_array, + int flags) { + + return send_many_fds_iov_sa(transport_fd, fds_array, n_fds_array, NULL, 0, NULL, 0, flags); +} ssize_t send_one_fd_iov_sa( int transport_fd, int fd, @@ -169,6 +192,8 @@ int send_one_fd_sa(int transport_fd, #define send_one_fd(transport_fd, fd, flags) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, NULL, 0, flags) ssize_t receive_one_fd_iov(int transport_fd, struct iovec *iov, size_t iovlen, int flags, int *ret_fd); int receive_one_fd(int transport_fd, int flags); +ssize_t receive_many_fds_iov(int transport_fd, struct iovec *iov, size_t iovlen, int **ret_fds_array, size_t *ret_n_fds_array, int flags); +int receive_many_fds(int transport_fd, int **ret_fds_array, size_t *ret_n_fds_array, int flags); ssize_t next_datagram_size_fd(int fd); @@ -181,7 +206,7 @@ int flush_accept(int fd); * at compile time, that the requested type has a smaller or same alignment as 'struct cmsghdr', and one * during runtime, that the actual pointer matches the alignment too. This is supposed to catch cases such as * 'struct timeval' is embedded into 'struct cmsghdr' on architectures where the alignment of the former is 8 - * bytes (because of a 64bit time_t), but of the latter is 4 bytes (because size_t is 32bit), such as + * bytes (because of a 64-bit time_t), but of the latter is 4 bytes (because size_t is 32 bits), such as * riscv32. */ #define CMSG_TYPED_DATA(cmsg, type) \ ({ \ @@ -296,7 +321,7 @@ static inline int getsockopt_int(int fd, int level, int optname, int *ret) { int socket_bind_to_ifname(int fd, const char *ifname); int socket_bind_to_ifindex(int fd, int ifindex); -/* Define a 64bit version of timeval/timespec in any case, even on 32bit userspace. */ +/* Define a 64-bit version of timeval/timespec in any case, even on 32-bit userspace. */ struct timeval_large { uint64_t tvl_sec, tvl_usec; }; @@ -304,7 +329,7 @@ struct timespec_large { uint64_t tvl_sec, tvl_nsec; }; -/* glibc duplicates timespec/timeval on certain 32bit archs, once in 32bit and once in 64bit. +/* glibc duplicates timespec/timeval on certain 32-bit arches, once in 32-bit and once in 64-bit. * See __convert_scm_timestamps() in glibc source code. Hence, we need additional buffer space for them * to prevent from recvmsg_safe() returning -EXFULL. */ #define CMSG_SPACE_TIMEVAL \ @@ -353,6 +378,14 @@ int socket_get_mtu(int fd, int af, size_t *ret); int connect_unix_path(int fd, int dir_fd, const char *path); +static inline bool VSOCK_CID_IS_REGULAR(unsigned cid) { + /* 0, 1, 2, UINT32_MAX are special, refuse those */ + return cid > 2 && cid < UINT32_MAX; +} + +int vsock_parse_port(const char *s, unsigned *ret); +int vsock_parse_cid(const char *s, unsigned *ret); + /* Parses AF_UNIX and AF_VSOCK addresses. AF_INET[6] require some netlink calls, so it cannot be in * src/basic/ and is done from 'socket_local_address from src/shared/. Return -EPROTO in case of * protocol mismatch. */ @@ -365,3 +398,5 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s); * /proc/sys/net/core/somaxconn anyway, thus by setting this to unbounded we just make that sysctl file * authoritative. */ #define SOMAXCONN_DELUXE INT_MAX + +int vsock_get_local_cid(unsigned *ret); diff --git a/src/libnm-systemd-shared/src/basic/sort-util.h b/src/libnm-systemd-shared/src/basic/sort-util.h index 52d611b820..9c818bd747 100644 --- a/src/libnm-systemd-shared/src/basic/sort-util.h +++ b/src/libnm-systemd-shared/src/basic/sort-util.h @@ -18,7 +18,7 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, ({ \ const typeof((b)[0]) *_k = k; \ int (*_func_)(const typeof((b)[0])*, const typeof((b)[0])*, typeof(userdata)) = func; \ - xbsearch_r((const void*) _k, (b), (n), sizeof((b)[0]), (comparison_userdata_fn_t) _func_, userdata); \ + (typeof((b)[0])*) xbsearch_r((const void*) _k, (b), (n), sizeof((b)[0]), (comparison_userdata_fn_t) _func_, userdata); \ }) /** @@ -38,7 +38,7 @@ static inline void* bsearch_safe(const void *key, const void *base, ({ \ const typeof((b)[0]) *_k = k; \ int (*_func_)(const typeof((b)[0])*, const typeof((b)[0])*) = func; \ - bsearch_safe((const void*) _k, (b), (n), sizeof((b)[0]), (comparison_fn_t) _func_); \ + (typeof((b)[0])*) bsearch_safe((const void*) _k, (b), (n), sizeof((b)[0]), (comparison_fn_t) _func_); \ }) /** @@ -61,7 +61,6 @@ static inline void _qsort_safe(void *base, size_t nmemb, size_t size, comparison _qsort_safe((p), (n), sizeof((p)[0]), (comparison_fn_t) _func_); \ }) -#if 0 /* NM_IGNORED */ static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, comparison_userdata_fn_t compar, void *userdata) { if (nmemb <= 1) return; @@ -75,6 +74,6 @@ static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, compariso int (*_func_)(const typeof((p)[0])*, const typeof((p)[0])*, typeof(userdata)) = func; \ qsort_r_safe((p), (n), sizeof((p)[0]), (comparison_userdata_fn_t) _func_, userdata); \ }) -#endif /* NM_IGNORED */ int cmp_int(const int *a, const int *b); +int cmp_uint16(const uint16_t *a, const uint16_t *b); diff --git a/src/libnm-systemd-shared/src/basic/stat-util.c b/src/libnm-systemd-shared/src/basic/stat-util.c index a81ee468ff..b50544c9f3 100644 --- a/src/libnm-systemd-shared/src/basic/stat-util.c +++ b/src/libnm-systemd-shared/src/basic/stat-util.c @@ -193,14 +193,12 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl struct stat a, b; assert(fda >= 0 || fda == AT_FDCWD); - assert(filea); assert(fdb >= 0 || fdb == AT_FDCWD); - assert(fileb); - if (fstatat(fda, filea, &a, flags) < 0) + if (fstatat(fda, strempty(filea), &a, flags) < 0) return log_debug_errno(errno, "Cannot stat %s: %m", filea); - if (fstatat(fdb, fileb, &b, flags) < 0) + if (fstatat(fdb, strempty(fileb), &b, flags) < 0) return log_debug_errno(errno, "Cannot stat %s: %m", fileb); return stat_inode_same(&a, &b); @@ -405,21 +403,35 @@ bool statx_mount_same(const struct new_statx *a, const struct new_statx *b) { a->stx_dev_minor == b->stx_dev_minor; } +static bool is_statx_fatal_error(int err, int flags) { + assert(err < 0); + + /* If statx() is not supported or if we see EPERM (which might indicate seccomp filtering or so), + * let's do a fallback. Note that on EACCES we'll not fall back, since that is likely an indication of + * fs access issues, which we should propagate. */ + if (ERRNO_IS_NOT_SUPPORTED(err) || err == -EPERM) + return false; + + /* When unsupported flags are specified, glibc's fallback function returns -EINVAL. + * See statx_generic() in glibc. */ + if (err != -EINVAL) + return true; + + if ((flags & ~(AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | AT_STATX_SYNC_AS_STAT)) != 0) + return false; /* Unsupported flags are specified. Let's try to use our implementation. */ + + return true; +} + int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct statx *sx) { static bool avoid_statx = false; struct stat st; + int r; if (!avoid_statx) { - if (statx(dfd, path, flags, mask, sx) < 0) { - if (!ERRNO_IS_NOT_SUPPORTED(errno) && errno != EPERM) - return -errno; - - /* If statx() is not supported or if we see EPERM (which might indicate seccomp - * filtering or so), let's do a fallback. Not that on EACCES we'll not fall back, - * since that is likely an indication of fs access issues, which we should - * propagate */ - } else - return 0; + r = RET_NERRNO(statx(dfd, path, flags, mask, sx)); + if (r >= 0 || is_statx_fatal_error(r, flags)) + return r; avoid_statx = true; } @@ -477,8 +489,8 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { #if 0 /* NM_IGNORED */ void inode_hash_func(const struct stat *q, struct siphash *state) { - siphash24_compress(&q->st_dev, sizeof(q->st_dev), state); - siphash24_compress(&q->st_ino, sizeof(q->st_ino), state); + siphash24_compress_typesafe(q->st_dev, state); + siphash24_compress_typesafe(q->st_ino, state); } int inode_compare_func(const struct stat *a, const struct stat *b) { @@ -503,6 +515,8 @@ const char* inode_type_to_string(mode_t m) { return "reg"; case S_IFDIR: return "dir"; + case S_IFLNK: + return "lnk"; case S_IFCHR: return "chr"; case S_IFBLK: @@ -515,4 +529,26 @@ const char* inode_type_to_string(mode_t m) { return NULL; } + +mode_t inode_type_from_string(const char *s) { + if (!s) + return MODE_INVALID; + + if (streq(s, "reg")) + return S_IFREG; + if (streq(s, "dir")) + return S_IFDIR; + if (streq(s, "lnk")) + return S_IFLNK; + if (streq(s, "chr")) + return S_IFCHR; + if (streq(s, "blk")) + return S_IFBLK; + if (streq(s, "fifo")) + return S_IFIFO; + if (streq(s, "sock")) + return S_IFSOCK; + + return MODE_INVALID; +} #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/stat-util.h b/src/libnm-systemd-shared/src/basic/stat-util.h index ae0aaf8f51..dc11a85f62 100644 --- a/src/libnm-systemd-shared/src/basic/stat-util.h +++ b/src/libnm-systemd-shared/src/basic/stat-util.h @@ -12,6 +12,7 @@ #include "macro.h" #include "missing_stat.h" #include "siphash24.h" +#include "time-util.h" int is_symlink(const char *path); int is_dir_full(int atfd, const char *fname, bool follow); @@ -109,8 +110,16 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret); } var #endif +static inline usec_t statx_timestamp_load(const struct statx_timestamp *ts) { + return timespec_load(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec }); +} +static inline nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) { + return timespec_load_nsec(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec }); +} + void inode_hash_func(const struct stat *q, struct siphash *state); int inode_compare_func(const struct stat *a, const struct stat *b); extern const struct hash_ops inode_hash_ops; const char* inode_type_to_string(mode_t m); +mode_t inode_type_from_string(const char *s); diff --git a/src/libnm-systemd-shared/src/basic/string-util.c b/src/libnm-systemd-shared/src/basic/string-util.c index 1afa49bba0..539c0c76fe 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.c +++ b/src/libnm-systemd-shared/src/basic/string-util.c @@ -18,6 +18,7 @@ #include "macro.h" #include "memory-util.h" #include "memstream-util.h" +#include "path-util.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" @@ -174,10 +175,15 @@ char *delete_trailing_chars(char *s, const char *bad) { } #endif /* NM_IGNORED */ -char *truncate_nl(char *s) { +char *truncate_nl_full(char *s, size_t *ret_len) { + size_t n; + assert(s); - s[strcspn(s, NEWLINE)] = 0; + n = strcspn(s, NEWLINE); + s[n] = '\0'; + if (ret_len) + *ret_len = n; return s; } @@ -294,6 +300,62 @@ static int write_ellipsis(char *buf, bool unicode) { return 3; } +static size_t ansi_sequence_length(const char *s, size_t len) { + assert(s); + + if (len < 2) + return 0; + + if (s[0] != 0x1B) /* ASCII 27, aka ESC, aka Ctrl-[ */ + return 0; /* Not the start of a sequence */ + + if (s[1] == 0x5B) { /* [, start of CSI sequence */ + size_t i = 2; + + if (i == len) + return 0; + + while (s[i] >= 0x30 && s[i] <= 0x3F) /* Parameter bytes */ + if (++i == len) + return 0; + while (s[i] >= 0x20 && s[i] <= 0x2F) /* Intermediate bytes */ + if (++i == len) + return 0; + if (s[i] >= 0x40 && s[i] <= 0x7E) /* Final byte */ + return i + 1; + return 0; /* Bad sequence */ + + } else if (s[1] >= 0x40 && s[1] <= 0x5F) /* other non-CSI Fe sequence */ + return 2; + + return 0; /* Bad escape? */ +} + +static bool string_has_ansi_sequence(const char *s, size_t len) { + const char *t = s; + + while ((t = memchr(s, 0x1B, len - (t - s)))) + if (ansi_sequence_length(t, len - (t - s)) > 0) + return true; + return false; +} + +static size_t previous_ansi_sequence(const char *s, size_t length, const char **ret_where) { + /* Locate the previous ANSI sequence and save its start in *ret_where and return length. */ + + for (size_t i = length - 2; i > 0; i--) { /* -2 because at least two bytes are needed */ + size_t slen = ansi_sequence_length(s + (i - 1), length - (i - 1)); + if (slen == 0) + continue; + + *ret_where = s + (i - 1); + return slen; + } + + *ret_where = NULL; + return 0; +} + static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { size_t x, need_space, suffix_len; char *t; @@ -353,7 +415,6 @@ static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_le char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { size_t x, k, len, len2; const char *i, *j; - char *e; int r; /* Note that 'old_length' refers to bytes in the string, while 'new_length' refers to character cells taken up @@ -377,73 +438,116 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne if (new_length == 0) return strdup(""); - /* If no multibyte characters use ascii_ellipsize_mem for speed */ - if (ascii_is_valid_n(s, old_length)) + bool has_ansi_seq = string_has_ansi_sequence(s, old_length); + + /* If no multibyte characters or ANSI sequences, use ascii_ellipsize_mem for speed */ + if (!has_ansi_seq && ascii_is_valid_n(s, old_length)) return ascii_ellipsize_mem(s, old_length, new_length, percent); - x = ((new_length - 1) * percent) / 100; + x = (new_length - 1) * percent / 100; assert(x <= new_length - 1); k = 0; - for (i = s; i < s + old_length; i = utf8_next_char(i)) { - char32_t c; - int w; + for (i = s; i < s + old_length; ) { + size_t slen = has_ansi_seq ? ansi_sequence_length(i, old_length - (i - s)) : 0; + if (slen > 0) { + i += slen; + continue; /* ANSI sequences don't take up any space in output */ + } + char32_t c; r = utf8_encoded_to_unichar(i, &c); if (r < 0) return NULL; - w = unichar_iswide(c) ? 2 : 1; - if (k + w <= x) - k += w; - else + int w = unichar_iswide(c) ? 2 : 1; + if (k + w > x) break; + + k += w; + i += r; } - for (j = s + old_length; j > i; ) { + const char *ansi_start = s + old_length; + size_t ansi_len = 0; + + for (const char *t = j = s + old_length; t > i && k < new_length; ) { char32_t c; int w; - const char *jj; + const char *tt; - jj = utf8_prev_char(j); - r = utf8_encoded_to_unichar(jj, &c); + if (has_ansi_seq && ansi_start >= t) + /* Figure out the previous ANSI sequence, if any */ + ansi_len = previous_ansi_sequence(s, t - s, &ansi_start); + + /* If the sequence extends all the way to the current position, skip it. */ + if (has_ansi_seq && ansi_len > 0 && ansi_start + ansi_len == t) { + t = ansi_start; + continue; + } + + tt = utf8_prev_char(t); + r = utf8_encoded_to_unichar(tt, &c); if (r < 0) return NULL; w = unichar_iswide(c) ? 2 : 1; - if (k + w <= new_length) { - k += w; - j = jj; - } else + if (k + w > new_length) break; + + k += w; + j = t = tt; /* j should always point to the first "real" character */ } - assert(i <= j); - /* we don't actually need to ellipsize */ - if (i == j) + /* We don't actually need to ellipsize */ + if (i >= j) return memdup_suffix0(s, old_length); - /* make space for ellipsis, if possible */ - if (j < s + old_length) - j = utf8_next_char(j); - else if (i > s) - i = utf8_prev_char(i); + if (k >= new_length) { + /* Make space for ellipsis, if required and possible. We know that the edge character is not + * part of an ANSI sequence (because then we'd skip it). If the last character we looked at + * was wide, we don't need to make space. */ + if (j < s + old_length) + j = utf8_next_char(j); + else if (i > s) + i = utf8_prev_char(i); + } len = i - s; len2 = s + old_length - j; - e = new(char, len + 3 + len2 + 1); + + /* If we have ANSI, allow the same length as the source string + ellipsis. It'd be too involved to + * figure out what exact space is needed. Strings with ANSI sequences are most likely to be fairly + * short anyway. */ + size_t alloc_len = has_ansi_seq ? old_length + 3 + 1 : len + 3 + len2 + 1; + + char *e = new(char, alloc_len); if (!e) return NULL; /* - printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n", + printf("old_length=%zu new_length=%zu x=%zu len=%zu len2=%zu k=%zu\n", old_length, new_length, x, len, len2, k); */ - memcpy(e, s, len); + memcpy_safe(e, s, len); write_ellipsis(e + len, true); - memcpy(e + len + 3, j, len2); - *(e + len + 3 + len2) = '\0'; + + char *dst = e + len + 3; + + if (has_ansi_seq) + /* Copy over any ANSI sequences in full */ + for (const char *p = s + len; p < j; ) { + size_t slen = ansi_sequence_length(p, j - p); + if (slen > 0) { + dst = mempcpy(dst, p, slen); + p += slen; + } else + p = utf8_next_char(p); + } + + memcpy_safe(dst, j, len2); + dst[len2] = '\0'; return e; } @@ -528,14 +632,23 @@ char* strshorten(char *s, size_t l) { } int strgrowpad0(char **s, size_t l) { + size_t sz; + assert(s); + if (*s) { + sz = strlen(*s) + 1; + if (sz >= l) /* never shrink */ + return 0; + } else + sz = 0; + char *q = realloc(*s, l); if (!q) return -ENOMEM; + *s = q; - size_t sz = strlen(*s); memzero(*s + sz, l - sz); return 0; } @@ -893,6 +1006,33 @@ oom: return -ENOMEM; } +char *strextendn(char **x, const char *s, size_t l) { + assert(x); + assert(s || l == 0); + + if (l == SIZE_MAX) + l = strlen_ptr(s); + else if (l > 0) + l = strnlen(s, l); /* ignore trailing noise */ + + if (l > 0 || !*x) { + size_t q; + char *m; + + q = strlen_ptr(*x); + m = realloc(*x, q + l + 1); + if (!m) + return NULL; + + memcpy_safe(m + q, s, l); + m[q + l] = 0; + + *x = m; + } + + return *x; +} + char *strrep(const char *s, unsigned n) { char *r, *p; size_t l; @@ -1191,6 +1331,7 @@ char *string_replace_char(char *str, char old_char, char new_char) { return str; } +#endif /* NM_IGNORED */ int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) { char *b; @@ -1235,6 +1376,7 @@ int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) { return 0; } +#if 0 /* NM_IGNORED */ size_t strspn_from_end(const char *str, const char *accept) { size_t n = 0; @@ -1290,14 +1432,109 @@ char *find_line_startswith(const char *haystack, const char *needle) { } #endif /* NM_IGNORED */ -char *startswith_strv(const char *string, char **strv) { - char *found = NULL; +bool version_is_valid(const char *s) { + if (isempty(s)) + return false; - STRV_FOREACH(i, strv) { - found = startswith(string, *i); - if (found) - break; + if (!filename_part_is_valid(s)) + return false; + + /* This is a superset of the characters used by semver. We additionally allow "," and "_". */ + if (!in_charset(s, ALPHANUMERICAL ".,_-+")) + return false; + + return true; +} + +bool version_is_valid_versionspec(const char *s) { + if (!filename_part_is_valid(s)) + return false; + + if (!in_charset(s, ALPHANUMERICAL "-.~^")) + return false; + + return true; +} + +ssize_t strlevenshtein(const char *x, const char *y) { + _cleanup_free_ size_t *t0 = NULL, *t1 = NULL, *t2 = NULL; + size_t xl, yl; + + /* This is inspired from the Linux kernel's Levenshtein implementation */ + + if (streq_ptr(x, y)) + return 0; + + xl = strlen_ptr(x); + if (xl > SSIZE_MAX) + return -E2BIG; + + yl = strlen_ptr(y); + if (yl > SSIZE_MAX) + return -E2BIG; + + if (isempty(x)) + return yl; + if (isempty(y)) + return xl; + + t0 = new0(size_t, yl + 1); + if (!t0) + return -ENOMEM; + t1 = new0(size_t, yl + 1); + if (!t1) + return -ENOMEM; + t2 = new0(size_t, yl + 1); + if (!t2) + return -ENOMEM; + + for (size_t i = 0; i <= yl; i++) + t1[i] = i; + + for (size_t i = 0; i < xl; i++) { + t2[0] = i + 1; + + for (size_t j = 0; j < yl; j++) { + /* Substitution */ + t2[j+1] = t1[j] + (x[i] != y[j]); + + /* Swap */ + if (i > 0 && j > 0 && x[i-1] == y[j] && x[i] == y[j-1] && t2[j+1] > t0[j-1] + 1) + t2[j+1] = t0[j-1] + 1; + + /* Deletion */ + if (t2[j+1] > t1[j+1] + 1) + t2[j+1] = t1[j+1] + 1; + + /* Insertion */ + if (t2[j+1] > t2[j] + 1) + t2[j+1] = t2[j] + 1; + } + + size_t *dummy = t0; + t0 = t1; + t1 = t2; + t2 = dummy; } - return found; + return t1[yl]; +} + +char *strrstr(const char *haystack, const char *needle) { + /* Like strstr() but returns the last rather than the first occurrence of "needle" in "haystack". */ + + if (!haystack || !needle) + return NULL; + + /* Special case: for the empty string we return the very last possible occurrence, i.e. *after* the + * last char, not before. */ + if (*needle == 0) + return strchr(haystack, 0); + + for (const char *p = strstr(haystack, needle), *q; p; p = q) { + q = strstr(p + 1, needle); + if (!q) + return (char *) p; + } + return NULL; } diff --git a/src/libnm-systemd-shared/src/basic/string-util.h b/src/libnm-systemd-shared/src/basic/string-util.h index 4430910e22..e162765aa7 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.h +++ b/src/libnm-systemd-shared/src/basic/string-util.h @@ -22,6 +22,9 @@ #define ALPHANUMERICAL LETTERS DIGITS #define HEXDIGITS DIGITS "abcdefABCDEF" #define LOWERCASE_HEXDIGITS DIGITS "abcdef" +#define URI_RESERVED ":/?#[]@!$&'()*+;=" /* [RFC3986] */ +#define URI_UNRESERVED ALPHANUMERICAL "-._~" /* [RFC3986] */ +#define URI_VALID URI_RESERVED URI_UNRESERVED /* [RFC3986] */ static inline char* strstr_ptr(const char *haystack, const char *needle) { if (!haystack || !needle) @@ -65,6 +68,10 @@ static inline const char* enable_disable(bool b) { return b ? "enable" : "disable"; } +static inline const char* enabled_disabled(bool b) { + return b ? "enabled" : "disabled"; +} + /* This macro's return pointer will have the "const" qualifier set or unset the same way as the input * pointer. */ #define empty_to_null(p) \ @@ -121,7 +128,10 @@ char *strjoin_real(const char *x, ...) _sentinel_; char *strstrip(char *s); char *delete_chars(char *s, const char *bad); char *delete_trailing_chars(char *s, const char *bad); -char *truncate_nl(char *s); +char *truncate_nl_full(char *s, size_t *ret_len); +static inline char *truncate_nl(char *s) { + return truncate_nl_full(s, NULL); +} static inline char *skip_leading_chars(const char *s, const char *bad) { if (!s) @@ -183,11 +193,24 @@ char *strextend_with_separator_internal(char **x, const char *separator, ...) _s #define strextend_with_separator(x, separator, ...) strextend_with_separator_internal(x, separator, __VA_ARGS__, NULL) #define strextend(x, ...) strextend_with_separator_internal(x, NULL, __VA_ARGS__, NULL) +char *strextendn(char **x, const char *s, size_t l); + int strextendf_with_separator(char **x, const char *separator, const char *format, ...) _printf_(3,4); #define strextendf(x, ...) strextendf_with_separator(x, NULL, __VA_ARGS__) char *strrep(const char *s, unsigned n); +#define strrepa(s, n) \ + ({ \ + char *_d_, *_p_; \ + size_t _len_ = strlen(s) * n; \ + _p_ = _d_ = newa(char, _len_ + 1); \ + for (unsigned _i_ = 0; _i_ < n; _i_++) \ + _p_ = stpcpy(_p_, s); \ + *_p_ = 0; \ + _d_; \ + }) + int split_pair(const char *s, const char *sep, char **l, char **r); int free_and_strdup(char **p, const char *s); @@ -268,7 +291,31 @@ char *strdupcspn(const char *a, const char *reject); char *find_line_startswith(const char *haystack, const char *needle); -char *startswith_strv(const char *string, char **strv); +bool version_is_valid(const char *s); + +bool version_is_valid_versionspec(const char *s); + +ssize_t strlevenshtein(const char *x, const char *y); + +static inline int strdup_or_null(const char *s, char **ret) { + char *c; + + assert(ret); + + /* This is a lot like strdup(), but is happy with NULL strings, and does not treat that as error, but + * copies the NULL value. */ + + if (!s) { + *ret = NULL; + return 0; + } + + c = strdup(s); + if (!c) + return -ENOMEM; + + *ret = c; + return 1; +} -#define STARTSWITH_SET(p, ...) \ - startswith_strv(p, STRV_MAKE(__VA_ARGS__)) +char *strrstr(const char *haystack, const char *needle); diff --git a/src/libnm-systemd-shared/src/basic/strv.c b/src/libnm-systemd-shared/src/basic/strv.c index 9ad5330739..7c6de915b2 100644 --- a/src/libnm-systemd-shared/src/basic/strv.c +++ b/src/libnm-systemd-shared/src/basic/strv.c @@ -90,6 +90,15 @@ char** strv_free_erase(char **l) { return mfree(l); } +void strv_free_many(char ***strvs, size_t n) { + assert(strvs || n == 0); + + FOREACH_ARRAY (i, strvs, n) + strv_free(*i); + + free(strvs); +} + char** strv_copy_n(char * const *l, size_t m) { _cleanup_strv_free_ char **result = NULL; char **k; @@ -116,6 +125,22 @@ char** strv_copy_n(char * const *l, size_t m) { return TAKE_PTR(result); } +int strv_copy_unless_empty(char * const *l, char ***ret) { + assert(ret); + + if (strv_isempty(l)) { + *ret = NULL; + return 0; + } + + char **copy = strv_copy(l); + if (!copy) + return -ENOMEM; + + *ret = TAKE_PTR(copy); + return 1; +} + size_t strv_length(char * const *l) { size_t n = 0; @@ -214,9 +239,7 @@ int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates) { return (int) i; rollback: - for (size_t j = 0; j < i; j++) - free(t[p + j]); - + free_many_charp(t + p, i); t[p] = NULL; return -ENOMEM; } @@ -488,29 +511,31 @@ int strv_insert(char ***l, size_t position, char *value) { char **c; size_t n, m; + assert(l); + if (!value) return 0; n = strv_length(*l); position = MIN(position, n); - /* increase and check for overflow */ - m = n + 2; - if (m < n) + /* check for overflow and increase*/ + if (n > SIZE_MAX - 2) return -ENOMEM; + m = n + 2; - c = new(char*, m); + c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m), sizeof(char*)); if (!c) return -ENOMEM; - for (size_t i = 0; i < position; i++) - c[i] = (*l)[i]; + if (n > position) + memmove(c + position + 1, c + position, (n - position) * sizeof(char*)); + c[position] = value; - for (size_t i = position; i < n; i++) - c[i+1] = (*l)[i]; - c[n+1] = NULL; + c[n + 1] = NULL; - return free_and_replace(*l, c); + *l = c; + return 0; } int strv_consume_with_size(char ***l, size_t *n, char *value) { @@ -571,39 +596,63 @@ int strv_extend_with_size(char ***l, size_t *n, const char *value) { return strv_consume_with_size(l, n, v); } -int strv_extend_front(char ***l, const char *value) { +int strv_extend_many_internal(char ***l, const char *value, ...) { + va_list ap; size_t n, m; - char *v, **c; + int r; assert(l); - /* Like strv_extend(), but prepends rather than appends the new entry */ + m = n = strv_length(*l); - if (!value) - return 0; + r = 0; + va_start(ap, value); + for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) { + if (!s) + continue; - n = strv_length(*l); + if (m > SIZE_MAX-1) { /* overflow */ + r = -ENOMEM; + break; + } + m++; + } + va_end(ap); - /* Increase and overflow check. */ - m = n + 2; - if (m < n) + if (r < 0) + return r; + if (m > SIZE_MAX-1) return -ENOMEM; - v = strdup(value); - if (!v) + char **c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m+1), sizeof(char*)); + if (!c) return -ENOMEM; + *l = c; - c = reallocarray(*l, m, sizeof(char*)); - if (!c) { - free(v); - return -ENOMEM; + r = 0; + size_t i = n; + va_start(ap, value); + for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) { + if (!s) + continue; + + c[i] = strdup(s); + if (!c[i]) { + r = -ENOMEM; + break; + } + i++; } + va_end(ap); - memmove(c+1, c, n * sizeof(char*)); - c[0] = v; - c[n+1] = NULL; + if (r < 0) { + /* rollback on error */ + for (size_t j = n; j < i; j++) + c[j] = mfree(c[j]); + return r; + } - *l = c; + c[i] = NULL; return 0; } @@ -707,6 +756,26 @@ int strv_extendf(char ***l, const char *format, ...) { return strv_consume(l, x); } +char* startswith_strv(const char *s, char * const *l) { + STRV_FOREACH(i, l) { + char *found = startswith(s, *i); + if (found) + return found; + } + + return NULL; +} + +char* endswith_strv(const char *s, char * const *l) { + STRV_FOREACH(i, l) { + char *found = endswith(s, *i); + if (found) + return found; + } + + return NULL; +} + char** strv_reverse(char **l) { size_t n; @@ -835,13 +904,15 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) { bool b = false; int r; + assert(f); + /* Like fputs(), but for strv, and with a less stupid argument order */ if (!space) space = &b; STRV_FOREACH(s, l) { - r = fputs_with_space(f, *s, separator, space); + r = fputs_with_separator(f, *s, separator, space); if (r < 0) return r; } diff --git a/src/libnm-systemd-shared/src/basic/strv.h b/src/libnm-systemd-shared/src/basic/strv.h index 544d46a3f8..91337b9287 100644 --- a/src/libnm-systemd-shared/src/basic/strv.h +++ b/src/libnm-systemd-shared/src/basic/strv.h @@ -32,10 +32,14 @@ char** strv_free_erase(char **l); DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase); #define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep) +void strv_free_many(char ***strvs, size_t n); + char** strv_copy_n(char * const *l, size_t n); static inline char** strv_copy(char * const *l) { return strv_copy_n(l, SIZE_MAX); } +int strv_copy_unless_empty(char * const *l, char ***ret); + size_t strv_length(char * const *l) _pure_; int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates); @@ -51,8 +55,10 @@ static inline int strv_extend(char ***l, const char *value) { return strv_extend_with_size(l, NULL, value); } +int strv_extend_many_internal(char ***l, const char *value, ...); +#define strv_extend_many(l, ...) strv_extend_many_internal(l, __VA_ARGS__, POINTER_MAX) + int strv_extendf(char ***l, const char *format, ...) _printf_(2,3); -int strv_extend_front(char ***l, const char *value); int strv_push_with_size(char ***l, size_t *n, char *value); static inline int strv_push(char ***l, char *value) { @@ -157,6 +163,16 @@ static inline void strv_print(char * const *l) { strv_print_full(l, NULL); } +char* startswith_strv(const char *s, char * const *l); + +#define STARTSWITH_SET(p, ...) \ + startswith_strv(p, STRV_MAKE(__VA_ARGS__)) + +char* endswith_strv(const char *s, char * const *l); + +#define ENDSWITH_SET(p, ...) \ + endswith_strv(p, STRV_MAKE(__VA_ARGS__)) + #define strv_from_stdarg_alloca(first) \ ({ \ char **_l; \ @@ -200,18 +216,6 @@ static inline void strv_print(char * const *l) { _x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \ }) -#define ENDSWITH_SET(p, ...) \ - ({ \ - const char *_p = (p); \ - char *_found = NULL; \ - STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) { \ - _found = endswith(_p, *_i); \ - if (_found) \ - break; \ - } \ - _found; \ - }) - #define _FOREACH_STRING(uniq, x, y, ...) \ for (const char *x, * const*UNIQ_T(l, uniq) = STRV_MAKE_CONST(({ x = y; }), ##__VA_ARGS__); \ x; \ diff --git a/src/libnm-systemd-shared/src/basic/time-util.c b/src/libnm-systemd-shared/src/basic/time-util.c index 092912b2b0..2147156e55 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.c +++ b/src/libnm-systemd-shared/src/basic/time-util.c @@ -66,7 +66,7 @@ nsec_t now_nsec(clockid_t clock_id) { return timespec_load_nsec(&ts); } -dual_timestamp* dual_timestamp_get(dual_timestamp *ts) { +dual_timestamp* dual_timestamp_now(dual_timestamp *ts) { assert(ts); ts->realtime = now(CLOCK_REALTIME); @@ -75,7 +75,7 @@ dual_timestamp* dual_timestamp_get(dual_timestamp *ts) { return ts; } -triple_timestamp* triple_timestamp_get(triple_timestamp *ts) { +triple_timestamp* triple_timestamp_now(triple_timestamp *ts) { assert(ts); ts->realtime = now(CLOCK_REALTIME); @@ -158,6 +158,25 @@ triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) return ts; } +triple_timestamp* triple_timestamp_from_boottime(triple_timestamp *ts, usec_t u) { + usec_t nowb; + + assert(ts); + + if (u == USEC_INFINITY) { + ts->realtime = ts->monotonic = ts->boottime = u; + return ts; + } + + nowb = now(CLOCK_BOOTTIME); + + ts->boottime = u; + ts->monotonic = map_clock_usec_internal(u, nowb, now(CLOCK_MONOTONIC)); + ts->realtime = map_clock_usec_internal(u, nowb, now(CLOCK_REALTIME)); + + return ts; +} + dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) { assert(ts); @@ -330,7 +349,7 @@ char *format_timestamp_style( if (l < (size_t) (1 + 1 + 1)) return NULL; /* not enough space for even the shortest of forms */ - return snprintf_ok(buf, l, "@" USEC_FMT, t / USEC_PER_SEC); /* round down µs → s */ + return snprintf_ok(buf, l, "@" USEC_FMT, t / USEC_PER_SEC); /* round down μs → s */ } utc = IN_SET(style, TIMESTAMP_UTC, TIMESTAMP_US_UTC, TIMESTAMP_DATE); @@ -618,7 +637,7 @@ char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { #if 0 /* NM_IGNORED */ static int parse_timestamp_impl( const char *t, - size_t tz_offset, + size_t max_len, bool utc, int isdst, long gmtoff, @@ -655,8 +674,12 @@ static int parse_timestamp_impl( /* Allowed syntaxes: * - * 2012-09-22 16:34:22 + * 2012-09-22 16:34:22.1[2[3[4[5[6]]]]] + * 2012-09-22 16:34:22 (µsec will be set to 0) * 2012-09-22 16:34 (seconds will be set to 0) + * 2012-09-22T16:34:22.1[2[3[4[5[6]]]]] + * 2012-09-22T16:34:22 (µsec will be set to 0) + * 2012-09-22T16:34 (seconds will be set to 0) * 2012-09-22 (time will be set to 00:00:00) * 16:34:22 (date will be set to today) * 16:34 (date will be set to today, seconds to 0) @@ -670,17 +693,26 @@ static int parse_timestamp_impl( * * Note, on DST change, 00:00:00 may not exist and in that case the time part may be shifted. * E.g. "Sun 2023-03-13 America/Havana" is parsed as "Sun 2023-03-13 01:00:00 CDT". + * + * A simplified strptime-spelled RFC3339 ABNF looks like + * "%Y-%m-%d" "T" "%H" ":" "%M" ":" "%S" [".%N"] ("Z" / (("+" / "-") "%H:%M")) + * We additionally allow no seconds and inherited timezone + * for symmetry with our other syntaxes and improved interactive usability: + * "%Y-%m-%d" "T" "%H" ":" "%M" ":" ["%S" [".%N"]] ["Z" / (("+" / "-") "%H:%M")] + * RFC3339 defines time-secfrac to as "." 1*DIGIT, but we limit to 6 digits, + * since we're limited to 1µs resolution. + * We also accept "Sat 2012-09-22T16:34:22", RFC3339 warns against it. */ assert(t); - if (tz_offset != SIZE_MAX) { + if (max_len != SIZE_MAX) { /* If the input string contains timezone, then cut it here. */ - if (tz_offset <= 1) /* timezone must be after a space. */ + if (max_len == 0) /* Can't be the only field */ return -EINVAL; - t_alloc = strndup(t, tz_offset - 1); + t_alloc = strndup(t, max_len); if (!t_alloc) return -ENOMEM; @@ -792,6 +824,7 @@ static int parse_timestamp_impl( goto from_tm; } + /* Our "canonical" RFC3339 syntax variant */ tm = copy; k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); if (k) { @@ -801,6 +834,16 @@ static int parse_timestamp_impl( goto from_tm; } + /* RFC3339 syntax */ + tm = copy; + k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm); + if (k) { + if (*k == '.') + goto parse_usec; + else if (*k == 0) + goto from_tm; + } + /* Support OUTPUT_SHORT and OUTPUT_SHORT_PRECISE formats */ tm = copy; k = strptime(t, "%b %d %H:%M:%S", &tm); @@ -818,6 +861,7 @@ static int parse_timestamp_impl( goto from_tm; } + /* Our "canonical" RFC3339 syntax variant without seconds */ tm = copy; k = strptime(t, "%Y-%m-%d %H:%M", &tm); if (k && *k == 0) { @@ -825,6 +869,14 @@ static int parse_timestamp_impl( goto from_tm; } + /* RFC3339 syntax without seconds */ + tm = copy; + k = strptime(t, "%Y-%m-%dT%H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto from_tm; + } + tm = copy; k = strptime(t, "%y-%m-%d", &tm); if (k && *k == 0) { @@ -927,13 +979,13 @@ static int parse_timestamp_maybe_with_tz(const char *t, size_t tz_offset, bool v continue; /* The specified timezone matches tzname[] of the local timezone. */ - return parse_timestamp_impl(t, tz_offset, /* utc = */ false, /* isdst = */ j, /* gmtoff = */ 0, ret); + return parse_timestamp_impl(t, tz_offset - 1, /* utc = */ false, /* isdst = */ j, /* gmtoff = */ 0, ret); } /* If we know that the last word is a valid timezone (e.g. Asia/Tokyo), then simply drop the timezone * and parse the remaining string as a local time. If we know that the last word is not a timezone, * then assume that it is a part of the time and try to parse the whole string as a local time. */ - return parse_timestamp_impl(t, valid_tz ? tz_offset : SIZE_MAX, + return parse_timestamp_impl(t, valid_tz ? tz_offset - 1 : SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret); } @@ -945,40 +997,50 @@ typedef struct ParseTimestampResult { int parse_timestamp(const char *t, usec_t *ret) { ParseTimestampResult *shared, tmp; const char *k, *tz, *current_tz; - size_t tz_offset; + size_t max_len, t_len; struct tm tm; int r; assert(t); + t_len = strlen(t); + if (t_len > 2 && t[t_len - 1] == 'Z' && t[t_len - 2] != ' ') /* RFC3339-style welded UTC: "1985-04-12T23:20:50.52Z" */ + return parse_timestamp_impl(t, t_len - 1, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret); + + if (t_len > 7 && IN_SET(t[t_len - 6], '+', '-') && t[t_len - 7] != ' ') { /* RFC3339-style welded offset: "1990-12-31T15:59:60-08:00" */ + k = strptime(&t[t_len - 6], "%z", &tm); + if (k && *k == '\0') + return parse_timestamp_impl(t, t_len - 6, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret); + } + tz = strrchr(t, ' '); if (!tz) - return parse_timestamp_impl(t, /* tz_offset = */ SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret); + return parse_timestamp_impl(t, /* max_len = */ SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret); + max_len = tz - t; tz++; - tz_offset = tz - t; /* Shortcut, parse the string as UTC. */ if (streq(tz, "UTC")) - return parse_timestamp_impl(t, tz_offset, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret); + return parse_timestamp_impl(t, max_len, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret); /* If the timezone is compatible with RFC-822/ISO 8601 (e.g. +06, or -03:00) then parse the string as * UTC and shift the result. Note, this must be earlier than the timezone check with tzname[], as * tzname[] may be in the same format. */ k = strptime(tz, "%z", &tm); if (k && *k == '\0') - return parse_timestamp_impl(t, tz_offset, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret); + return parse_timestamp_impl(t, max_len, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret); /* If the last word is not a timezone file (e.g. Asia/Tokyo), then let's check if it matches * tzname[] of the local timezone, e.g. JST or CEST. */ if (!timezone_is_valid(tz, LOG_DEBUG)) - return parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ false, ret); + return parse_timestamp_maybe_with_tz(t, tz - t, /* valid_tz = */ false, ret); /* Shortcut. If the current $TZ is equivalent to the specified timezone, it is not necessary to fork * the process. */ current_tz = getenv("TZ"); if (current_tz && *current_tz == ':' && streq(current_tz + 1, tz)) - return parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ true, ret); + return parse_timestamp_maybe_with_tz(t, tz - t, /* valid_tz = */ true, ret); /* Otherwise, to avoid polluting the current environment variables, let's fork the process and set * the specified timezone in the child process. */ @@ -987,7 +1049,7 @@ int parse_timestamp(const char *t, usec_t *ret) { if (shared == MAP_FAILED) return negative_errno(); - r = safe_fork("(sd-timestamp)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL); + r = safe_fork("(sd-timestamp)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, NULL); if (r < 0) { (void) munmap(shared, sizeof *shared); return r; @@ -1003,7 +1065,7 @@ int parse_timestamp(const char *t, usec_t *ret) { _exit(EXIT_FAILURE); } - shared->return_value = parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ true, &shared->usec); + shared->return_value = parse_timestamp_maybe_with_tz(t, tz - t, /* valid_tz = */ true, &shared->usec); _exit(EXIT_SUCCESS); } @@ -1051,7 +1113,8 @@ static const char* extract_multiplier(const char *p, usec_t *ret) { { "y", USEC_PER_YEAR }, { "usec", 1ULL }, { "us", 1ULL }, - { "µs", 1ULL }, + { "μs", 1ULL }, /* U+03bc (aka GREEK SMALL LETTER MU) */ + { "µs", 1ULL }, /* U+b5 (aka MICRO SIGN) */ }; assert(p); @@ -1229,7 +1292,8 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) { { "y", NSEC_PER_YEAR }, { "usec", NSEC_PER_USEC }, { "us", NSEC_PER_USEC }, - { "µs", NSEC_PER_USEC }, + { "μs", NSEC_PER_USEC }, /* U+03bc (aka GREEK LETTER MU) */ + { "µs", NSEC_PER_USEC }, /* U+b5 (aka MICRO SIGN) */ { "nsec", 1ULL }, { "ns", 1ULL }, { "", 1ULL }, /* default is nsec */ @@ -1458,7 +1522,7 @@ int get_timezones(char ***ret) { /* Always include UTC */ r = strv_extend(&zones, "UTC"); if (r < 0) - return -ENOMEM; + return r; strv_sort(zones); strv_uniq(zones); @@ -1666,13 +1730,13 @@ int time_change_fd(void) { if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) >= 0) return TAKE_FD(fd); - /* So apparently there are systems where time_t is 64bit, but the kernel actually doesn't support - * 64bit time_t. In that case configuring a timer to TIME_T_MAX will fail with EOPNOTSUPP or a + /* So apparently there are systems where time_t is 64-bit, but the kernel actually doesn't support + * 64-bit time_t. In that case configuring a timer to TIME_T_MAX will fail with EOPNOTSUPP or a * similar error. If that's the case let's try with INT32_MAX instead, maybe that works. It's a bit * of a black magic thing though, but what can we do? * - * We don't want this code on x86-64, hence let's conditionalize this for systems with 64bit time_t - * but where "long" is shorter than 64bit, i.e. 32bit archs. + * We don't want this code on x86-64, hence let's conditionalize this for systems with 64-bit time_t + * but where "long" is shorter than 64-bit, i.e. 32-bit archs. * * See: https://github.com/systemd/systemd/issues/14362 */ @@ -1708,9 +1772,9 @@ TimestampStyle timestamp_style_from_string(const char *s) { t = (TimestampStyle) string_table_lookup(timestamp_style_table, ELEMENTSOF(timestamp_style_table), s); if (t >= 0) return t; - if (streq_ptr(s, "µs")) + if (STRPTR_IN_SET(s, "µs", "μs")) /* accept both µ symbols in unicode, i.e. micro symbol + Greek small letter mu. */ return TIMESTAMP_US; - if (streq_ptr(s, "µs+utc")) + if (STRPTR_IN_SET(s, "µs+utc", "μs+utc")) return TIMESTAMP_US_UTC; return t; } diff --git a/src/libnm-systemd-shared/src/basic/time-util.h b/src/libnm-systemd-shared/src/basic/time-util.h index b49137d5c3..29373477f4 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.h +++ b/src/libnm-systemd-shared/src/basic/time-util.h @@ -79,13 +79,14 @@ nsec_t now_nsec(clockid_t clock); usec_t map_clock_usec(usec_t from, clockid_t from_clock, clockid_t to_clock); -dual_timestamp* dual_timestamp_get(dual_timestamp *ts); +dual_timestamp* dual_timestamp_now(dual_timestamp *ts); dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u); dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u); dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u); -triple_timestamp* triple_timestamp_get(triple_timestamp *ts); +triple_timestamp* triple_timestamp_now(triple_timestamp *ts); triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u); +triple_timestamp* triple_timestamp_from_boottime(triple_timestamp *ts, usec_t u); #define DUAL_TIMESTAMP_HAS_CLOCK(clock) \ IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC) @@ -211,10 +212,24 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) { return usec_sub_unsigned(timestamp, (usec_t) delta); } +static inline int usleep_safe(usec_t usec) { + /* usleep() takes useconds_t that is (typically?) uint32_t. Also, usleep() may only support the + * range [0, 1000000]. See usleep(3). Let's override usleep() with clock_nanosleep(). + * + * ⚠️ Note we are not using plain nanosleep() here, since that operates on CLOCK_REALTIME, not + * CLOCK_MONOTONIC! */ + + if (usec == 0) + return 0; + + // FIXME: use RET_NERRNO() macro here. Currently, this header cannot include errno-util.h. + return clock_nanosleep(CLOCK_MONOTONIC, 0, TIMESPEC_STORE(usec), NULL) < 0 ? -errno : 0; +} + /* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit * year territory. However, since we want to stay away from this in all timezones we take one day off. */ #define USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT ((usec_t) 253402214399000000) /* Thu 9999-12-30 23:59:59 UTC */ -/* With a 32bit time_t we can't go beyond 2038... +/* With a 32-bit time_t we can't go beyond 2038... * We parse timestamp with RFC-822/ISO 8601 (e.g. +06, or -03:00) as UTC, hence the upper bound must be off * by USEC_PER_DAY. See parse_timestamp() for more details. */ #define USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT (((usec_t) INT32_MAX) * USEC_PER_SEC - USEC_PER_DAY) diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.h b/src/libnm-systemd-shared/src/basic/tmpfile-util.h index 50904ecac1..8c917c0680 100644 --- a/src/libnm-systemd-shared/src/basic/tmpfile-util.h +++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.h @@ -29,7 +29,6 @@ static inline int open_tmpfile_linkable(const char *target, int flags, char **re } int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file); - typedef enum LinkTmpfileFlags { LINK_TMPFILE_REPLACE = 1 << 0, LINK_TMPFILE_SYNC = 1 << 1, diff --git a/src/libnm-systemd-shared/src/basic/umask-util.h b/src/libnm-systemd-shared/src/basic/umask-util.h index 6f0e1cc2b2..00417fa304 100644 --- a/src/libnm-systemd-shared/src/basic/umask-util.h +++ b/src/libnm-systemd-shared/src/basic/umask-util.h @@ -8,12 +8,12 @@ #include "macro.h" static inline void umaskp(mode_t *u) { - umask(*u & 0777); + umask(*u); } #define _cleanup_umask_ _cleanup_(umaskp) -/* We make use of the fact here that the umask() concept is using only the lower 9 bits of mode_t, although +/* We make use of the fact here that the umask() syscall uses only the lower 9 bits of mode_t, although * mode_t has space for the file type in the bits further up. We simply OR in the file type mask S_IFMT to * distinguish the first and the second iteration of the WITH_UMASK() loop, so that we can run the first one, * and exit on the second. */ diff --git a/src/libnm-systemd-shared/src/basic/user-util.h b/src/libnm-systemd-shared/src/basic/user-util.h index 8b829a9ae2..9d07ef31d2 100644 --- a/src/libnm-systemd-shared/src/basic/user-util.h +++ b/src/libnm-systemd-shared/src/basic/user-util.h @@ -42,8 +42,8 @@ typedef enum UserCredsFlags { USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */ } UserCredsFlags; -int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell, UserCredsFlags flags); -int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags); +int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags); +int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags); char* uid_to_name(uid_t uid); char* gid_to_name(gid_t gid); @@ -57,7 +57,10 @@ int getgroups_alloc(gid_t** gids); int get_home_dir(char **ret); int get_shell(char **ret); -int reset_uid_gid(void); +int fully_set_uid_gid(uid_t uid, gid_t gid, const gid_t supplementary_gids[], size_t n_supplementary_gids); +static inline int reset_uid_gid(void) { + return fully_set_uid_gid(0, 0, NULL, 0); +} int take_etc_passwd_lock(const char *root); @@ -69,13 +72,13 @@ int take_etc_passwd_lock(const char *root); /* If REMOUNT_IDMAPPING_HOST_ROOT is set for remount_idmap() we'll include a mapping here that maps the host * root user accessing the idmapped mount to the this user ID on the backing fs. This is the last valid UID in - * the *signed* 32bit range. You might wonder why precisely use this specific UID for this purpose? Well, we + * the *signed* 32-bit range. You might wonder why precisely use this specific UID for this purpose? Well, we * definitely cannot use the first 0…65536 UIDs for that, since in most cases that's precisely the file range * we intend to map to some high UID range, and since UID mappings have to be bijective we thus cannot use - * them at all. Furthermore the UID range beyond INT32_MAX (i.e. the range above the signed 32bit range) is + * them at all. Furthermore the UID range beyond INT32_MAX (i.e. the range above the signed 32-bit range) is * icky, since many APIs cannot use it (example: setfsuid() returns the old UID as signed integer). Following - * our usual logic of assigning a 16bit UID range to each container, so that the upper 16bit of a 32bit UID - * value indicate kind of a "container ID" and the lower 16bit map directly to the intended user you can read + * our usual logic of assigning a 16-bit UID range to each container, so that the upper 16-bit of a 32-bit UID + * value indicate kind of a "container ID" and the lower 16-bit map directly to the intended user you can read * this specific UID as the "nobody" user of the container with ID 0x7FFF, which is kinda nice. */ #define UID_MAPPED_ROOT ((uid_t) (INT32_MAX-1)) #define GID_MAPPED_ROOT ((gid_t) (INT32_MAX-1)) @@ -155,3 +158,9 @@ static inline bool hashed_password_is_locked_or_invalid(const char *password) { * Also see https://github.com/systemd/systemd/pull/24680#pullrequestreview-1439464325. */ #define PASSWORD_UNPROVISIONED "!unprovisioned" + +int getpwuid_malloc(uid_t uid, struct passwd **ret); +int getpwnam_malloc(const char *name, struct passwd **ret); + +int getgrnam_malloc(const char *name, struct group **ret); +int getgrgid_malloc(gid_t gid, struct group **ret); diff --git a/src/libnm-systemd-shared/src/basic/utf8.c b/src/libnm-systemd-shared/src/basic/utf8.c index c8e39fe45e..cf24e82f09 100644 --- a/src/libnm-systemd-shared/src/basic/utf8.c +++ b/src/libnm-systemd-shared/src/basic/utf8.c @@ -92,7 +92,7 @@ int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) { switch (len) { case 1: *ret_unichar = (char32_t)str[0]; - return 0; + return 1; case 2: unichar = str[0] & 0x1f; break; @@ -121,15 +121,14 @@ int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) { } *ret_unichar = unichar; - - return 0; + return len; } bool utf8_is_printable_newline(const char* str, size_t length, bool allow_newline) { assert(str); for (const char *p = str; length > 0;) { - int encoded_len, r; + int encoded_len; char32_t val; encoded_len = utf8_encoded_valid_unichar(p, length); @@ -137,8 +136,7 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool allow_newlin return false; assert(encoded_len > 0 && (size_t) encoded_len <= length); - r = utf8_encoded_to_unichar(p, &val); - if (r < 0 || + if (utf8_encoded_to_unichar(p, &val) < 0 || unichar_is_control(val) || (!allow_newline && val == '\n')) return false; @@ -398,11 +396,23 @@ char *utf16_to_utf8(const char16_t *s, size_t length /* bytes! */) { const uint8_t *f; char *r, *t; + if (length == 0) + return new0(char, 1); + assert(s); + if (length == SIZE_MAX) { + length = char16_strlen(s); + + if (length > SIZE_MAX/2) + return NULL; /* overflow */ + + length *= 2; + } + /* Input length is in bytes, i.e. the shortest possible character takes 2 bytes. Each unicode character may * take up to 4 bytes in UTF-8. Let's also account for a trailing NUL byte. */ - if (length * 2 < length) + if (length > (SIZE_MAX - 1) / 2) return NULL; /* overflow */ r = new(char, length * 2 + 1); @@ -472,8 +482,17 @@ char16_t *utf8_to_utf16(const char *s, size_t length) { char16_t *n, *p; int r; + if (length == 0) + return new0(char16_t, 1); + assert(s); + if (length == SIZE_MAX) + length = strlen(s); + + if (length > SIZE_MAX - 1) + return NULL; /* overflow */ + n = new(char16_t, length + 1); if (!n) return NULL; diff --git a/src/libnm-systemd-shared/src/basic/utf8.h b/src/libnm-systemd-shared/src/basic/utf8.h index 4a06dd62c5..962312c5fb 100644 --- a/src/libnm-systemd-shared/src/basic/utf8.h +++ b/src/libnm-systemd-shared/src/basic/utf8.h @@ -38,7 +38,7 @@ size_t utf16_encode_unichar(char16_t *out, char32_t c); char *utf16_to_utf8(const char16_t *s, size_t length /* bytes! */); char16_t *utf8_to_utf16(const char *s, size_t length); -size_t char16_strlen(const char16_t *s); /* returns the number of 16bit words in the string (not bytes!) */ +size_t char16_strlen(const char16_t *s); /* returns the number of 16-bit words in the string (not bytes!) */ int utf8_encoded_valid_unichar(const char *str, size_t length); int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar); diff --git a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h index 89b83e7d0b..c810261308 100644 --- a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h @@ -11,6 +11,42 @@ #include <stddef.h> #include <stdint.h> +/* Temporarily disable some warnings */ +#define DISABLE_WARNING_DEPRECATED_DECLARATIONS \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + +#define DISABLE_WARNING_FORMAT_NONLITERAL \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"") + +#define DISABLE_WARNING_MISSING_PROTOTYPES \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"") + +#define DISABLE_WARNING_NONNULL \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wnonnull\"") + +#define DISABLE_WARNING_SHADOW \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wshadow\"") + +#define DISABLE_WARNING_INCOMPATIBLE_POINTER_TYPES \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"") + +#define DISABLE_WARNING_TYPE_LIMITS \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wtype-limits\"") + +#define DISABLE_WARNING_ADDRESS \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Waddress\"") + +#define REENABLE_WARNING \ + _Pragma("GCC diagnostic pop") + #define _align_(x) __attribute__((__aligned__(x))) #define _alignas_(x) __attribute__((__aligned__(alignof(x)))) #define _alignptr_ __attribute__((__aligned__(sizeof(void *)))) @@ -79,7 +115,7 @@ _noreturn_ void efi_assert(const char *expr, const char *file, unsigned line, const char *function); #ifdef NDEBUG - #define assert(expr) + #define assert(expr) ({ if (!(expr)) __builtin_unreachable(); }) #define assert_not_reached() __builtin_unreachable() #else #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); }) @@ -129,6 +165,10 @@ __atomic_exchange_n(&(o), true, __ATOMIC_SEQ_CST); \ }) +#define U64_KB UINT64_C(1024) +#define U64_MB (UINT64_C(1024) * U64_KB) +#define U64_GB (UINT64_C(1024) * U64_MB) + #undef MAX #define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b)) #define __MAX(aq, a, bq, b) \ @@ -355,7 +395,40 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) { if (l > SIZE_MAX - (ali - 1)) return SIZE_MAX; /* indicate overflow */ - return ((l + ali - 1) & ~(ali - 1)); + return ((l + (ali - 1)) & ~(ali - 1)); +} + +static inline uint64_t ALIGN_TO_U64(uint64_t l, uint64_t ali) { + assert(ISPOWEROF2(ali)); + + if (l > UINT64_MAX - (ali - 1)) + return UINT64_MAX; /* indicate overflow */ + + return ((l + (ali - 1)) & ~(ali - 1)); +} + +static inline size_t ALIGN_DOWN(size_t l, size_t ali) { + assert(ISPOWEROF2(ali)); + + return l & ~(ali - 1); +} + +static inline uint64_t ALIGN_DOWN_U64(uint64_t l, uint64_t ali) { + assert(ISPOWEROF2(ali)); + + return l & ~(ali - 1); +} + +static inline size_t ALIGN_OFFSET(size_t l, size_t ali) { + assert(ISPOWEROF2(ali)); + + return l & (ali - 1); +} + +static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { + assert(ISPOWEROF2(ali)); + + return l & (ali - 1); } #define ALIGN2(l) ALIGN_TO(l, 2) @@ -399,6 +472,42 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) { #define FLAGS_SET(v, flags) \ ((~(v) & (flags)) == 0) +/* A wrapper for 'func' to return void. + * Only useful when a void-returning function is required by some API. */ +#define DEFINE_TRIVIAL_DESTRUCTOR(name, type, func) \ + static inline void name(type *p) { \ + func(p); \ + } + +/* When func() returns the void value (NULL, -1, …) of the appropriate type */ +#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ + static inline void func##p(type *p) { \ + if (*p) \ + *p = func(*p); \ + } + +/* When func() doesn't return the appropriate type, set variable to empty afterwards. + * The func() may be provided by a dynamically loaded shared library, hence add an assertion. */ +#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(type, func, empty) \ + static inline void func##p(type *p) { \ + if (*p != (empty)) { \ + DISABLE_WARNING_ADDRESS; \ + assert(func); \ + REENABLE_WARNING; \ + func(*p); \ + *p = (empty); \ + } \ + } + +/* When func() doesn't return the appropriate type, and is also a macro, set variable to empty afterwards. */ +#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(type, func, empty) \ + static inline void func##p(type *p) { \ + if (*p != (empty)) { \ + func(*p); \ + *p = (empty); \ + } \ + } + /* Declare a flexible array usable in a union. * This is essentially a work-around for a pointless constraint in C99 * and might go away in some future version of the standard. @@ -410,3 +519,16 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) { dummy_t __empty__ ## name; \ type name[]; \ } + +/* Declares an ELF read-only string section that does not occupy memory at runtime. */ +#define DECLARE_NOALLOC_SECTION(name, text) \ + asm(".pushsection " name ",\"S\"\n\t" \ + ".ascii " STRINGIFY(text) "\n\t" \ + ".zero 1\n\t" \ + ".popsection\n") + +#ifdef SBAT_DISTRO + #define DECLARE_SBAT(text) DECLARE_NOALLOC_SECTION(".sbat", text) +#else + #define DECLARE_SBAT(text) +#endif diff --git a/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h index 78e2dbec59..6870f54f58 100644 --- a/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h @@ -11,6 +11,12 @@ #include "macro-fundamental.h" +#define memzero(x, l) \ + ({ \ + size_t _l_ = (l); \ + _l_ > 0 ? memset((x), 0, _l_) : (x); \ + }) + #if !SD_BOOT && HAVE_EXPLICIT_BZERO static inline void *explicit_bzero_safe(void *p, size_t l) { if (p && l > 0) @@ -64,3 +70,39 @@ static inline void erase_varp(struct VarEraser *e) { .p = (ptr), \ .size = (sz), \ } + +typedef void (*free_array_func_t)(void *p, size_t n); + +/* An automatic _cleanup_-like logic for destroy arrays (i.e. pointers + size) when leaving scope */ +typedef struct ArrayCleanup { + void **parray; + size_t *pn; + free_array_func_t pfunc; +} ArrayCleanup; + +static inline void array_cleanup(const ArrayCleanup *c) { + assert(c); + + assert(!c->parray == !c->pn); + + if (!c->parray) + return; + + if (*c->parray) { + assert(c->pfunc); + c->pfunc(*c->parray, *c->pn); + *c->parray = NULL; + } + + *c->pn = 0; +} + +#define CLEANUP_ARRAY(array, n, func) \ + _cleanup_(array_cleanup) _unused_ const ArrayCleanup CONCATENATE(_cleanup_array_, UNIQ) = { \ + .parray = (void**) &(array), \ + .pn = &(n), \ + .pfunc = (free_array_func_t) ({ \ + void (*_f)(typeof(array[0]) *a, size_t b) = func; \ + _f; \ + }), \ + } diff --git a/src/libnm-systemd-shared/src/fundamental/sha256.c b/src/libnm-systemd-shared/src/fundamental/sha256.c index a4c6d627b7..84113aed6b 100644 --- a/src/libnm-systemd-shared/src/fundamental/sha256.c +++ b/src/libnm-systemd-shared/src/fundamental/sha256.c @@ -36,16 +36,9 @@ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ # define SWAP(n) \ - (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) + __builtin_bswap32(n) # define SWAP64(n) \ - (((n) << 56) \ - | (((n) & 0xff00) << 40) \ - | (((n) & 0xff0000) << 24) \ - | (((n) & 0xff000000) << 8) \ - | (((n) >> 8) & 0xff000000) \ - | (((n) >> 24) & 0xff0000) \ - | (((n) >> 40) & 0xff00) \ - | ((n) >> 56)) + __builtin_bswap64(n) #else # define SWAP(n) (n) # define SWAP64(n) (n) diff --git a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c index 3a3e7f593a..da810cb749 100644 --- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c +++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c @@ -35,14 +35,14 @@ sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) { return (sd_char*) s + l; } -sd_char* endswith(const sd_char *s, const sd_char *postfix) { +sd_char* endswith(const sd_char *s, const sd_char *suffix) { size_t sl, pl; assert(s); - assert(postfix); + assert(suffix); sl = strlen(s); - pl = strlen(postfix); + pl = strlen(suffix); if (pl == 0) return (sd_char*) s + sl; @@ -50,20 +50,20 @@ sd_char* endswith(const sd_char *s, const sd_char *postfix) { if (sl < pl) return NULL; - if (strcmp(s + sl - pl, postfix) != 0) + if (!streq(s + sl - pl, suffix)) return NULL; return (sd_char*) s + sl - pl; } -sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) { +sd_char* endswith_no_case(const sd_char *s, const sd_char *suffix) { size_t sl, pl; assert(s); - assert(postfix); + assert(suffix); sl = strlen(s); - pl = strlen(postfix); + pl = strlen(suffix); if (pl == 0) return (sd_char*) s + sl; @@ -71,7 +71,7 @@ sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) { if (sl < pl) return NULL; - if (strcasecmp(s + sl - pl, postfix) != 0) + if (!strcaseeq(s + sl - pl, suffix)) return NULL; return (sd_char*) s + sl - pl; diff --git a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h index 9019542b16..419f1cc3da 100644 --- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h @@ -59,8 +59,8 @@ static inline size_t strlen_ptr(const sd_char *s) { sd_char *startswith(const sd_char *s, const sd_char *prefix) _pure_; sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) _pure_; -sd_char *endswith(const sd_char *s, const sd_char *postfix) _pure_; -sd_char *endswith_no_case(const sd_char *s, const sd_char *postfix) _pure_; +sd_char *endswith(const sd_char *s, const sd_char *suffix) _pure_; +sd_char *endswith_no_case(const sd_char *s, const sd_char *suffix) _pure_; static inline bool isempty(const sd_char *a) { return !a || a[0] == '\0'; @@ -74,6 +74,10 @@ static inline const sd_char *yes_no(bool b) { return b ? STR_C("yes") : STR_C("no"); } +static inline const sd_char *on_off(bool b) { + return b ? STR_C("on") : STR_C("off"); +} + static inline const sd_char* comparison_operator(int result) { return result < 0 ? STR_C("<") : result > 0 ? STR_C(">") : STR_C("=="); } diff --git a/src/libnm-systemd-shared/src/shared/dns-domain.c b/src/libnm-systemd-shared/src/shared/dns-domain.c index 43f43197a9..a07eaa33c8 100644 --- a/src/libnm-systemd-shared/src/shared/dns-domain.c +++ b/src/libnm-systemd-shared/src/shared/dns-domain.c @@ -87,12 +87,9 @@ int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags f ((unsigned) (n[1] - '0') * 10) + ((unsigned) (n[2] - '0')); - /* Don't allow anything that doesn't - * fit in 8bit. Note that we do allow - * control characters, as some servers - * (e.g. cloudflare) are happy to - * generate labels with them - * inside. */ + /* Don't allow anything that doesn't fit in 8 bits. Note that we do allow + * control characters, as some servers (e.g. cloudflare) are happy to + * generate labels with them inside. */ if (k > 255) return -EINVAL; @@ -213,7 +210,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { char *q; /* DNS labels must be between 1 and 63 characters long. A - * zero-length label does not exist. See RFC 2182, Section + * zero-length label does not exist. See RFC 2181, Section * 11. */ if (l <= 0 || l > DNS_LABEL_MAX) @@ -302,14 +299,14 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) { int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { _cleanup_free_ uint32_t *input = NULL; size_t input_size, l; - bool contains_8bit = false; + bool contains_8_bit = false; char buffer[DNS_LABEL_MAX+1]; int r; assert(encoded); assert(decoded); - /* Converts an U-label into an A-label */ + /* Converts a U-label into an A-label */ r = dlopen_idn(); if (r < 0) @@ -320,9 +317,9 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded for (const char *p = encoded; p < encoded + encoded_size; p++) if ((uint8_t) *p > 127) - contains_8bit = true; + contains_8_bit = true; - if (!contains_8bit) { + if (!contains_8_bit) { if (encoded_size > DNS_LABEL_MAX) return -EINVAL; @@ -361,7 +358,7 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t w; int r; - /* To be invoked after unescaping. Converts an A-label into an U-label. */ + /* To be invoked after unescaping. Converts an A-label into a U-label. */ assert(encoded); assert(decoded); @@ -419,7 +416,7 @@ int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_r goto finish; for (;;) { - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; r = dns_label_unescape(&p, label, sizeof label, flags); if (r < 0) @@ -517,7 +514,7 @@ int dns_name_compare_func(const char *a, const char *b) { y = b + strlen(b); for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; if (x == NULL && y == NULL) return 0; @@ -553,7 +550,7 @@ int dns_name_equal(const char *x, const char *y) { assert(y); for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; r = dns_label_unescape(&x, la, sizeof la, 0); if (r < 0) @@ -584,7 +581,7 @@ int dns_name_endswith(const char *name, const char *suffix) { s = suffix; for (;;) { - char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; r = dns_label_unescape(&n, ln, sizeof ln, 0); if (r < 0) @@ -622,7 +619,7 @@ int dns_name_startswith(const char *name, const char *prefix) { p = prefix; for (;;) { - char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; + char ln[DNS_LABEL_MAX+1], lp[DNS_LABEL_MAX+1]; r = dns_label_unescape(&p, lp, sizeof lp, 0); if (r < 0) @@ -654,7 +651,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char s = old_suffix; for (;;) { - char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; if (!saved_before) saved_before = n; @@ -941,7 +938,7 @@ bool dns_srv_type_is_valid(const char *name) { return false; for (;;) { - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; /* This more or less implements RFC 6335, Section 5.1 */ @@ -1239,7 +1236,7 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) { return m; for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; const char *x, *y; if (k >= n || k >= m) { @@ -1340,7 +1337,7 @@ int dns_name_apply_idna(const char *name, char **ret) { assert(ret); for (;;) { - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; r = dns_label_unescape(&name, label, sizeof label, 0); if (r < 0) @@ -1425,6 +1422,10 @@ bool dns_name_dont_resolve(const char *name) { if (dns_name_endswith(name, "invalid") > 0) return true; + /* Never respond to some of the domains listed in RFC9476 */ + if (dns_name_endswith(name, "alt") > 0) + return true; + return false; } #endif /* NM_IGNORED */ |