diff options
Diffstat (limited to 'src/libnm-systemd-shared')
68 files changed, 1506 insertions, 2092 deletions
diff --git a/src/libnm-systemd-shared/meson.build b/src/libnm-systemd-shared/meson.build index 68daa20e..05c81faf 100644 --- a/src/libnm-systemd-shared/meson.build +++ b/src/libnm-systemd-shared/meson.build @@ -19,6 +19,7 @@ libnm_systemd_shared = static_library( 'src/basic/hexdecoct.c', 'src/basic/hostname-util.c', 'src/basic/in-addr-util.c', + 'src/basic/inotify-util.c', 'src/basic/io-util.c', 'src/basic/memory-util.c', 'src/basic/mempool.c', diff --git a/src/libnm-systemd-shared/sd-adapt-shared/chase-symlinks.h b/src/libnm-systemd-shared/sd-adapt-shared/chase-symlinks.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-shared/sd-adapt-shared/chase-symlinks.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-shared/sd-adapt-shared/missing_ioprio.h b/src/libnm-systemd-shared/sd-adapt-shared/missing_ioprio.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-shared/sd-adapt-shared/missing_ioprio.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h b/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h index 2f6188dc..586c3a7e 100644 --- a/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h +++ b/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h @@ -29,7 +29,7 @@ /*****************************************************************************/ #ifndef VALGRIND - #define VALGRIND 0 +#define VALGRIND 0 #endif #define ENABLE_DEBUG_HASHMAP 0 @@ -41,17 +41,17 @@ #if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD - #include <sys/syscall.h> - #include <sys/ioctl.h> - #include <pthread.h> +#include <sys/syscall.h> +#include <sys/ioctl.h> +#include <pthread.h> - #define ENABLE_GSHADOW FALSE +#define ENABLE_GSHADOW FALSE - #define HAVE_SECCOMP 0 +#define HAVE_SECCOMP 0 - #define LOG_TRACE 0 +#define LOG_TRACE 0 - #define BUILD_MODE_DEVELOPER (NM_MORE_ASSERTS > 0) +#define BUILD_MODE_DEVELOPER (NM_MORE_ASSERTS > 0) /*****************************************************************************/ @@ -68,63 +68,63 @@ struct statx; static inline pid_t raw_getpid(void) { - #if defined(__alpha__) +#if defined(__alpha__) return (pid_t) syscall(__NR_getxpid); - #else +#else return (pid_t) syscall(__NR_getpid); - #endif +#endif } - #define gettid() nm_utils_gettid() - - /* we build with C11 and thus <uchar.h> provides char32_t,char16_t. */ - #define HAVE_CHAR32_T 1 - #define HAVE_CHAR16_T 1 - - #if defined(HAVE_DECL_REALLOCARRAY) && HAVE_DECL_REALLOCARRAY == 1 - #define HAVE_REALLOCARRAY 1 - #else - #define HAVE_REALLOCARRAY 0 - #endif - - #if defined(HAVE_DECL_EXPLICIT_BZERO) && HAVE_DECL_EXPLICIT_BZERO == 1 - #define HAVE_EXPLICIT_BZERO 1 - #else - #define HAVE_EXPLICIT_BZERO 0 - #endif - - #if defined(HAVE_DECL_PIDFD_OPEN) && HAVE_DECL_PIDFD_OPEN == 1 - #define HAVE_PIDFD_OPEN 1 - #else - #define HAVE_PIDFD_OPEN 0 - #endif - - #if defined(HAVE_DECL_PIDFD_SEND_SIGNAL) && HAVE_DECL_PIDFD_SEND_SIGNAL == 1 - #define HAVE_PIDFD_SEND_SIGNAL 1 - #else - #define HAVE_PIDFD_SEND_SIGNAL 0 - #endif - - #if defined(HAVE_DECL_RT_SIGQUEUEINFO) && HAVE_DECL_RT_SIGQUEUEINFO == 1 - #define HAVE_RT_SIGQUEUEINFO 1 - #else - #define HAVE_RT_SIGQUEUEINFO 0 - #endif - - #ifndef ALTIFNAMSIZ - #define ALTIFNAMSIZ 128 - #endif - - #define HAVE_LINUX_TIME_TYPES_H 0 - - #ifndef __COMPAR_FN_T - #define __COMPAR_FN_T +#define gettid() nm_utils_gettid() + +/* we build with C11 and thus <uchar.h> provides char32_t,char16_t. */ +#define HAVE_CHAR32_T 1 +#define HAVE_CHAR16_T 1 + +#if defined(HAVE_DECL_REALLOCARRAY) && HAVE_DECL_REALLOCARRAY == 1 +#define HAVE_REALLOCARRAY 1 +#else +#define HAVE_REALLOCARRAY 0 +#endif + +#if defined(HAVE_DECL_EXPLICIT_BZERO) && HAVE_DECL_EXPLICIT_BZERO == 1 +#define HAVE_EXPLICIT_BZERO 1 +#else +#define HAVE_EXPLICIT_BZERO 0 +#endif + +#if defined(HAVE_DECL_PIDFD_OPEN) && HAVE_DECL_PIDFD_OPEN == 1 +#define HAVE_PIDFD_OPEN 1 +#else +#define HAVE_PIDFD_OPEN 0 +#endif + +#if defined(HAVE_DECL_PIDFD_SEND_SIGNAL) && HAVE_DECL_PIDFD_SEND_SIGNAL == 1 +#define HAVE_PIDFD_SEND_SIGNAL 1 +#else +#define HAVE_PIDFD_SEND_SIGNAL 0 +#endif + +#if defined(HAVE_DECL_RT_SIGQUEUEINFO) && HAVE_DECL_RT_SIGQUEUEINFO == 1 +#define HAVE_RT_SIGQUEUEINFO 1 +#else +#define HAVE_RT_SIGQUEUEINFO 0 +#endif + +#ifndef ALTIFNAMSIZ +#define ALTIFNAMSIZ 128 +#endif + +#define HAVE_LINUX_TIME_TYPES_H 0 + +#ifndef __COMPAR_FN_T +#define __COMPAR_FN_T typedef int (*__compar_fn_t)(const void *, const void *); typedef __compar_fn_t comparison_fn_t; typedef int (*__compar_d_fn_t)(const void *, const void *, void *); - #endif +#endif - #ifndef __GLIBC__ +#ifndef __GLIBC__ static inline int __register_atfork(void (*prepare)(void), void (*parent)(void), @@ -133,7 +133,7 @@ __register_atfork(void (*prepare)(void), { return pthread_atfork(prepare, parent, child); } - #endif +#endif #endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */ diff --git a/src/libnm-systemd-shared/sd-adapt-shared/sync-util.h b/src/libnm-systemd-shared/sd-adapt-shared/sync-util.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-shared/sd-adapt-shared/sync-util.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.h b/src/libnm-systemd-shared/src/basic/alloc-util.h index 99fbd3a8..e587fe79 100644 --- a/src/libnm-systemd-shared/src/basic/alloc-util.h +++ b/src/libnm-systemd-shared/src/basic/alloc-util.h @@ -44,16 +44,19 @@ typedef void (*free_func_t)(void *p); #define malloc0(n) (calloc(1, (n) ?: 1)) -static inline void *mfree(void *memory) { - free(memory); - return NULL; -} +#define mfree(memory) \ + ({ \ + free(memory); \ + (typeof(memory)) NULL; \ + }) #define free_and_replace(a, b) \ ({ \ - free(a); \ - (a) = (b); \ - (b) = NULL; \ + typeof(a)* _a = &(a); \ + typeof(b)* _b = &(b); \ + free(*_a); \ + (*_a) = (*_b); \ + (*_b) = NULL; \ 0; \ }) @@ -79,6 +82,13 @@ void* memdup_suffix0(const void *p, size_t l); /* We can't use _alloc_() here, s memcpy_safe(_q_, p, _l_); \ }) +static inline void unsetp(void *p) { + /* A trivial "destructor" that can be used in cases where we want to + * unset a pointer from a _cleanup_ function. */ + + *(void**)p = NULL; +} + static inline void freep(void *p) { *(void**)p = mfree(*(void**) p); } diff --git a/src/libnm-systemd-shared/src/basic/cgroup-util.h b/src/libnm-systemd-shared/src/basic/cgroup-util.h index ce2f4c65..90ccd2c0 100644 --- a/src/libnm-systemd-shared/src/basic/cgroup-util.h +++ b/src/libnm-systemd-shared/src/basic/cgroup-util.h @@ -32,6 +32,7 @@ typedef enum CGroupController { CGROUP_CONTROLLER_BPF_DEVICES, CGROUP_CONTROLLER_BPF_FOREIGN, CGROUP_CONTROLLER_BPF_SOCKET_BIND, + CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES, _CGROUP_CONTROLLER_MAX, _CGROUP_CONTROLLER_INVALID = -EINVAL, @@ -53,6 +54,7 @@ typedef enum CGroupMask { CGROUP_MASK_BPF_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_DEVICES), CGROUP_MASK_BPF_FOREIGN = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_FOREIGN), CGROUP_MASK_BPF_SOCKET_BIND = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_SOCKET_BIND), + CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_RESTRICT_NETWORK_INTERFACES), /* All real cgroup v1 controllers */ CGROUP_MASK_V1 = CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT|CGROUP_MASK_BLKIO|CGROUP_MASK_MEMORY|CGROUP_MASK_DEVICES|CGROUP_MASK_PIDS, @@ -61,7 +63,7 @@ typedef enum CGroupMask { CGROUP_MASK_V2 = CGROUP_MASK_CPU|CGROUP_MASK_CPUSET|CGROUP_MASK_IO|CGROUP_MASK_MEMORY|CGROUP_MASK_PIDS, /* 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 = 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 } CGroupMask; @@ -172,6 +174,7 @@ typedef enum CGroupFlags { typedef int (*cg_kill_log_func_t)(pid_t 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_split_spec(const char *spec, char **ret_controller, char **ret_path); @@ -272,6 +275,7 @@ int cg_kernel_controllers(Set **controllers); bool cg_ns_supported(void); bool cg_freezer_supported(void); +bool cg_kill_supported(void); int cg_all_unified(void); int cg_hybrid_unified(void); diff --git a/src/libnm-systemd-shared/src/basic/env-util.c b/src/libnm-systemd-shared/src/basic/env-util.c index 849ee47e..431862f6 100644 --- a/src/libnm-systemd-shared/src/basic/env-util.c +++ b/src/libnm-systemd-shared/src/basic/env-util.c @@ -186,39 +186,51 @@ static int env_append(char **r, char ***k, char **a) { return 0; } -char **strv_env_merge(size_t n_lists, ...) { - _cleanup_strv_free_ char **ret = NULL; - size_t n = 0; - char **l, **k; +char** _strv_env_merge(char **first, ...) { + _cleanup_strv_free_ char **merged = NULL; + char **k; va_list ap; /* Merges an arbitrary number of environment sets */ - va_start(ap, n_lists); - for (size_t i = 0; i < n_lists; i++) { + size_t n = strv_length(first); + + va_start(ap, first); + for (;;) { + char **l; + l = va_arg(ap, char**); + if (l == POINTER_MAX) + break; + n += strv_length(l); } va_end(ap); - ret = new(char*, n+1); - if (!ret) + k = merged = new(char*, n + 1); + if (!merged) + return NULL; + merged[0] = NULL; + + if (env_append(merged, &k, first) < 0) return NULL; - *ret = NULL; - k = ret; + va_start(ap, first); + for (;;) { + char **l; - va_start(ap, n_lists); - for (size_t i = 0; i < n_lists; i++) { l = va_arg(ap, char**); - if (env_append(ret, &k, l) < 0) { + if (l == POINTER_MAX) + break; + + if (env_append(merged, &k, l) < 0) { va_end(ap); return NULL; } } va_end(ap); - return TAKE_PTR(ret); + return TAKE_PTR(merged); } static bool env_match(const char *t, const char *pattern) { @@ -412,6 +424,32 @@ int strv_env_replace_strdup(char ***l, const char *assignment) { return strv_env_replace_consume(l, p); } +int strv_env_replace_strdup_passthrough(char ***l, const char *assignment) { + /* Like strv_env_replace_strdup(), but pulls the variable from the environment of + * the calling program, if a variable name without value is specified. + */ + char *p; + + if (strchr(assignment, '=')) { + if (!env_assignment_is_valid(assignment)) + return -EINVAL; + + p = strdup(assignment); + } else { + if (!env_name_is_valid(assignment)) + return -EINVAL; + + /* If we can't find the variable in our environment, we will use + * the empty string. This way "passthrough" is equivalent to passing + * --setenv=FOO=$FOO in the shell. */ + p = strjoin(assignment, "=", secure_getenv(assignment)); + } + if (!p) + return -ENOMEM; + + return strv_env_replace_consume(l, p); +} + int strv_env_assign(char ***l, const char *key, const char *value) { if (!env_name_is_valid(key)) return -EINVAL; @@ -580,6 +618,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { word = e+1; state = WORD; + nest--; } else if (*e == ':') { if (flags & REPLACE_ENV_ALLOW_EXTENDED) { len = e - word - 2; @@ -837,4 +876,21 @@ int getenv_path_list(const char *name, char ***ret_paths) { *ret_paths = TAKE_PTR(l); return 1; } + +int unsetenv_erase(const char *name) { + char *p; + + assert(name); + + p = getenv(name); + if (!p) + return 0; + + string_erase(p); + + if (unsetenv(name) < 0) + return -errno; + + return 1; +} #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 1fbe7e42..38bfc8a3 100644 --- a/src/libnm-systemd-shared/src/basic/env-util.h +++ b/src/libnm-systemd-shared/src/basic/env-util.h @@ -39,13 +39,15 @@ char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const cha bool strv_env_name_is_valid(char **l); bool strv_env_name_or_assignment_is_valid(char **l); -char **strv_env_merge(size_t n_lists, ...); +char** _strv_env_merge(char **first, ...); +#define strv_env_merge(first, ...) _strv_env_merge(first, __VA_ARGS__, POINTER_MAX) char **strv_env_delete(char **x, size_t n_lists, ...); /* New copy */ char **strv_env_unset(char **l, const char *p); /* In place ... */ char **strv_env_unset_many(char **l, ...) _sentinel_; 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); char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_; @@ -66,3 +68,5 @@ int setenv_systemd_exec_pid(bool update_only); /* Parses and does sanity checks on an environment variable containing * PATH-like colon-separated absolute paths */ int getenv_path_list(const char *name, char ***ret_paths); + +int unsetenv_erase(const char *name); diff --git a/src/libnm-systemd-shared/src/basic/escape.c b/src/libnm-systemd-shared/src/basic/escape.c index 6a138020..93288563 100644 --- a/src/libnm-systemd-shared/src/basic/escape.c +++ b/src/libnm-systemd-shared/src/basic/escape.c @@ -10,6 +10,7 @@ #include "escape.h" #include "hexdecoct.h" #include "macro.h" +#include "strv.h" #include "utf8.h" int cescape_char(char c, char *buf) { @@ -291,10 +292,12 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, } #if 0 /* NM_IGNORED */ -int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { - char *r, *t; +ssize_t cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { + _cleanup_free_ char *ans = NULL; + char *t; const char *f; size_t pl; + int r; assert(s); assert(ret); @@ -303,18 +306,17 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi pl = strlen_ptr(prefix); - r = new(char, pl+length+1); - if (!r) + ans = new(char, pl+length+1); + if (!ans) return -ENOMEM; if (prefix) - memcpy(r, prefix, pl); + memcpy(ans, prefix, pl); - for (f = s, t = r + pl; f < s + length; f++) { + for (f = s, t = ans + pl; f < s + length; f++) { size_t remaining; bool eight_bit = false; char32_t u; - int k; remaining = s + length - f; assert(remaining > 0); @@ -332,23 +334,21 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi continue; } - free(r); return -EINVAL; } - k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL); - if (k < 0) { + r = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL); + if (r < 0) { if (flags & UNESCAPE_RELAX) { /* Invalid escape code, let's take it literal then */ *(t++) = '\\'; continue; } - free(r); - return k; + return r; } - f += k; + f += r; if (eight_bit) /* One byte? Set directly as specified */ *(t++) = u; @@ -359,8 +359,9 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi *t = 0; - *ret = r; - return t - r; + assert(t >= ans); /* Let static analyzers know that the answer is non-negative. */ + *ret = TAKE_PTR(ans); + return t - *ret; } char* xescape_full(const char *s, const char *bad, size_t console_width, XEscapeFlags flags) { @@ -445,7 +446,6 @@ char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFl console_width, FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS)); } -#endif /* NM_IGNORED */ char* octescape(const char *s, size_t len) { char *r, *t; @@ -546,3 +546,24 @@ char* shell_maybe_quote(const char *s, ShellEscapeFlags flags) { return str_realloc(buf); } + +char* quote_command_line(char **argv) { + _cleanup_free_ char *result = NULL; + + assert(argv); + + char **a; + STRV_FOREACH(a, argv) { + _cleanup_free_ char *t = NULL; + + t = shell_maybe_quote(*a, SHELL_ESCAPE_EMPTY); + if (!t) + return NULL; + + if (!strextend_with_separator(&result, " ", t)) + return NULL; + } + + return TAKE_PTR(result); +} +#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/escape.h b/src/libnm-systemd-shared/src/basic/escape.h index 907b572b..d490510d 100644 --- a/src/libnm-systemd-shared/src/basic/escape.h +++ b/src/libnm-systemd-shared/src/basic/escape.h @@ -45,14 +45,15 @@ char* cescape(const char *s); char* cescape_length(const char *s, size_t n); int cescape_char(char c, char *buf); -int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); -static inline int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { +int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul); + +ssize_t cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); +static inline ssize_t cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { return cunescape_length_with_prefix(s, length, NULL, flags, ret); } -static inline int cunescape(const char *s, UnescapeFlags flags, char **ret) { +static inline ssize_t cunescape(const char *s, UnescapeFlags flags, char **ret) { return cunescape_length(s, strlen(s), flags, ret); } -int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul); typedef enum XEscapeFlags { XESCAPE_8_BIT = 1 << 0, @@ -68,3 +69,4 @@ char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFl char* shell_escape(const char *s, const char *bad); char* shell_maybe_quote(const char *s, ShellEscapeFlags flags); +char* quote_command_line(char **argv); 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 f60e1042..c73dcff1 100644 --- a/src/libnm-systemd-shared/src/basic/ether-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.c @@ -12,13 +12,13 @@ #include "macro.h" #include "string-util.h" -char* hw_addr_to_string(const hw_addr_data *addr, char buffer[HW_ADDR_TO_STRING_MAX]) { +char* hw_addr_to_string(const struct hw_addr_data *addr, char buffer[HW_ADDR_TO_STRING_MAX]) { assert(addr); assert(buffer); assert(addr->length <= HW_ADDR_MAX_SIZE); for (size_t i = 0; i < addr->length; i++) { - sprintf(&buffer[3*i], "%02"PRIx8, addr->addr.bytes[i]); + sprintf(&buffer[3*i], "%02"PRIx8, addr->bytes[i]); if (i < addr->length - 1) buffer[3*i + 2] = ':'; } @@ -26,6 +26,29 @@ char* hw_addr_to_string(const hw_addr_data *addr, char buffer[HW_ADDR_TO_STRING_ return buffer; } +int hw_addr_compare(const struct hw_addr_data *a, const struct hw_addr_data *b) { + int r; + + assert(a); + assert(b); + + r = CMP(a->length, b->length); + if (r != 0) + return r; + + return memcmp(a->bytes, b->bytes, a->length); +} + +static 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); +} + +DEFINE_HASH_OPS(hw_addr_hash_ops, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare); + char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]) { assert(addr); assert(buffer); 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 712c9277..794fc55b 100644 --- a/src/libnm-systemd-shared/src/basic/ether-addr-util.h +++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.h @@ -11,24 +11,34 @@ * defines a macro of the same name with a much lower size. */ #define HW_ADDR_MAX_SIZE 32 -union hw_addr_union { - struct ether_addr ether; - uint8_t infiniband[INFINIBAND_ALEN]; - uint8_t bytes[HW_ADDR_MAX_SIZE]; -}; - -typedef struct hw_addr_data { - union hw_addr_union addr; +struct hw_addr_data { size_t length; -} hw_addr_data; + union { + struct ether_addr ether; + uint8_t infiniband[INFINIBAND_ALEN]; + uint8_t bytes[HW_ADDR_MAX_SIZE]; + }; +}; #define HW_ADDR_TO_STRING_MAX (3*HW_ADDR_MAX_SIZE) -char* hw_addr_to_string(const hw_addr_data *addr, char buffer[HW_ADDR_TO_STRING_MAX]); +char* hw_addr_to_string(const struct hw_addr_data *addr, char buffer[HW_ADDR_TO_STRING_MAX]); -/* Use only as function argument, never stand-alone! */ +/* Note: the lifetime of the compound literal is the immediately surrounding block, + * see C11 ยง6.5.2.5, and + * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */ #define HW_ADDR_TO_STR(hw_addr) hw_addr_to_string((hw_addr), (char[HW_ADDR_TO_STRING_MAX]){}) -#define HW_ADDR_NULL ((const hw_addr_data){}) +#define HW_ADDR_NULL ((const struct hw_addr_data){}) + +int hw_addr_compare(const struct hw_addr_data *a, const struct hw_addr_data *b); +static inline bool hw_addr_equal(const struct hw_addr_data *a, const struct hw_addr_data *b) { + return hw_addr_compare(a, b) == 0; +} +static inline bool hw_addr_is_null(const struct hw_addr_data *addr) { + return hw_addr_equal(addr, &HW_ADDR_NULL); +} + +extern const struct hash_ops hw_addr_hash_ops; #define ETHER_ADDR_FORMAT_STR "%02X%02X%02X%02X%02X%02X" #define ETHER_ADDR_FORMAT_VAL(x) (x).ether_addr_octet[0], (x).ether_addr_octet[1], (x).ether_addr_octet[2], (x).ether_addr_octet[3], (x).ether_addr_octet[4], (x).ether_addr_octet[5] @@ -36,6 +46,8 @@ char* hw_addr_to_string(const hw_addr_data *addr, char buffer[HW_ADDR_TO_STRING_ #define ETHER_ADDR_TO_STRING_MAX (3*6) char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]); int ether_addr_to_string_alloc(const struct ether_addr *addr, char **ret); +/* Use only as function argument, never stand-alone! */ +#define ETHER_ADDR_TO_STR(addr) ether_addr_to_string((addr), (char[ETHER_ADDR_TO_STRING_MAX]){}) int ether_addr_compare(const struct ether_addr *a, const struct ether_addr *b); static inline bool ether_addr_equal(const struct ether_addr *a, const struct ether_addr *b) { diff --git a/src/libnm-systemd-shared/src/basic/extract-word.c b/src/libnm-systemd-shared/src/basic/extract-word.c index b339eae2..6781fb55 100644 --- a/src/libnm-systemd-shared/src/basic/extract-word.c +++ b/src/libnm-systemd-shared/src/basic/extract-word.c @@ -29,6 +29,7 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra assert(p); assert(ret); + assert(!FLAGS_SET(flags, EXTRACT_KEEP_QUOTE | EXTRACT_UNQUOTE)); /* Bail early if called after last value or with no input */ if (!*p) @@ -52,7 +53,8 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra goto finish_force_terminate; else if (strchr(separators, c)) { if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { - (*p)++; + if (!(flags & EXTRACT_RETAIN_SEPARATORS)) + (*p)++; goto finish_force_next; } } else { @@ -125,48 +127,58 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra return -EINVAL; } else if (c == quote) { /* found the end quote */ quote = 0; - break; + if (flags & EXTRACT_UNQUOTE) + break; } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { backslash = true; break; - } else { - if (!GREEDY_REALLOC(s, sz+2)) - return -ENOMEM; - - s[sz++] = c; } + + if (!GREEDY_REALLOC(s, sz+2)) + return -ENOMEM; + + s[sz++] = c; + + if (quote == 0) + break; } } else { for (;; (*p)++, c = **p) { if (c == 0) goto finish_force_terminate; - else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_UNQUOTE)) { + else if (IN_SET(c, '\'', '"') && (flags & (EXTRACT_KEEP_QUOTE | EXTRACT_UNQUOTE))) { quote = c; - break; + if (flags & EXTRACT_UNQUOTE) + break; } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { backslash = true; break; } else if (strchr(separators, c)) { if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { - (*p)++; + if (!(flags & EXTRACT_RETAIN_SEPARATORS)) + (*p)++; goto finish_force_next; } - /* Skip additional coalesced separators. */ - for (;; (*p)++, c = **p) { - if (c == 0) - goto finish_force_terminate; - if (!strchr(separators, c)) - break; - } + if (!(flags & EXTRACT_RETAIN_SEPARATORS)) + /* Skip additional coalesced separators. */ + for (;; (*p)++, c = **p) { + if (c == 0) + goto finish_force_terminate; + if (!strchr(separators, c)) + break; + } goto finish; - } else { - if (!GREEDY_REALLOC(s, sz+2)) - return -ENOMEM; - - s[sz++] = c; } + + if (!GREEDY_REALLOC(s, sz+2)) + return -ENOMEM; + + s[sz++] = c; + + if (quote != 0) + break; } } } diff --git a/src/libnm-systemd-shared/src/basic/extract-word.h b/src/libnm-systemd-shared/src/basic/extract-word.h index 0e9e77e9..c82ad761 100644 --- a/src/libnm-systemd-shared/src/basic/extract-word.h +++ b/src/libnm-systemd-shared/src/basic/extract-word.h @@ -8,9 +8,11 @@ typedef enum ExtractFlags { EXTRACT_CUNESCAPE = 1 << 1, /* Unescape known escape sequences. */ EXTRACT_UNESCAPE_RELAX = 1 << 2, /* Allow and keep unknown escape sequences, allow and keep trailing backslash. */ EXTRACT_UNESCAPE_SEPARATORS = 1 << 3, /* Unescape separators (those specified, or whitespace by default). */ - EXTRACT_UNQUOTE = 1 << 4, /* Remove quoting with "" and ''. */ - EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 5, /* Don't treat multiple adjacent separators as one */ - EXTRACT_RETAIN_ESCAPE = 1 << 6, /* Treat escape character '\' as any other character without special meaning */ + EXTRACT_KEEP_QUOTE = 1 << 4, /* Ignore separators in quoting with "" and ''. */ + EXTRACT_UNQUOTE = 1 << 5, /* Ignore separators in quoting with "" and '', and remove the quotes. */ + EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 6, /* Don't treat multiple adjacent separators as one */ + EXTRACT_RETAIN_ESCAPE = 1 << 7, /* Treat escape character '\' as any other character without special meaning */ + EXTRACT_RETAIN_SEPARATORS = 1 << 8, /* Do not advance the original string pointer past the separator(s) */ /* Note that if no flags are specified, escaped escape characters will be silently stripped. */ } ExtractFlags; diff --git a/src/libnm-systemd-shared/src/basic/fd-util.c b/src/libnm-systemd-shared/src/basic/fd-util.c index 670b72ff..ee2a4caf 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.c +++ b/src/libnm-systemd-shared/src/basic/fd-util.c @@ -4,19 +4,20 @@ #include <errno.h> #include <fcntl.h> +#include <linux/btrfs.h> +#include <linux/magic.h> +#include <sys/ioctl.h> #include <sys/resource.h> #include <sys/stat.h> #include <unistd.h> #include "alloc-util.h" -#include "copy.h" #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "io-util.h" #include "macro.h" -#include "memfd-util.h" #include "missing_fcntl.h" #include "missing_syscall.h" #include "parse-util.h" @@ -210,10 +211,9 @@ static int get_max_fd(void) { return (int) (m - 1); } -int close_all_fds(const int except[], size_t n_except) { +int close_all_fds_full(int except[], size_t n_except, bool allow_alloc) { static bool have_close_range = true; /* Assume we live in the future */ _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; int r = 0; assert(n_except == 0 || except); @@ -229,129 +229,104 @@ int close_all_fds(const int except[], size_t n_except) { /* Close everything. Yay! */ if (close_range(3, -1, 0) >= 0) - return 1; + return 0; - if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) + if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) + have_close_range = false; + else return -errno; - have_close_range = false; } else { - _cleanup_free_ int *sorted_malloc = NULL; - size_t n_sorted; - int *sorted; - - assert(n_except < SIZE_MAX); - n_sorted = n_except + 1; - - if (n_sorted > 64) /* Use heap for large numbers of fds, stack otherwise */ - sorted = sorted_malloc = new(int, n_sorted); - else - sorted = newa(int, n_sorted); - - if (sorted) { - int c = 0; - - memcpy(sorted, except, n_except * sizeof(int)); + typesafe_qsort(except, n_except, cmp_int); - /* Let's add fd 2 to the list of fds, to simplify the loop below, as this - * allows us to cover the head of the array the same way as the body */ - sorted[n_sorted-1] = 2; + for (size_t i = 0; i < n_except; i++) { + int start = i == 0 ? 2 : MAX(except[i-1], 2); /* The first three fds shall always remain open */ + int end = MAX(except[i], 2); - typesafe_qsort(sorted, n_sorted, cmp_int); + assert(end >= start); - for (size_t i = 0; i < n_sorted-1; i++) { - int start, end; - - start = MAX(sorted[i], 2); /* The first three fds shall always remain open */ - end = MAX(sorted[i+1], 2); - - assert(end >= start); - - if (end - start <= 1) - continue; - - /* Close everything between the start and end fds (both of which shall stay open) */ - if (close_range(start + 1, end - 1, 0) < 0) { - if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) - return -errno; + if (end - start <= 1) + continue; + /* Close everything between the start and end fds (both of which shall stay open) */ + if (close_range(start + 1, end - 1, 0) < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) have_close_range = false; - break; - } - - c += end - start - 1; + else + return -errno; + goto opendir_fallback; } + } - if (have_close_range) { - /* The loop succeeded. Let's now close everything beyond the end */ + /* The loop succeeded. Let's now close everything beyond the end */ - if (sorted[n_sorted-1] >= INT_MAX) /* Dont let the addition below overflow */ - return c; + if (except[n_except-1] >= INT_MAX) /* Don't let the addition below overflow */ + return 0; - if (close_range(sorted[n_sorted-1] + 1, -1, 0) >= 0) - return c + 1; + int start = MAX(except[n_except-1], 2); - if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) - return -errno; + if (close_range(start + 1, -1, 0) >= 0) + return 0; - have_close_range = false; - } - } + if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) + have_close_range = false; + else + return -errno; } - - /* Fallback on OOM or if close_range() is not supported */ } - d = opendir("/proc/self/fd"); - if (!d) { - int fd, max_fd; + /* Fallback for when close_range() is not supported */ + opendir_fallback: + d = allow_alloc ? opendir("/proc/self/fd") : NULL; + if (d) { + struct dirent *de; - /* When /proc isn't available (for example in chroots) the fallback is brute forcing through - * the fd table */ + FOREACH_DIRENT(de, d, return -errno) { + int fd = -1, q; - max_fd = get_max_fd(); - if (max_fd < 0) - return max_fd; + if (safe_atoi(de->d_name, &fd) < 0) + /* Let's better ignore this, just in case */ + continue; - /* Refuse to do the loop over more too many elements. It's better to fail immediately than to - * spin the CPU for a long time. */ - if (max_fd > MAX_FD_LOOP_LIMIT) - return log_debug_errno(SYNTHETIC_ERRNO(EPERM), - "/proc/self/fd is inaccessible. Refusing to loop over %d potential fds.", - max_fd); + if (fd < 3) + continue; - for (fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) { - int q; + if (fd == dirfd(d)) + continue; if (fd_in_set(fd, except, n_except)) continue; q = close_nointr(fd); - if (q < 0 && q != -EBADF && r >= 0) + if (q < 0 && q != -EBADF && r >= 0) /* Valgrind has its own FD and doesn't want to have it closed */ r = q; } return r; } - FOREACH_DIRENT(de, d, return -errno) { - int fd = -1, q; + /* Fallback for when /proc isn't available (for example in chroots) or when we cannot allocate by + * brute-forcing through the file descriptor table. */ - if (safe_atoi(de->d_name, &fd) < 0) - /* Let's better ignore this, just in case */ - continue; + int max_fd = get_max_fd(); + if (max_fd < 0) + return max_fd; - if (fd < 3) - continue; + /* Refuse to do the loop over more too many elements. It's better to fail immediately than to + * spin the CPU for a long time. */ + if (max_fd > MAX_FD_LOOP_LIMIT) + return log_debug_errno(SYNTHETIC_ERRNO(EPERM), + "/proc/self/fd is inaccessible. Refusing to loop over %d potential fds.", + max_fd); - if (fd == dirfd(d)) - continue; + for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) { + int q; if (fd_in_set(fd, except, n_except)) continue; q = close_nointr(fd); - if (q < 0 && q != -EBADF && r >= 0) /* Valgrind has its own FD and doesn't want to have it closed */ + if (q < 0 && q != -EBADF && r >= 0) r = q; } @@ -463,11 +438,9 @@ bool fdname_is_valid(const char *s) { #if 0 /* NM_IGNORED */ int fd_get_path(int fd, char **ret) { - char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; int r; - xsprintf(procfs_path, "/proc/self/fd/%i", fd); - r = readlink_malloc(procfs_path, ret); + r = readlink_malloc(FORMAT_PROC_FD_PATH(fd), ret); if (r == -ENOENT) { /* ENOENT can mean two things: that the fd does not exist or that /proc is not mounted. Let's make * things debuggable and distinguish the two. */ @@ -524,343 +497,6 @@ int move_fd(int from, int to, int cloexec) { return to; } - -int acquire_data_fd(const void *data, size_t size, unsigned flags) { - - _cleanup_close_pair_ int pipefds[2] = { -1, -1 }; - char pattern[] = "/dev/shm/data-fd-XXXXXX"; - _cleanup_close_ int fd = -1; - int isz = 0, r; - ssize_t n; - off_t f; - - assert(data || size == 0); - - /* Acquire a read-only file descriptor that when read from returns the specified data. This is much more - * complex than I wish it was. But here's why: - * - * a) First we try to use memfds. They are the best option, as we can seal them nicely to make them - * read-only. Unfortunately they require kernel 3.17, and โ at the time of writing โ we still support 3.14. - * - * b) Then, we try classic pipes. They are the second best options, as we can close the writing side, retaining - * a nicely read-only fd in the reading side. However, they are by default quite small, and unprivileged - * clients can only bump their size to a system-wide limit, which might be quite low. - * - * c) Then, we try an O_TMPFILE file in /dev/shm (that dir is the only suitable one known to exist from - * earliest boot on). To make it read-only we open the fd a second time with O_RDONLY via - * /proc/self/<fd>. Unfortunately O_TMPFILE is not available on older kernels on tmpfs. - * - * d) Finally, we try creating a regular file in /dev/shm, which we then delete. - * - * It sucks a bit that depending on the situation we return very different objects here, but that's Linux I - * figure. */ - - if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0)) { - /* As a special case, return /dev/null if we have been called for an empty data block */ - r = open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (r < 0) - return -errno; - - return r; - } - - if ((flags & ACQUIRE_NO_MEMFD) == 0) { - fd = memfd_new("data-fd"); - if (fd < 0) - goto try_pipe; - - n = write(fd, data, size); - if (n < 0) - return -errno; - if ((size_t) n != size) - return -EIO; - - f = lseek(fd, 0, SEEK_SET); - if (f != 0) - return -errno; - - r = memfd_set_sealed(fd); - if (r < 0) - return r; - - return TAKE_FD(fd); - } - -try_pipe: - if ((flags & ACQUIRE_NO_PIPE) == 0) { - if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0) - return -errno; - - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - - if ((size_t) isz < size) { - isz = (int) size; - if (isz < 0 || (size_t) isz != size) - return -E2BIG; - - /* Try to bump the pipe size */ - (void) fcntl(pipefds[1], F_SETPIPE_SZ, isz); - - /* See if that worked */ - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - - if ((size_t) isz < size) - goto try_dev_shm; - } - - n = write(pipefds[1], data, size); - if (n < 0) - return -errno; - if ((size_t) n != size) - return -EIO; - - (void) fd_nonblock(pipefds[0], false); - - return TAKE_FD(pipefds[0]); - } - -try_dev_shm: - if ((flags & ACQUIRE_NO_TMPFILE) == 0) { - fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500); - if (fd < 0) - goto try_dev_shm_without_o_tmpfile; - - n = write(fd, data, size); - if (n < 0) - return -errno; - if ((size_t) n != size) - return -EIO; - - /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */ - return fd_reopen(fd, O_RDONLY|O_CLOEXEC); - } - -try_dev_shm_without_o_tmpfile: - if ((flags & ACQUIRE_NO_REGULAR) == 0) { - fd = mkostemp_safe(pattern); - if (fd < 0) - return fd; - - n = write(fd, data, size); - if (n < 0) { - r = -errno; - goto unlink_and_return; - } - if ((size_t) n != size) { - r = -EIO; - goto unlink_and_return; - } - - /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */ - r = open(pattern, O_RDONLY|O_CLOEXEC); - if (r < 0) - r = -errno; - - unlink_and_return: - (void) unlink(pattern); - return r; - } - - return -EOPNOTSUPP; -} - -/* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */ -#define DATA_FD_MEMORY_LIMIT (64U*1024U) - -/* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */ -#define DATA_FD_TMP_LIMIT (1024U*1024U) - -int fd_duplicate_data_fd(int fd) { - - _cleanup_close_ int copy_fd = -1, tmp_fd = -1; - _cleanup_free_ void *remains = NULL; - size_t remains_size = 0; - const char *td; - struct stat st; - int r; - - /* Creates a 'data' fd from the specified source fd, containing all the same data in a read-only fashion, but - * independent of it (i.e. the source fd can be closed and unmounted after this call succeeded). Tries to be - * somewhat smart about where to place the data. In the best case uses a memfd(). If memfd() are not supported - * uses a pipe instead. For larger data will use an unlinked file in /tmp, and for even larger data one in - * /var/tmp. */ - - if (fstat(fd, &st) < 0) - return -errno; - - /* For now, let's only accept regular files, sockets, pipes and char devices */ - if (S_ISDIR(st.st_mode)) - return -EISDIR; - if (S_ISLNK(st.st_mode)) - return -ELOOP; - if (!S_ISREG(st.st_mode) && !S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode) && !S_ISCHR(st.st_mode)) - return -EBADFD; - - /* If we have reason to believe the data is bounded in size, then let's use memfds or pipes as backing fd. Note - * that we use the reported regular file size only as a hint, given that there are plenty special files in - * /proc and /sys which report a zero file size but can be read from. */ - - if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) { - - /* Try a memfd first */ - copy_fd = memfd_new("data-fd"); - if (copy_fd >= 0) { - off_t f; - - r = copy_bytes(fd, copy_fd, DATA_FD_MEMORY_LIMIT, 0); - if (r < 0) - return r; - - f = lseek(copy_fd, 0, SEEK_SET); - if (f != 0) - return -errno; - - if (r == 0) { - /* Did it fit into the limit? If so, we are done. */ - r = memfd_set_sealed(copy_fd); - if (r < 0) - return r; - - return TAKE_FD(copy_fd); - } - - /* Hmm, pity, this didn't fit. Let's fall back to /tmp then, see below */ - - } else { - _cleanup_(close_pairp) int pipefds[2] = { -1, -1 }; - int isz; - - /* If memfds aren't available, use a pipe. Set O_NONBLOCK so that we will get EAGAIN rather - * then block indefinitely when we hit the pipe size limit */ - - if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0) - return -errno; - - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - - /* Try to enlarge the pipe size if necessary */ - if ((size_t) isz < DATA_FD_MEMORY_LIMIT) { - - (void) fcntl(pipefds[1], F_SETPIPE_SZ, DATA_FD_MEMORY_LIMIT); - - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - } - - if ((size_t) isz >= DATA_FD_MEMORY_LIMIT) { - - r = copy_bytes_full(fd, pipefds[1], DATA_FD_MEMORY_LIMIT, 0, &remains, &remains_size, NULL, NULL); - if (r < 0 && r != -EAGAIN) - return r; /* If we get EAGAIN it could be because of the source or because of - * the destination fd, we can't know, as sendfile() and friends won't - * tell us. Hence, treat this as reason to fall back, just to be - * sure. */ - if (r == 0) { - /* Everything fit in, yay! */ - (void) fd_nonblock(pipefds[0], false); - - return TAKE_FD(pipefds[0]); - } - - /* Things didn't fit in. But we read data into the pipe, let's remember that, so that - * when writing the new file we incorporate this first. */ - copy_fd = TAKE_FD(pipefds[0]); - } - } - } - - /* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */ - if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) && - (DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) { - off_t f; - - tmp_fd = open_tmpfile_unlinkable(NULL /* NULL as directory means /tmp */, O_RDWR|O_CLOEXEC); - if (tmp_fd < 0) - return tmp_fd; - - if (copy_fd >= 0) { - /* If we tried a memfd/pipe first and it ended up being too large, then copy this into the - * temporary file first. */ - - r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, 0); - if (r < 0) - return r; - - assert(r == 0); - } - - if (remains_size > 0) { - /* If there were remaining bytes (i.e. read into memory, but not written out yet) from the - * failed copy operation, let's flush them out next. */ - - r = loop_write(tmp_fd, remains, remains_size, false); - if (r < 0) - return r; - } - - r = copy_bytes(fd, tmp_fd, DATA_FD_TMP_LIMIT - DATA_FD_MEMORY_LIMIT - remains_size, COPY_REFLINK); - if (r < 0) - return r; - if (r == 0) - goto finish; /* Yay, it fit in */ - - /* It didn't fit in. Let's not forget to use what we already used */ - f = lseek(tmp_fd, 0, SEEK_SET); - if (f != 0) - return -errno; - - CLOSE_AND_REPLACE(copy_fd, tmp_fd); - - remains = mfree(remains); - remains_size = 0; - } - - /* As last fallback use /var/tmp */ - r = var_tmp_dir(&td); - if (r < 0) - return r; - - tmp_fd = open_tmpfile_unlinkable(td, O_RDWR|O_CLOEXEC); - if (tmp_fd < 0) - return tmp_fd; - - if (copy_fd >= 0) { - /* If we tried a memfd/pipe first, or a file in /tmp, and it ended up being too large, than copy this - * into the temporary file first. */ - r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, COPY_REFLINK); - if (r < 0) - return r; - - assert(r == 0); - } - - if (remains_size > 0) { - /* Then, copy in any read but not yet written bytes. */ - r = loop_write(tmp_fd, remains, remains_size, false); - if (r < 0) - return r; - } - - /* Copy in the rest */ - r = copy_bytes(fd, tmp_fd, UINT64_MAX, COPY_REFLINK); - if (r < 0) - return r; - - assert(r == 0); - -finish: - /* Now convert the O_RDWR file descriptor into an O_RDONLY one (and as side effect seek to the beginning of the - * file again */ - - return fd_reopen(tmp_fd, O_RDONLY|O_CLOEXEC); -} #endif /* NM_IGNORED */ int fd_move_above_stdio(int fd) { @@ -1016,7 +652,6 @@ finish: } int fd_reopen(int fd, int flags) { - char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; int new_fd; /* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to @@ -1026,8 +661,7 @@ int fd_reopen(int fd, int flags) { * * This implicitly resets the file read index to 0. */ - xsprintf(procfs_path, "/proc/self/fd/%i", fd); - new_fd = open(procfs_path, flags); + new_fd = open(FORMAT_PROC_FD_PATH(fd), flags); if (new_fd < 0) { if (errno != ENOENT) return -errno; @@ -1064,4 +698,21 @@ int read_nr_open(void) { /* If we fail, fall back to the hard-coded kernel limit of 1024 * 1024. */ return 1024 * 1024; } + +/* This is here because it's fd-related and is called from sd-journal code. Other btrfs-related utilities are + * in src/shared, but libsystemd must not link to libsystemd-shared, see docs/ARCHITECTURE.md. */ +int btrfs_defrag_fd(int fd) { + int r; + + assert(fd >= 0); + + r = fd_verify_regular(fd); + if (r < 0) + return r; + + if (ioctl(fd, BTRFS_IOC_DEFRAG, NULL) < 0) + return -errno; + + return 0; +} #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 aa8e082b..2382d52d 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.h +++ b/src/libnm-systemd-shared/src/basic/fd-util.h @@ -7,6 +7,7 @@ #include <sys/socket.h> #include "macro.h" +#include "stdio-util.h" /* maximum length of fdname */ #define FDNAME_MAX 255 @@ -56,7 +57,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL); int fd_nonblock(int fd, bool nonblock); int fd_cloexec(int fd, bool cloexec); -int close_all_fds(const int except[], size_t n_except); +int close_all_fds_full(int except[], size_t n_except, bool allow_alloc); +static inline int close_all_fds(int except[], size_t n_except) { + return close_all_fds_full(except, n_except, true); +} int same_fd(int a, int b); @@ -76,10 +80,6 @@ enum { ACQUIRE_NO_REGULAR = 1 << 4, }; -int acquire_data_fd(const void *data, size_t size, unsigned flags); - -int fd_duplicate_data_fd(int fd); - int fd_move_above_stdio(int fd); int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd); @@ -105,7 +105,20 @@ static inline int make_null_stdio(void) { 0; \ }) - int fd_reopen(int fd, int flags); - int read_nr_open(void); +int btrfs_defrag_fd(int fd); + +/* 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)) + +static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int fd) { + assert(buf); + assert(fd >= 0); + assert_se(snprintf_ok(buf, PROC_FD_PATH_MAX, "/proc/self/fd/%i", fd)); + return buf; +} + +#define FORMAT_PROC_FD_PATH(fd) \ + format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd)) diff --git a/src/libnm-systemd-shared/src/basic/fileio.c b/src/libnm-systemd-shared/src/basic/fileio.c index de78f70e..322638a2 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.c +++ b/src/libnm-systemd-shared/src/basic/fileio.c @@ -15,6 +15,7 @@ #include <unistd.h> #include "alloc-util.h" +#include "chase-symlinks.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -27,10 +28,22 @@ #include "socket-util.h" #include "stdio-util.h" #include "string-util.h" +#include "sync-util.h" #include "tmpfile-util.h" -/* The maximum size of the file we'll read in one go. */ -#define READ_FULL_BYTES_MAX (4U*1024U*1024U - 1) +/* 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) + +/* 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 + * limit. This reflects the fact that we use different strategies for reading virtual and regular files: + * virtual files we generally have to read in a single read() syscall since the kernel doesn't support + * continuation read()s for them. Thankfully they are somewhat size constrained. Thus we can allocate the + * full potential buffer in advance. Regular files OTOH can be much larger, and there we grow the allocations + * exponentially in a loop. We use a size limit of 4M-2 because 4M-1 is the maximum buffer that /proc/sys/ + * allows us to read() (larger reads will fail with ENOMEM), and we want to read one extra byte so that we + * can detect EOFs. */ +#define READ_VIRTUAL_BYTES_MAX (4U*1024U*1024U - 2U) int fopen_unlocked(const char *path, const char *options, FILE **ret) { assert(ret); @@ -140,6 +153,30 @@ int write_string_stream_ts( return -EBADF; } + if (flags & WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) { + _cleanup_free_ char *t = NULL; + + /* If value to be written is same as that of the existing value, then suppress the write. */ + + if (fd < 0) { + fd = fileno(f); + if (fd < 0) + return -EBADF; + } + + /* Read an additional byte to detect cases where the prefix matches but the rest + * doesn't. Also, 0 returned by read_virtual_file_fd() means the read was truncated and + * it won't be equal to the new value. */ + if (read_virtual_file_fd(fd, strlen(line)+1, &t, NULL) > 0 && + streq_skip_trailing_chars(line, t, NEWLINE)) { + log_debug("No change in value '%s', supressing write", line); + return 0; + } + + if (lseek(fd, 0, SEEK_SET) < 0) + return -errno; + } + needs_nl = !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) && !endswith(line, "\n"); if (needs_nl && (flags & WRITE_STRING_FILE_DISABLE_BUFFER)) { @@ -255,10 +292,11 @@ int write_string_file_ts( assert(!ts); /* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */ - fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY | + fd = open(fn, O_CLOEXEC|O_NOCTTY | (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) | (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_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)); if (fd < 0) { r = -errno; @@ -368,9 +406,8 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { } #endif /* NM_IGNORED */ -int read_virtual_file(const char *filename, size_t max_size, char **ret_contents, size_t *ret_size) { +int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *ret_size) { _cleanup_free_ char *buf = NULL; - _cleanup_close_ int fd = -1; size_t n, size; int n_retries; bool truncated = false; @@ -388,11 +425,8 @@ int read_virtual_file(const char *filename, size_t max_size, char **ret_contents * contents* may be returned. (Though the read is still done using one syscall.) Returns 0 on * partial success, 1 if untruncated contents were read. */ - fd = open(filename, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return -errno; - - assert(max_size <= READ_FULL_BYTES_MAX || max_size == SIZE_MAX); + assert(fd >= 0); + assert(max_size <= READ_VIRTUAL_BYTES_MAX || max_size == SIZE_MAX); /* Limit the number of attempts to read the number of bytes returned by fstat(). */ n_retries = 3; @@ -407,7 +441,7 @@ int read_virtual_file(const char *filename, size_t max_size, char **ret_contents return -EBADF; /* Be prepared for files from /proc which generally report a file size of 0. */ - assert_cc(READ_FULL_BYTES_MAX < SSIZE_MAX); + assert_cc(READ_VIRTUAL_BYTES_MAX < SSIZE_MAX); if (st.st_size > 0 && n_retries > 1) { /* Let's use the file size if we have more than 1 attempt left. On the last attempt * we'll ignore the file size */ @@ -421,13 +455,18 @@ int read_virtual_file(const char *filename, size_t max_size, char **ret_contents } else { size = MIN((size_t) st.st_size, max_size); - if (size > READ_FULL_BYTES_MAX) + if (size > READ_VIRTUAL_BYTES_MAX) return -EFBIG; } n_retries--; + } else if (n_retries > 1) { + /* Files in /proc are generally smaller than the page size so let's start with + * a page size buffer from malloc and only use the max buffer on the final try. */ + size = MIN3(page_size() - 1, READ_VIRTUAL_BYTES_MAX, max_size); + n_retries = 1; } else { - size = MIN(READ_FULL_BYTES_MAX, max_size); + size = MIN(READ_VIRTUAL_BYTES_MAX, max_size); n_retries = 0; } @@ -436,7 +475,7 @@ int read_virtual_file(const char *filename, size_t max_size, char **ret_contents return -ENOMEM; /* Use a bigger allocation if we got it anyway, but not more than the limit. */ - size = MIN3(MALLOC_SIZEOF_SAFE(buf) - 1, max_size, READ_FULL_BYTES_MAX); + size = MIN3(MALLOC_SIZEOF_SAFE(buf) - 1, max_size, READ_VIRTUAL_BYTES_MAX); for (;;) { ssize_t k; @@ -458,9 +497,14 @@ int read_virtual_file(const char *filename, size_t max_size, char **ret_contents if (n <= size) break; - /* If a maximum size is specified and we already read as much, no need to try again */ - if (max_size != SIZE_MAX && n >= max_size) { - n = max_size; + /* If a maximum size is specified and we already read more we know the file is larger, and + * can handle this as truncation case. Note that if the size of what we read equals the + * maximum size then this doesn't mean truncation, the file might or might not end on that + * byte. We need to rerun the loop in that case, with a larger buffer size, so that we read + * at least one more byte to be able to distinguish EOF from truncation. */ + if (max_size != SIZE_MAX && n > max_size) { + n = size; /* Make sure we never use more than what we sized the buffer for (so that + * we have one free byte in it for the trailing NUL we add below).*/ truncated = true; break; } @@ -507,6 +551,18 @@ int read_virtual_file(const char *filename, size_t max_size, char **ret_contents return !truncated; } +int read_virtual_file(const char *filename, size_t max_size, char **ret_contents, size_t *ret_size) { + _cleanup_close_ int fd = -1; + + assert(filename); + + fd = open(filename, O_RDONLY | O_NOCTTY | O_CLOEXEC); + if (fd < 0) + return -errno; + + return read_virtual_file_fd(fd, max_size, ret_contents, ret_size); +} + int read_full_stream_full( FILE *f, const char *filename, @@ -517,18 +573,17 @@ int read_full_stream_full( size_t *ret_size) { _cleanup_free_ char *buf = NULL; - size_t n, n_next, l; + size_t n, n_next = 0, l; int fd, r; assert(f); assert(ret_contents); assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)); + assert(size != SIZE_MAX || !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)); - if (offset != UINT64_MAX && offset > LONG_MAX) + if (offset != UINT64_MAX && offset > LONG_MAX) /* fseek() can only deal with "long" offsets */ return -ERANGE; - n_next = size != SIZE_MAX ? size : LINE_MAX; /* Start size */ - 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 */ @@ -538,20 +593,20 @@ int read_full_stream_full( return -errno; if (S_ISREG(st.st_mode)) { - if (size == SIZE_MAX) { + + /* Try to start with the right file size if we shall read the file in full. Note + * that we increase the size to read here by one, so that the first read attempt + * already makes us notice the EOF. If the reported size of the file is zero, we + * avoid this logic however, since quite likely it might be a virtual file in procfs + * that all report a zero file size. */ + + if (st.st_size > 0 && + (size == SIZE_MAX || FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER))) { + uint64_t rsize = LESS_BY((uint64_t) st.st_size, offset == UINT64_MAX ? 0 : offset); - /* Safety check */ - if (rsize > READ_FULL_BYTES_MAX) - return -E2BIG; - - /* Start with the right file size. Note that we increase the size to read - * here by one, so that the first read attempt already makes us notice the - * EOF. If the reported size of the file is zero, we avoid this logic - * however, since quite likely it might be a virtual file in procfs that all - * report a zero file size. */ - if (st.st_size > 0) + if (rsize < SIZE_MAX) /* overflow check */ n_next = rsize + 1; } @@ -560,6 +615,17 @@ int read_full_stream_full( } } + /* If we don't know how much to read, figure it out now. If we shall read a part of the file, then + * allocate the requested size. If we shall load the full file start with LINE_MAX. Note that if + * READ_FULL_FILE_FAIL_WHEN_LARGER we consider the specified size a safety limit, and thus also start + * with LINE_MAX, under assumption the file is most likely much shorter. */ + if (n_next == 0) + n_next = size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) ? size : LINE_MAX; + + /* Never read more than we need to determine that our own limit is hit */ + if (n_next > READ_FULL_BYTES_MAX) + n_next = READ_FULL_BYTES_MAX + 1; + if (offset != UINT64_MAX && fseek(f, offset, SEEK_SET) < 0) return -errno; @@ -568,6 +634,11 @@ int read_full_stream_full( char *t; size_t k; + /* If we shall fail when reading overly large data, then read exactly one byte more than the + * specified size at max, since that'll tell us if there's anymore data beyond the limit*/ + if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && n_next > size) + n_next = size + 1; + if (flags & READ_FULL_FILE_SECURE) { t = malloc(n_next + 1); if (!t) { @@ -576,7 +647,7 @@ int read_full_stream_full( } memcpy_safe(t, buf, n); explicit_bzero_safe(buf, n); - buf = mfree(buf); + free(buf); } else { t = realloc(buf, n_next + 1); if (!t) @@ -601,14 +672,18 @@ int read_full_stream_full( if (feof(f)) break; - if (size != SIZE_MAX) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */ + if (size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */ assert(l == size); break; } assert(k > 0); /* we can't have read zero bytes because that would have been EOF */ - /* Safety check */ + if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && l > size) { + r = -E2BIG; + goto finalize; + } + if (n >= READ_FULL_BYTES_MAX) { r = -E2BIG; goto finalize; @@ -697,8 +772,6 @@ int read_full_file_full( if (dir_fd == AT_FDCWD) r = sockaddr_un_set_path(&sa.un, filename); else { - char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; - /* If we shall operate relative to some directory, then let's use O_PATH first to * open the socket inode, and then connect to it via /proc/self/fd/. We have to do * this since there's not connectat() that takes a directory fd as first arg. */ @@ -707,8 +780,7 @@ int read_full_file_full( if (dfd < 0) return -errno; - xsprintf(procfs_path, "/proc/self/fd/%i", dfd); - r = sockaddr_un_set_path(&sa.un, procfs_path); + r = sockaddr_un_set_path(&sa.un, FORMAT_PROC_FD_PATH(dfd)); } if (r < 0) return r; @@ -875,10 +947,12 @@ DIR *xopendirat(int fd, const char *name, int flags) { return d; } -static int mode_to_flags(const char *mode) { +int fopen_mode_to_flags(const char *mode) { const char *p; int flags; + assert(mode); + if ((p = startswith(mode, "r+"))) flags = O_RDWR; else if ((p = startswith(mode, "r"))) @@ -931,7 +1005,7 @@ int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **r } else { int fd, mode_flags; - mode_flags = mode_to_flags(mode); + mode_flags = fopen_mode_to_flags(mode); if (mode_flags < 0) return mode_flags; @@ -1071,39 +1145,6 @@ int search_and_fopen_nulstr( return search_and_fopen_internal(filename, mode, root, s, ret, ret_path); } - -int chase_symlinks_and_fopen_unlocked( - const char *path, - const char *root, - unsigned chase_flags, - const char *open_flags, - FILE **ret_file, - char **ret_path) { - - _cleanup_close_ int fd = -1; - _cleanup_free_ char *final_path = NULL; - int mode_flags, r; - - assert(path); - assert(open_flags); - assert(ret_file); - - mode_flags = mode_to_flags(open_flags); - if (mode_flags < 0) - return mode_flags; - - fd = chase_symlinks_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL); - if (fd < 0) - return fd; - - r = take_fdopen_unlocked(&fd, open_flags, ret_file); - if (r < 0) - return r; - - if (ret_path) - *ret_path = TAKE_PTR(final_path); - return 0; -} #endif /* NM_IGNORED */ int fflush_and_check(FILE *f) { @@ -1134,10 +1175,7 @@ int fflush_sync_and_check(FILE *f) { if (fd < 0) return 0; - if (fsync(fd) < 0) - return -errno; - - r = fsync_directory_of_file(fd); + r = fsync_full(fd); if (r < 0) return r; @@ -1409,18 +1447,3 @@ int warn_file_is_world_accessible(const char *filename, struct stat *st, const c filename, st->st_mode & 07777); return 0; } - -#if 0 /* NM_IGNORED */ -int rename_and_apply_smack_floor_label(const char *from, const char *to) { - int r = 0; - if (rename(from, to) < 0) - return -errno; - -#if HAVE_SMACK_RUN_LABEL - r = mac_smack_apply(to, SMACK_ATTR_ACCESS, SMACK_FLOOR_LABEL); - if (r < 0) - return r; -#endif - return r; -} -#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/fileio.h b/src/libnm-systemd-shared/src/basic/fileio.h index 1a8b7dec..899def94 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.h +++ b/src/libnm-systemd-shared/src/basic/fileio.h @@ -2,15 +2,11 @@ #pragma once #include <dirent.h> +#include <fcntl.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> #include <sys/stat.h> -#if 0 /* NM_IGNORED */ -#include <sys/fcntl.h> -#else /* NM_IGNORED */ -#include <fcntl.h> -#endif /* NM_IGNORED */ #include <sys/types.h> #include "macro.h" @@ -19,17 +15,18 @@ #define LONG_LINE_MAX (1U*1024U*1024U) typedef enum { - WRITE_STRING_FILE_CREATE = 1 << 0, - WRITE_STRING_FILE_TRUNCATE = 1 << 1, - WRITE_STRING_FILE_ATOMIC = 1 << 2, - WRITE_STRING_FILE_AVOID_NEWLINE = 1 << 3, - WRITE_STRING_FILE_VERIFY_ON_FAILURE = 1 << 4, - WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE = 1 << 5, - WRITE_STRING_FILE_SYNC = 1 << 6, - WRITE_STRING_FILE_DISABLE_BUFFER = 1 << 7, - WRITE_STRING_FILE_NOFOLLOW = 1 << 8, - WRITE_STRING_FILE_MKDIR_0755 = 1 << 9, - WRITE_STRING_FILE_MODE_0600 = 1 << 10, + WRITE_STRING_FILE_CREATE = 1 << 0, + WRITE_STRING_FILE_TRUNCATE = 1 << 1, + WRITE_STRING_FILE_ATOMIC = 1 << 2, + WRITE_STRING_FILE_AVOID_NEWLINE = 1 << 3, + WRITE_STRING_FILE_VERIFY_ON_FAILURE = 1 << 4, + WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE = 1 << 5, + WRITE_STRING_FILE_SYNC = 1 << 6, + WRITE_STRING_FILE_DISABLE_BUFFER = 1 << 7, + 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, /* 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() @@ -43,6 +40,7 @@ typedef enum { READ_FULL_FILE_UNHEX = 1 << 2, /* hex decode what we read */ READ_FULL_FILE_WARN_WORLD_READABLE = 1 << 3, /* if regular file, log at LOG_WARNING level if access mode above 0700 */ READ_FULL_FILE_CONNECT_SOCKET = 1 << 4, /* if socket inode, connect to it and read off it */ + READ_FULL_FILE_FAIL_WHEN_LARGER = 1 << 5, /* fail loading if file is larger than specified size */ } ReadFullFileFlags; int fopen_unlocked(const char *path, const char *options, FILE **ret); @@ -70,6 +68,7 @@ static inline int read_full_file(const char *filename, char **ret_contents, size return read_full_file_full(AT_FDCWD, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size); } +int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *ret_size); int read_virtual_file(const char *filename, size_t max_size, char **ret_contents, size_t *ret_size); static inline int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size) { return read_virtual_file(filename, SIZE_MAX, ret_contents, ret_size); @@ -92,14 +91,6 @@ int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **r 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 chase_symlinks_and_fopen_unlocked( - const char *path, - const char *root, - unsigned chase_flags, - const char *open_flags, - FILE **ret_file, - char **ret_path); - int fflush_and_check(FILE *f); int fflush_sync_and_check(FILE *f); @@ -128,4 +119,4 @@ int safe_fgetc(FILE *f, char *ret); int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line); -int rename_and_apply_smack_floor_label(const char *temp_path, const char *dest_path); +int fopen_mode_to_flags(const char *mode); diff --git a/src/libnm-systemd-shared/src/basic/format-util.c b/src/libnm-systemd-shared/src/basic/format-util.c index 398a1815..ebe52713 100644 --- a/src/libnm-systemd-shared/src/basic/format-util.c +++ b/src/libnm-systemd-shared/src/basic/format-util.c @@ -5,23 +5,43 @@ #include "format-util.h" #include "memory-util.h" #include "stdio-util.h" +#include "strxcpyx.h" + +assert_cc(STRLEN("%") + DECIMAL_STR_MAX(int) <= IF_NAMESIZE); +int format_ifname_full(int ifindex, FormatIfnameFlag flag, char buf[static IF_NAMESIZE]) { + if (ifindex <= 0) + return -EINVAL; -assert_cc(DECIMAL_STR_MAX(int) + 1 <= IF_NAMESIZE + 1); -char *format_ifname_full(int ifindex, char buf[static IF_NAMESIZE + 1], FormatIfnameFlag flag) { - /* Buffer is always cleared */ - memzero(buf, IF_NAMESIZE + 1); if (if_indextoname(ifindex, buf)) - return buf; + return 0; if (!FLAGS_SET(flag, FORMAT_IFNAME_IFINDEX)) - return NULL; + return -errno; if (FLAGS_SET(flag, FORMAT_IFNAME_IFINDEX_WITH_PERCENT)) - snprintf(buf, IF_NAMESIZE + 1, "%%%d", ifindex); + assert(snprintf_ok(buf, IF_NAMESIZE, "%%%d", ifindex)); else - snprintf(buf, IF_NAMESIZE + 1, "%d", ifindex); + assert(snprintf_ok(buf, IF_NAMESIZE, "%d", ifindex)); - return buf; + return 0; +} + +int format_ifname_full_alloc(int ifindex, FormatIfnameFlag flag, char **ret) { + char buf[IF_NAMESIZE], *copy; + int r; + + assert(ret); + + r = format_ifname_full(ifindex, flag, buf); + if (r < 0) + return r; + + copy = strdup(buf); + if (!copy) + return -ENOMEM; + + *ret = copy; + return 0; } char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) { @@ -58,23 +78,23 @@ char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) { for (size_t i = 0; i < n; i++) if (t >= table[i].factor) { if (flag & FORMAT_BYTES_BELOW_POINT) { - snprintf(buf, l, - "%" PRIu64 ".%" PRIu64 "%s", - t / table[i].factor, - i != n - 1 ? - (t / table[i + 1].factor * UINT64_C(10) / table[n - 1].factor) % UINT64_C(10): - (t * UINT64_C(10) / table[i].factor) % UINT64_C(10), - table[i].suffix); + (void) snprintf(buf, l, + "%" PRIu64 ".%" PRIu64 "%s", + t / table[i].factor, + i != n - 1 ? + (t / table[i + 1].factor * UINT64_C(10) / table[n - 1].factor) % UINT64_C(10): + (t * UINT64_C(10) / table[i].factor) % UINT64_C(10), + table[i].suffix); } else - snprintf(buf, l, - "%" PRIu64 "%s", - t / table[i].factor, - table[i].suffix); + (void) snprintf(buf, l, + "%" PRIu64 "%s", + t / table[i].factor, + table[i].suffix); goto finish; } - snprintf(buf, l, "%" PRIu64 "%s", t, flag & FORMAT_BYTES_TRAILING_B ? "B" : ""); + (void) snprintf(buf, l, "%" PRIu64 "%s", t, flag & FORMAT_BYTES_TRAILING_B ? "B" : ""); finish: buf[l-1] = 0; diff --git a/src/libnm-systemd-shared/src/basic/format-util.h b/src/libnm-systemd-shared/src/basic/format-util.h index b7e18768..7dd422b9 100644 --- a/src/libnm-systemd-shared/src/basic/format-util.h +++ b/src/libnm-systemd-shared/src/basic/format-util.h @@ -61,11 +61,24 @@ typedef enum { FORMAT_IFNAME_IFINDEX_WITH_PERCENT = (1 << 1) | FORMAT_IFNAME_IFINDEX, } FormatIfnameFlag; -char *format_ifname_full(int ifindex, char buf[static IF_NAMESIZE + 1], FormatIfnameFlag flag); -static inline char *format_ifname(int ifindex, char buf[static IF_NAMESIZE + 1]) { - return format_ifname_full(ifindex, buf, 0); +int format_ifname_full(int ifindex, FormatIfnameFlag flag, char buf[static IF_NAMESIZE]); +int format_ifname_full_alloc(int ifindex, FormatIfnameFlag flag, char **ret); + +static inline int format_ifname(int ifindex, char buf[static IF_NAMESIZE]) { + return format_ifname_full(ifindex, 0, buf); +} +static inline int format_ifname_alloc(int ifindex, char **ret) { + return format_ifname_full_alloc(ifindex, 0, ret); +} + +static inline char *_format_ifname_full(int ifindex, FormatIfnameFlag flag, char buf[static IF_NAMESIZE]) { + (void) format_ifname_full(ifindex, flag, buf); + return buf; } +#define FORMAT_IFNAME_FULL(index, flag) _format_ifname_full(index, flag, (char[IF_NAMESIZE]){}) +#define FORMAT_IFNAME(index) _format_ifname_full(index, 0, (char[IF_NAMESIZE]){}) + typedef enum { FORMAT_BYTES_USE_IEC = 1 << 0, FORMAT_BYTES_BELOW_POINT = 1 << 1, @@ -74,16 +87,17 @@ typedef enum { #define FORMAT_BYTES_MAX 16U -char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag); +char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) _warn_unused_result_; +_warn_unused_result_ static inline char *format_bytes(char *buf, size_t l, uint64_t t) { return format_bytes_full(buf, l, t, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | FORMAT_BYTES_TRAILING_B); } -static inline char *format_bytes_cgroup_protection(char *buf, size_t l, uint64_t t) { - if (t == CGROUP_LIMIT_MAX) { - (void) snprintf(buf, l, "%s", "infinity"); - return buf; - } - return format_bytes(buf, l, t); -} +/* Note: the lifetime of the compound literal is the immediately surrounding block, + * see C11 ยง6.5.2.5, and + * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */ +#define FORMAT_BYTES(t) format_bytes((char[FORMAT_BYTES_MAX]){}, FORMAT_BYTES_MAX, t) +#define FORMAT_BYTES_FULL(t, flag) format_bytes_full((char[FORMAT_BYTES_MAX]){}, FORMAT_BYTES_MAX, t, flag) + +#define FORMAT_BYTES_CGROUP_PROTECTION(t) (t == CGROUP_LIMIT_MAX ? "infinity" : FORMAT_BYTES(t)) diff --git a/src/libnm-systemd-shared/src/basic/fs-util.c b/src/libnm-systemd-shared/src/basic/fs-util.c index ee8eec90..3f309868 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.c +++ b/src/libnm-systemd-shared/src/basic/fs-util.c @@ -10,12 +10,10 @@ #include <unistd.h> #include "alloc-util.h" -#include "blockdev-util.h" #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" -#include "locale-util.h" #include "log.h" #include "macro.h" #include "missing_fcntl.h" @@ -49,50 +47,39 @@ int unlink_noerrno(const char *path) { #if 0 /* NM_IGNORED */ int rmdir_parents(const char *path, const char *stop) { - size_t l; - int r = 0; + char *p; + int r; assert(path); assert(stop); - l = strlen(path); - - /* Skip trailing slashes */ - while (l > 0 && path[l-1] == '/') - l--; + if (!path_is_safe(path)) + return -EINVAL; - while (l > 0) { - char *t; + if (!path_is_safe(stop)) + return -EINVAL; - /* Skip last component */ - while (l > 0 && path[l-1] != '/') - l--; + p = strdupa(path); - /* Skip trailing slashes */ - while (l > 0 && path[l-1] == '/') - l--; + for (;;) { + char *slash = NULL; - if (l <= 0) - break; + /* skip the last component. */ + r = path_find_last_component(p, /* accept_dot_dot= */ false, (const char **) &slash, NULL); + if (r <= 0) + return r; + if (slash == p) + return 0; - t = strndup(path, l); - if (!t) - return -ENOMEM; + assert(*slash == '/'); + *slash = '\0'; - if (path_startswith(stop, t)) { - free(t); + if (path_startswith_full(stop, p, /* accept_dot_dot= */ false)) return 0; - } - - r = rmdir(t); - free(t); - if (r < 0) - if (errno != ENOENT) - return -errno; + if (rmdir(p) < 0 && errno != ENOENT) + return -errno; } - - return 0; } int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { @@ -314,14 +301,11 @@ int fchmod_umask(int fd, mode_t m) { } int fchmod_opath(int fd, mode_t m) { - char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; - /* 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. */ - xsprintf(procfs_path, "/proc/self/fd/%i", fd); - if (chmod(procfs_path, m) < 0) { + if (chmod(FORMAT_PROC_FD_PATH(fd), m) < 0) { if (errno != ENOENT) return -errno; @@ -335,12 +319,9 @@ int fchmod_opath(int fd, mode_t m) { } int futimens_opath(int fd, const struct timespec ts[2]) { - char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; - /* Similar to fchmod_path() but for futimens() */ - xsprintf(procfs_path, "/proc/self/fd/%i", fd); - if (utimensat(AT_FDCWD, procfs_path, ts, 0) < 0) { + if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, 0) < 0) { if (errno != ENOENT) return -errno; @@ -386,9 +367,8 @@ int fd_warn_permissions(const char *path, int fd) { } int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) { - char fdpath[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; _cleanup_close_ int fd = -1; - int r, ret = 0; + int r, ret; assert(path); @@ -418,8 +398,6 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi /* Let's make a path from the fd, and operate on that. With this logic, we can adjust the access mode, * ownership and time of the file node in all cases, even if the fd refers to an O_PATH object โ which is * something fchown(), fchmod(), futimensat() don't allow. */ - xsprintf(fdpath, "/proc/self/fd/%i", fd); - ret = fchmod_and_chown(fd, mode, uid, gid); if (stamp != USEC_INFINITY) { @@ -427,11 +405,11 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi timespec_store(&ts[0], stamp); ts[1] = ts[0]; - r = utimensat(AT_FDCWD, fdpath, ts, 0); + r = futimens_opath(fd, ts); } else - r = utimensat(AT_FDCWD, fdpath, NULL, 0); + r = futimens_opath(fd, NULL); if (r < 0 && ret >= 0) - return -errno; + return r; return ret; } @@ -586,8 +564,6 @@ int get_files_in_directory(const char *path, char ***list) { return -errno; FOREACH_DIRENT_ALL(de, d, return -errno) { - dirent_ensure_type(d, de); - if (!dirent_is_file(de)) continue; @@ -713,544 +689,11 @@ int unlink_or_warn(const char *filename) { return 0; } -#endif /* NM_IGNORED */ - -int inotify_add_watch_fd(int fd, int what, uint32_t mask) { - char path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; - int wd; - - /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */ - xsprintf(path, "/proc/self/fd/%i", what); - - wd = inotify_add_watch(fd, path, mask); - if (wd < 0) - return -errno; - - return wd; -} - -#if 0 /* NM_IGNORED */ -int inotify_add_watch_and_warn(int fd, const char *pathname, uint32_t mask) { - int wd; - - wd = inotify_add_watch(fd, pathname, mask); - if (wd < 0) { - if (errno == ENOSPC) - return log_error_errno(errno, "Failed to add a watch for %s: inotify watch limit reached", pathname); - - return log_error_errno(errno, "Failed to add a watch for %s: %m", pathname); - } - - return wd; -} - -static bool unsafe_transition(const struct stat *a, const struct stat *b) { - /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to - * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files - * making us believe we read something safe even though it isn't safe in the specific context we open it in. */ - - if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */ - return false; - - return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */ -} - -static int log_unsafe_transition(int a, int b, const char *path, unsigned flags) { - _cleanup_free_ char *n1 = NULL, *n2 = NULL; - - if (!FLAGS_SET(flags, CHASE_WARN)) - return -ENOLINK; - - (void) fd_get_path(a, &n1); - (void) fd_get_path(b, &n2); - - return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK), - "Detected unsafe path transition %s %s %s during canonicalization of %s.", - strna(n1), special_glyph(SPECIAL_GLYPH_ARROW), strna(n2), path); -} - -static int log_autofs_mount_point(int fd, const char *path, unsigned flags) { - _cleanup_free_ char *n1 = NULL; - - if (!FLAGS_SET(flags, CHASE_WARN)) - return -EREMOTE; - - (void) fd_get_path(fd, &n1); - - return log_warning_errno(SYNTHETIC_ERRNO(EREMOTE), - "Detected autofs mount point %s during canonicalization of %s.", - strna(n1), path); -} - -int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret_path, int *ret_fd) { - _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL; - _cleanup_close_ int fd = -1; - unsigned max_follow = CHASE_SYMLINKS_MAX; /* how many symlinks to follow before giving up and returning ELOOP */ - bool exists = true, append_trail_slash = false; - struct stat previous_stat; - const char *todo; - int r; - - assert(path); - - /* Either the file may be missing, or we return an fd to the final object, but both make no sense */ - if ((flags & CHASE_NONEXISTENT) && ret_fd) - return -EINVAL; - - if ((flags & CHASE_STEP) && ret_fd) - return -EINVAL; - - if (isempty(path)) - return -EINVAL; - - /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following - * symlinks relative to a root directory, instead of the root of the host. - * - * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following - * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is - * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first - * prefixed accordingly. - * - * Algorithmically this operates on two path buffers: "done" are the components of the path we already - * processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to - * process. On each iteration, we move one component from "todo" to "done", processing it's special meaning - * each time. The "todo" path always starts with at least one slash, the "done" path always ends in no - * slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races - * to a minimum. - * - * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got - * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this - * function what to do when encountering a symlink with an absolute path as directory: prefix it by the - * specified path. - * - * There are five ways to invoke this function: - * - * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is - * returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0 - * is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is - * returned if the destination was found, -ENOENT if it wasn't. - * - * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file - * descriptor is returned as return value. This is useful to open files relative to some root - * directory. Note that the returned O_PATH file descriptors must be converted into a regular one (using - * fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be combined with - * CHASE_NONEXISTENT. - * - * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only the first - * symlink or ".." component of the path is resolved, and the resulting path is returned. This is useful if - * a caller wants to trace the path through the file system verbosely. Returns < 0 on error, > 0 if the - * path is fully normalized, and == 0 for each normalization step. This may be combined with - * CHASE_NONEXISTENT, in which case 1 is returned when a component is not found. - * - * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions from - * unprivileged to privileged files or directories. In such cases the return value is -ENOLINK. If - * CHASE_WARN is also set, a warning describing the unsafe transition is emitted. - * - * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization - * is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of - * the mount point is emitted. - */ - - /* A root directory of "/" or "" is identical to none */ - if (empty_or_root(original_root)) - original_root = NULL; - - if (!original_root && !ret_path && !(flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP)) && ret_fd) { - /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root set - * and doesn't care about any of the other special features we provide either. */ - r = open(path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0)); - if (r < 0) - return -errno; - - *ret_fd = r; - return 0; - } - - if (original_root) { - r = path_make_absolute_cwd(original_root, &root); - if (r < 0) - return r; - - /* Simplify the root directory, so that it has no duplicate slashes and nothing at the - * end. While we won't resolve the root path we still simplify it. Note that dropping the - * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY - * anyway. Moreover at the end of this function after processing everything we'll always turn - * the empty string back to "/". */ - delete_trailing_chars(root, "/"); - path_simplify(root); - - if (flags & CHASE_PREFIX_ROOT) { - /* We don't support relative paths in combination with a root directory */ - if (!path_is_absolute(path)) - return -EINVAL; - - path = prefix_roota(root, path); - } - } - - r = path_make_absolute_cwd(path, &buffer); - if (r < 0) - return r; - - fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH); - if (fd < 0) - return -errno; - - if (flags & CHASE_SAFE) - if (fstat(fd, &previous_stat) < 0) - return -errno; - - if (flags & CHASE_TRAIL_SLASH) - append_trail_slash = endswith(buffer, "/") || endswith(buffer, "/."); - - if (root) { - /* If we are operating on a root directory, let's take the root directory as it is. */ - - todo = path_startswith(buffer, root); - if (!todo) - return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG, - SYNTHETIC_ERRNO(ECHRNG), - "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.", - path, root); - - done = strdup(root); - } else { - todo = buffer; - done = strdup("/"); - } - - for (;;) { - _cleanup_free_ char *first = NULL; - _cleanup_close_ int child = -1; - struct stat st; - const char *e; - - r = path_find_first_component(&todo, true, &e); - if (r < 0) - return r; - if (r == 0) { /* We reached the end. */ - if (append_trail_slash) - if (!strextend(&done, "/")) - return -ENOMEM; - break; - } - - first = strndup(e, r); - if (!first) - return -ENOMEM; - - /* Two dots? Then chop off the last bit of what we already found out. */ - if (path_equal(first, "..")) { - _cleanup_free_ char *parent = NULL; - _cleanup_close_ int fd_parent = -1; - - /* If we already are at the top, then going up will not change anything. This is in-line with - * how the kernel handles this. */ - if (empty_or_root(done)) - continue; - - parent = dirname_malloc(done); - if (!parent) - return -ENOMEM; - - /* Don't allow this to leave the root dir. */ - if (root && - path_startswith(done, root) && - !path_startswith(parent, root)) - continue; - - free_and_replace(done, parent); - - if (flags & CHASE_STEP) - goto chased_one; - - fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH); - if (fd_parent < 0) - return -errno; - - if (flags & CHASE_SAFE) { - if (fstat(fd_parent, &st) < 0) - return -errno; - - if (unsafe_transition(&previous_stat, &st)) - return log_unsafe_transition(fd, fd_parent, path, flags); - - previous_stat = st; - } - - safe_close(fd); - fd = TAKE_FD(fd_parent); - - continue; - } - - /* Otherwise let's see what this is. */ - child = openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH); - if (child < 0) { - if (errno == ENOENT && - (flags & CHASE_NONEXISTENT) && - (isempty(todo) || path_is_safe(todo))) { - /* If CHASE_NONEXISTENT is set, and the path does not exist, then - * that's OK, return what we got so far. But don't allow this if the - * remaining path contains "../" or something else weird. */ - - if (!path_extend(&done, first, todo)) - return -ENOMEM; - - exists = false; - break; - } - - return -errno; - } - - if (fstat(child, &st) < 0) - return -errno; - if ((flags & CHASE_SAFE) && - unsafe_transition(&previous_stat, &st)) - return log_unsafe_transition(fd, child, path, flags); - - previous_stat = st; - - if ((flags & CHASE_NO_AUTOFS) && - fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0) - return log_autofs_mount_point(child, path, flags); - - if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) { - _cleanup_free_ char *destination = NULL; - - /* This is a symlink, in this case read the destination. But let's make sure we - * don't follow symlinks without bounds. */ - if (--max_follow <= 0) - return -ELOOP; - - r = readlinkat_malloc(fd, first, &destination); - if (r < 0) - return r; - if (isempty(destination)) - return -EINVAL; - - if (path_is_absolute(destination)) { - - /* An absolute destination. Start the loop from the beginning, but use the root - * directory as base. */ - - safe_close(fd); - fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH); - if (fd < 0) - return -errno; - - if (flags & CHASE_SAFE) { - if (fstat(fd, &st) < 0) - return -errno; - - if (unsafe_transition(&previous_stat, &st)) - return log_unsafe_transition(child, fd, path, flags); - - previous_stat = st; - } - - /* Note that we do not revalidate the root, we take it as is. */ - r = free_and_strdup(&done, empty_to_root(root)); - if (r < 0) - return r; - } - - /* Prefix what's left to do with what we just read, and start the loop again, but - * remain in the current directory. */ - if (!path_extend(&destination, todo)) - return -ENOMEM; - - free_and_replace(buffer, destination); - todo = buffer; - - if (flags & CHASE_STEP) - goto chased_one; - - continue; - } - - /* If this is not a symlink, then let's just add the name we read to what we already verified. */ - if (!path_extend(&done, first)) - return -ENOMEM; - - /* And iterate again, but go one directory further down. */ - safe_close(fd); - fd = TAKE_FD(child); - } - - if (ret_path) - *ret_path = TAKE_PTR(done); - - if (ret_fd) { - /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a - * proper fd by opening /proc/self/fd/xyz. */ - - assert(fd >= 0); - *ret_fd = TAKE_FD(fd); - } - - if (flags & CHASE_STEP) - return 1; - - return exists; - -chased_one: - if (ret_path) { - const char *e; - - /* todo may contain slashes at the beginning. */ - r = path_find_first_component(&todo, true, &e); - if (r < 0) - return r; - if (r == 0) - *ret_path = TAKE_PTR(done); - else { - char *c; - - c = path_join(done, e); - if (!c) - return -ENOMEM; - - *ret_path = c; - } - } - - return 0; -} - -int chase_symlinks_and_open( - const char *path, - const char *root, - unsigned chase_flags, - int open_flags, - char **ret_path) { - - _cleanup_close_ int path_fd = -1; - _cleanup_free_ char *p = NULL; - int r; - - if (chase_flags & CHASE_NONEXISTENT) - return -EINVAL; - - if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { - /* Shortcut this call if none of the special features of this call are requested */ - r = open(path, open_flags); - if (r < 0) - return -errno; - - return r; - } - - r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); - if (r < 0) - return r; - assert(path_fd >= 0); - - r = fd_reopen(path_fd, open_flags); - if (r < 0) - return r; - - if (ret_path) - *ret_path = TAKE_PTR(p); - - return r; -} - -int chase_symlinks_and_opendir( - const char *path, - const char *root, - unsigned chase_flags, - char **ret_path, - DIR **ret_dir) { - - char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; - _cleanup_close_ int path_fd = -1; - _cleanup_free_ char *p = NULL; - DIR *d; - int r; - - if (!ret_dir) - return -EINVAL; - if (chase_flags & CHASE_NONEXISTENT) - return -EINVAL; - - if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { - /* Shortcut this call if none of the special features of this call are requested */ - d = opendir(path); - if (!d) - return -errno; - - *ret_dir = d; - return 0; - } - - r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); - if (r < 0) - return r; - assert(path_fd >= 0); - - xsprintf(procfs_path, "/proc/self/fd/%i", path_fd); - d = opendir(procfs_path); - if (!d) - return -errno; - - if (ret_path) - *ret_path = TAKE_PTR(p); - - *ret_dir = d; - return 0; -} - -int chase_symlinks_and_stat( - const char *path, - const char *root, - unsigned chase_flags, - char **ret_path, - struct stat *ret_stat, - int *ret_fd) { - - _cleanup_close_ int path_fd = -1; - _cleanup_free_ char *p = NULL; - int r; - - assert(path); - assert(ret_stat); - - if (chase_flags & CHASE_NONEXISTENT) - return -EINVAL; - - if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { - /* Shortcut this call if none of the special features of this call are requested */ - if (stat(path, ret_stat) < 0) - return -errno; - - return 1; - } - - r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); - if (r < 0) - return r; - assert(path_fd >= 0); - - if (fstat(path_fd, ret_stat) < 0) - return -errno; - - if (ret_path) - *ret_path = TAKE_PTR(p); - if (ret_fd) - *ret_fd = TAKE_FD(path_fd); - - return 1; -} int access_fd(int fd, int mode) { - char p[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1]; - /* Like access() but operates on an already open fd */ - xsprintf(p, "/proc/self/fd/%i", fd); - if (access(p, mode) < 0) { + if (access(FORMAT_PROC_FD_PATH(fd), mode) < 0) { if (errno != ENOENT) return -errno; @@ -1385,115 +828,6 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) { return 0; } -#if 0 /* NM_IGNORED */ -int fsync_directory_of_file(int fd) { - _cleanup_free_ char *path = NULL; - _cleanup_close_ int dfd = -1; - struct stat st; - int r; - - assert(fd >= 0); - - /* We only reasonably can do this for regular files and directories, hence check for that */ - if (fstat(fd, &st) < 0) - return -errno; - - if (S_ISREG(st.st_mode)) { - - r = fd_get_path(fd, &path); - if (r < 0) { - log_debug_errno(r, "Failed to query /proc/self/fd/%d%s: %m", - fd, - r == -ENOSYS ? ", ignoring" : ""); - - if (r == -ENOSYS) - /* If /proc is not available, we're most likely running in some - * chroot environment, and syncing the directory is not very - * important in that case. Let's just silently do nothing. */ - return 0; - - return r; - } - - if (!path_is_absolute(path)) - return -EINVAL; - - dfd = open_parent(path, O_CLOEXEC|O_NOFOLLOW, 0); - if (dfd < 0) - return dfd; - - } else if (S_ISDIR(st.st_mode)) { - dfd = openat(fd, "..", O_RDONLY|O_DIRECTORY|O_CLOEXEC, 0); - if (dfd < 0) - return -errno; - } else - return -ENOTTY; - - if (fsync(dfd) < 0) - return -errno; - - return 0; -} -#endif /* NM_IGNORED */ - -int fsync_full(int fd) { - int r, q; - - /* Sync both the file and the directory */ - - r = fsync(fd) < 0 ? -errno : 0; - - q = fsync_directory_of_file(fd); - if (r < 0) /* Return earlier error */ - return r; - if (q == -ENOTTY) /* Ignore if the 'fd' refers to a block device or so which doesn't really have a - * parent dir */ - return 0; - return q; -} - -int fsync_path_at(int at_fd, const char *path) { - _cleanup_close_ int opened_fd = -1; - int fd; - - if (isempty(path)) { - if (at_fd == AT_FDCWD) { - opened_fd = open(".", O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (opened_fd < 0) - return -errno; - - fd = opened_fd; - } else - fd = at_fd; - } else { - opened_fd = openat(at_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK); - if (opened_fd < 0) - return -errno; - - fd = opened_fd; - } - - if (fsync(fd) < 0) - return -errno; - - return 0; -} - -int syncfs_path(int atfd, const char *path) { - _cleanup_close_ int fd = -1; - - assert(path); - - fd = openat(atfd, path, O_CLOEXEC|O_RDONLY|O_NONBLOCK); - if (fd < 0) - return -errno; - - if (syncfs(fd) < 0) - return -errno; - - return 0; -} - int open_parent(const char *path, int flags, mode_t mode) { _cleanup_free_ char *parent = NULL; int fd, r; @@ -1516,91 +850,6 @@ int open_parent(const char *path, int flags, mode_t mode) { return fd; } - -static int blockdev_is_encrypted(const char *sysfs_path, unsigned depth_left) { - _cleanup_free_ char *p = NULL, *uuids = NULL; - _cleanup_closedir_ DIR *d = NULL; - int r, found_encrypted = false; - - assert(sysfs_path); - - if (depth_left == 0) - return -EINVAL; - - p = path_join(sysfs_path, "dm/uuid"); - if (!p) - return -ENOMEM; - - r = read_one_line_file(p, &uuids); - if (r != -ENOENT) { - if (r < 0) - return r; - - /* The DM device's uuid attribute is prefixed with "CRYPT-" if this is a dm-crypt device. */ - if (startswith(uuids, "CRYPT-")) - return true; - } - - /* Not a dm-crypt device itself. But maybe it is on top of one? Follow the links in the "slaves/" - * subdir. */ - - p = mfree(p); - p = path_join(sysfs_path, "slaves"); - if (!p) - return -ENOMEM; - - d = opendir(p); - if (!d) { - if (errno == ENOENT) /* Doesn't have underlying devices */ - return false; - - return -errno; - } - - for (;;) { - _cleanup_free_ char *q = NULL; - struct dirent *de; - - errno = 0; - de = readdir_no_dot(d); - if (!de) { - if (errno != 0) - return -errno; - - break; /* No more underlying devices */ - } - - q = path_join(p, de->d_name); - if (!q) - return -ENOMEM; - - r = blockdev_is_encrypted(q, depth_left - 1); - if (r < 0) - return r; - if (r == 0) /* we found one that is not encrypted? then propagate that immediately */ - return false; - - found_encrypted = true; - } - - return found_encrypted; -} - -int path_is_encrypted(const char *path) { - char p[SYS_BLOCK_PATH_MAX(NULL)]; - dev_t devt; - int r; - - r = get_block_device(path, &devt); - if (r < 0) - return r; - if (r == 0) /* doesn't have a block device */ - return false; - - xsprintf_sys_block_path(p, NULL, devt); - - return blockdev_is_encrypted(p, 10 /* safety net: maximum recursion depth */); -} #endif /* NM_IGNORED */ int conservative_renameat( diff --git a/src/libnm-systemd-shared/src/basic/fs-util.h b/src/libnm-systemd-shared/src/basic/fs-util.h index 7bac2570..f8a7657a 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.h +++ b/src/libnm-systemd-shared/src/basic/fs-util.h @@ -6,7 +6,6 @@ #include <limits.h> #include <stdbool.h> #include <stdint.h> -#include <sys/inotify.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> @@ -67,42 +66,6 @@ int var_tmp_dir(const char **ret); int unlink_or_warn(const char *filename); -#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1) - -#define FOREACH_INOTIFY_EVENT(e, buffer, sz) \ - for ((e) = &buffer.ev; \ - (uint8_t*) (e) < (uint8_t*) (buffer.raw) + (sz); \ - (e) = (struct inotify_event*) ((uint8_t*) (e) + sizeof(struct inotify_event) + (e)->len)) - -union inotify_event_buffer { - struct inotify_event ev; - uint8_t raw[INOTIFY_EVENT_MAX]; -}; - -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); - -enum { - CHASE_PREFIX_ROOT = 1 << 0, /* The specified path will be prefixed by the specified root before beginning the iteration */ - CHASE_NONEXISTENT = 1 << 1, /* It's OK if the path doesn't actually exist. */ - CHASE_NO_AUTOFS = 1 << 2, /* Return -EREMOTE if autofs mount point found */ - CHASE_SAFE = 1 << 3, /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */ - CHASE_TRAIL_SLASH = 1 << 4, /* Any trailing slash will be preserved */ - CHASE_STEP = 1 << 5, /* Just execute a single step of the normalization */ - CHASE_NOFOLLOW = 1 << 6, /* Do not follow the path's right-most component. With ret_fd, when the path's - * right-most component refers to symlink, return O_PATH fd of the symlink. */ - CHASE_WARN = 1 << 7, /* Emit an appropriate warning when an error is encountered */ -}; - -/* How many iterations to execute before returning -ELOOP */ -#define CHASE_SYMLINKS_MAX 32 - -int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret_path, int *ret_fd); - -int chase_symlinks_and_open(const char *path, const char *root, unsigned chase_flags, int open_flags, char **ret_path); -int chase_symlinks_and_opendir(const char *path, const char *root, unsigned chase_flags, char **ret_path, DIR **ret_dir); -int chase_symlinks_and_stat(const char *path, const char *root, unsigned chase_flags, char **ret_path, struct stat *ret_stat, int *ret_fd); - /* Useful for usage with _cleanup_(), removes a directory and frees the pointer */ static inline char *rmdir_and_free(char *p) { PROTECT_ERRNO; @@ -135,16 +98,8 @@ typedef enum UnlinkDeallocateFlags { int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags); -int fsync_directory_of_file(int fd); -int fsync_full(int fd); -int fsync_path_at(int at_fd, const char *path); - -int syncfs_path(int atfd, const char *path); - int open_parent(const char *path, int flags, mode_t mode); -int path_is_encrypted(const char *path); - int conservative_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); static inline int conservative_rename(const char *oldpath, const char *newpath) { return conservative_renameat(AT_FDCWD, oldpath, AT_FDCWD, newpath); diff --git a/src/libnm-systemd-shared/src/basic/hashmap.c b/src/libnm-systemd-shared/src/basic/hashmap.c index 28db939d..ee2a12e8 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.c +++ b/src/libnm-systemd-shared/src/basic/hashmap.c @@ -114,7 +114,7 @@ assert_cc(IDX_FIRST == _IDX_SWAP_END); assert_cc(IDX_FIRST == _IDX_ITERATOR_FIRST); /* Storage space for the "swap" buckets. - * All entry types can fit into a ordered_hashmap_entry. */ + * All entry types can fit into an ordered_hashmap_entry. */ struct swap_entries { struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN]; }; @@ -403,7 +403,7 @@ static struct hashmap_base_entry* bucket_at_virtual(HashmapBase *h, struct swap_ if (idx < _IDX_SWAP_END) return &bucket_at_swap(swap, idx)->p.b; - assert_not_reached("Invalid index"); + assert_not_reached(); } static dib_raw_t* dib_raw_ptr(HashmapBase *h) { @@ -515,7 +515,7 @@ static void* entry_value(HashmapBase *h, struct hashmap_base_entry *e) { return (void*) e->key; default: - assert_not_reached("Unknown hashmap type"); + assert_not_reached(); } } @@ -1749,7 +1749,7 @@ HashmapBase* _hashmap_copy(HashmapBase *h HASHMAP_DEBUG_PARAMS) { r = set_merge((Set*)copy, (Set*)h); break; default: - assert_not_reached("Unknown hashmap type"); + assert_not_reached(); } if (r < 0) @@ -1763,6 +1763,9 @@ char** _hashmap_get_strv(HashmapBase *h) { Iterator i; unsigned idx, n; + if (!h) + return new0(char*, 1); + sv = new(char*, n_entries(h)+1); if (!sv) return NULL; diff --git a/src/libnm-systemd-shared/src/basic/hashmap.h b/src/libnm-systemd-shared/src/basic/hashmap.h index f7ade2e7..eafc08f6 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.h +++ b/src/libnm-systemd-shared/src/basic/hashmap.h @@ -224,7 +224,7 @@ static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const vo return hashmap_remove_and_replace(PLAIN_HASHMAP(h), old_key, new_key, value); } -/* Since merging data from a OrderedHashmap into a Hashmap or vice-versa +/* Since merging data from an OrderedHashmap into a Hashmap or vice-versa * should just work, allow this by having looser type-checking here. */ int _hashmap_merge(Hashmap *h, Hashmap *other); #define hashmap_merge(h, other) _hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other)) diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.c b/src/libnm-systemd-shared/src/basic/hexdecoct.c index 6eb9343f..381e79ba 100644 --- a/src/libnm-systemd-shared/src/basic/hexdecoct.c +++ b/src/libnm-systemd-shared/src/basic/hexdecoct.c @@ -530,6 +530,16 @@ char base64char(int x) { } #endif /* NM_IGNORED */ +/* This is almost base64char(), but not entirely, as it uses the "url and filename safe" alphabet, + * since we don't want "/" appear in interface names (since interfaces appear in sysfs as filenames). + * See section #5 of RFC 4648. */ +char urlsafe_base64char(int x) { + static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_"; + return table[x & 63]; +} + int unbase64char(char c) { unsigned offset; @@ -560,38 +570,79 @@ int unbase64char(char c) { } #if 0 /* NM_IGNORED */ -ssize_t base64mem(const void *p, size_t l, char **out) { - char *r, *z; +static void maybe_line_break(char **x, char *start, size_t line_break) { + size_t n; + + assert(x); + assert(*x); + assert(start); + assert(*x >= start); + + if (line_break == SIZE_MAX) + return; + + n = *x - start; + + if (n % (line_break + 1) == line_break) + *((*x)++) = '\n'; +} + +ssize_t base64mem_full( + const void *p, + size_t l, + size_t line_break, + char **out) { + const uint8_t *x; + char *r, *z; + size_t m; assert(p || l == 0); assert(out); + assert(line_break > 0); /* three input bytes makes four output bytes, padding is added so we must round up */ - z = r = malloc(4 * (l + 2) / 3 + 1); + m = 4 * (l + 2) / 3 + 1; + + if (line_break != SIZE_MAX) + m += m / line_break; + + z = r = malloc(m); if (!r) return -ENOMEM; for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) { /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */ + maybe_line_break(&z, r, line_break); *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + maybe_line_break(&z, r, line_break); *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + maybe_line_break(&z, r, line_break); *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */ + maybe_line_break(&z, r, line_break); *(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */ } switch (l % 3) { case 2: + maybe_line_break(&z, r, line_break); *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + maybe_line_break(&z, r, line_break); *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + maybe_line_break(&z, r, line_break); *(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */ + maybe_line_break(&z, r, line_break); *(z++) = '='; break; case 1: + maybe_line_break(&z, r, line_break); *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + maybe_line_break(&z, r, line_break); *(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */ + maybe_line_break(&z, r, line_break); *(z++) = '='; + maybe_line_break(&z, r, line_break); *(z++) = '='; break; @@ -599,6 +650,7 @@ ssize_t base64mem(const void *p, size_t l, char **out) { *z = 0; *out = r; + assert(z >= r); /* Let static analyzers know that the answer is non-negative. */ return z - r; } diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.h b/src/libnm-systemd-shared/src/basic/hexdecoct.h index 7e2a6892..5218f786 100644 --- a/src/libnm-systemd-shared/src/basic/hexdecoct.h +++ b/src/libnm-systemd-shared/src/basic/hexdecoct.h @@ -27,12 +27,17 @@ char base32hexchar(int x) _const_; int unbase32hexchar(char c) _const_; char base64char(int x) _const_; +char urlsafe_base64char(int x) _const_; int unbase64char(char c) _const_; char *base32hexmem(const void *p, size_t l, bool padding); int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len); -ssize_t base64mem(const void *p, size_t l, char **out); +ssize_t base64mem_full(const void *p, size_t l, size_t line_break, char **ret); +static inline ssize_t base64mem(const void *p, size_t l, char **ret) { + return base64mem_full(p, l, SIZE_MAX, ret); +} + int base64_append(char **prefix, int plen, const void *p, size_t l, int margin, int width); diff --git a/src/libnm-systemd-shared/src/basic/hostname-util.c b/src/libnm-systemd-shared/src/basic/hostname-util.c index 8f7679b6..3063cc7c 100644 --- a/src/libnm-systemd-shared/src/basic/hostname-util.c +++ b/src/libnm-systemd-shared/src/basic/hostname-util.c @@ -39,69 +39,42 @@ char* get_default_hostname(void) { return strdup(FALLBACK_HOSTNAME); } -char* gethostname_malloc(void) { +int gethostname_full(GetHostnameFlags flags, char **ret) { + _cleanup_free_ char *buf = NULL, *fallback = NULL; struct utsname u; const char *s; - /* This call tries to return something useful, either the actual hostname - * or it makes something up. The only reason it might fail is OOM. - * It might even return "localhost" if that's set. */ + assert(ret); assert_se(uname(&u) >= 0); s = u.nodename; - if (isempty(s) || streq(s, "(none)")) - return get_default_hostname(); - - return strdup(s); -} - -char* gethostname_short_malloc(void) { - struct utsname u; - const char *s; - _cleanup_free_ char *f = NULL; - - /* Like above, but kills the FQDN part if present. */ - - assert_se(uname(&u) >= 0); - - s = u.nodename; - if (isempty(s) || streq(s, "(none)") || s[0] == '.') { - s = f = get_default_hostname(); + if (isempty(s) || + (!FLAGS_SET(flags, GET_HOSTNAME_ALLOW_NONE) && streq(s, "(none)")) || + (!FLAGS_SET(flags, GET_HOSTNAME_ALLOW_LOCALHOST) && is_localhost(s)) || + (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.')) { + if (!FLAGS_SET(flags, GET_HOSTNAME_FALLBACK_DEFAULT)) + return -ENXIO; + + s = fallback = get_default_hostname(); if (!s) - return NULL; + return -ENOMEM; - assert(s[0] != '.'); + if (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.') + return -ENXIO; } - return strndup(s, strcspn(s, ".")); -} -#endif /* NM_IGNORED */ - -int gethostname_strict(char **ret) { - struct utsname u; - char *k; - - /* This call will rather fail than make up a name. It will not return "localhost" either. */ - - assert_se(uname(&u) >= 0); - - if (isempty(u.nodename)) - return -ENXIO; - - if (streq(u.nodename, "(none)")) - return -ENXIO; - - if (is_localhost(u.nodename)) - return -ENXIO; - - k = strdup(u.nodename); - if (!k) + if (FLAGS_SET(flags, GET_HOSTNAME_SHORT)) + buf = strndup(s, strcspn(s, ".")); + else + buf = strdup(s); + if (!buf) return -ENOMEM; - *ret = k; + *ret = TAKE_PTR(buf); return 0; } +#endif /* NM_IGNORED */ bool valid_ldh_char(char c) { /* "LDH" โ "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */ @@ -168,6 +141,7 @@ bool hostname_is_valid(const char *s, ValidHostnameFlags flags) { return true; } +#if 0 /* NM_IGNORED */ char* hostname_cleanup(char *s) { char *p, *d; bool dot, hyphen; @@ -205,6 +179,7 @@ char* hostname_cleanup(char *s) { return s; } +#endif /* NM_IGNORED */ bool is_localhost(const char *hostname) { assert(hostname); diff --git a/src/libnm-systemd-shared/src/basic/hostname-util.h b/src/libnm-systemd-shared/src/basic/hostname-util.h index c3fc6752..0d1574db 100644 --- a/src/libnm-systemd-shared/src/basic/hostname-util.h +++ b/src/libnm-systemd-shared/src/basic/hostname-util.h @@ -4,13 +4,41 @@ #include <stdbool.h> #include <stdio.h> +#include "env-file.h" #include "macro.h" #include "strv.h" +typedef enum GetHostnameFlags { + GET_HOSTNAME_ALLOW_NONE = 1 << 0, /* accepts "(none)". */ + GET_HOSTNAME_ALLOW_LOCALHOST = 1 << 1, /* accepts "localhost" or friends. */ + GET_HOSTNAME_FALLBACK_DEFAULT = 1 << 2, /* use default hostname if no hostname is set. */ + GET_HOSTNAME_SHORT = 1 << 3, /* kills the FQDN part if present. */ +} GetHostnameFlags; + +int gethostname_full(GetHostnameFlags flags, char **ret); +static inline int gethostname_strict(char **ret) { + return gethostname_full(0, ret); +} + +static inline char* gethostname_malloc(void) { + char *s; + + if (gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &s) < 0) + return NULL; + + return s; +} + +static inline char* gethostname_short_malloc(void) { + char *s; + + if (gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT | GET_HOSTNAME_SHORT, &s) < 0) + return NULL; + + return s; +} + char* get_default_hostname(void); -char* gethostname_malloc(void); -char* gethostname_short_malloc(void); -int gethostname_strict(char **ret); bool valid_ldh_char(char c) _const_; @@ -33,3 +61,7 @@ static inline bool is_outbound_hostname(const char *hostname) { /* This tries to identify the valid syntaxes for the our synthetic "outbound" host. */ return STRCASE_IN_SET(hostname, "_outbound", "_outbound."); } + +static inline int get_pretty_hostname(char **ret) { + return parse_env_file(NULL, "/etc/machine-info", "PRETTY_HOSTNAME", ret); +} 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 18c44cf1..bd4dd8fc 100644 --- a/src/libnm-systemd-shared/src/basic/in-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/in-addr-util.c @@ -123,6 +123,12 @@ int in_addr_is_localhost(int family, const union in_addr_union *u) { return -EAFNOSUPPORT; } +bool in6_addr_is_ipv4_mapped_address(const struct in6_addr *a) { + return a->s6_addr32[0] == 0 && + a->s6_addr32[1] == 0 && + a->s6_addr32[2] == htobe32(UINT32_C(0x0000ffff)); +} + bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b) { assert(a); assert(b); @@ -797,7 +803,7 @@ int in_addr_prefix_from_string_auto_internal( k = 0; break; default: - assert_not_reached("Invalid prefixlen mode"); + assert_not_reached(); } if (ret_family) @@ -834,35 +840,6 @@ static int in_addr_data_compare_func(const struct in_addr_data *x, const struct DEFINE_HASH_OPS(in_addr_data_hash_ops, struct in_addr_data, in_addr_data_hash_func, in_addr_data_compare_func); -static void in_addr_prefix_hash_func(const struct in_addr_prefix *a, struct siphash *state) { - assert(a); - assert(state); - - siphash24_compress(&a->family, sizeof(a->family), state); - siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state); - siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state); -} - -static int in_addr_prefix_compare_func(const struct in_addr_prefix *x, const struct in_addr_prefix *y) { - int r; - - assert(x); - assert(y); - - r = CMP(x->family, y->family); - if (r != 0) - return r; - - r = CMP(x->prefixlen, y->prefixlen); - if (r != 0) - return r; - - return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family)); -} - -DEFINE_HASH_OPS(in_addr_prefix_hash_ops, struct in_addr_prefix, in_addr_prefix_hash_func, in_addr_prefix_compare_func); -DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(in_addr_prefix_hash_ops_free, struct in_addr_prefix, in_addr_prefix_hash_func, in_addr_prefix_compare_func, free); - void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state) { assert(addr); assert(state); 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 906b3fe9..c74b0d51 100644 --- a/src/libnm-systemd-shared/src/basic/in-addr-util.h +++ b/src/libnm-systemd-shared/src/basic/in-addr-util.h @@ -20,12 +20,6 @@ struct in_addr_data { union in_addr_union address; }; -struct in_addr_prefix { - int family; - uint8_t prefixlen; - union in_addr_union address; -}; - bool in4_addr_is_null(const struct in_addr *a); static inline bool in4_addr_is_set(const struct in_addr *a) { return !in4_addr_is_null(a); @@ -58,6 +52,7 @@ int in_addr_is_localhost(int family, const union in_addr_union *u); bool in4_addr_is_local_multicast(const struct in_addr *a); bool in4_addr_is_non_local(const struct in_addr *a); +bool in6_addr_is_ipv4_mapped_address(const struct in6_addr *a); bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b); bool in6_addr_equal(const struct in6_addr *a, const struct in6_addr *b); @@ -123,8 +118,6 @@ void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state); int in6_addr_compare_func(const struct in6_addr *a, const struct in6_addr *b); extern const struct hash_ops in_addr_data_hash_ops; -extern const struct hash_ops in_addr_prefix_hash_ops; -extern const struct hash_ops in_addr_prefix_hash_ops_free; extern const struct hash_ops in6_addr_hash_ops; #define IPV4_ADDRESS_FMT_STR "%u.%u.%u.%u" diff --git a/src/libnm-systemd-shared/src/basic/inotify-util.c b/src/libnm-systemd-shared/src/basic/inotify-util.c new file mode 100644 index 00000000..cc2a92ad --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/inotify-util.c @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-shared.h" + +#include "fd-util.h" +#include "inotify-util.h" + +int inotify_add_watch_fd(int fd, int what, uint32_t mask) { + int wd; + + /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */ + wd = inotify_add_watch(fd, FORMAT_PROC_FD_PATH(what), mask); + if (wd < 0) + return -errno; + + return wd; +} + +int inotify_add_watch_and_warn(int fd, const char *pathname, uint32_t mask) { + int wd; + + wd = inotify_add_watch(fd, pathname, mask); + if (wd < 0) { + if (errno == ENOSPC) + return log_error_errno(errno, "Failed to add a watch for %s: inotify watch limit reached", pathname); + + return log_error_errno(errno, "Failed to add a watch for %s: %m", pathname); + } + + return wd; +} diff --git a/src/libnm-systemd-shared/src/basic/inotify-util.h b/src/libnm-systemd-shared/src/basic/inotify-util.h new file mode 100644 index 00000000..88af0868 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/inotify-util.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> +#include <limits.h> +#include <stddef.h> +#include <sys/inotify.h> + +#define INOTIFY_EVENT_MAX (offsetof(struct inotify_event, name) + NAME_MAX + 1) + +#define FOREACH_INOTIFY_EVENT(e, buffer, sz) \ + for ((e) = &buffer.ev; \ + (uint8_t*) (e) < (uint8_t*) (buffer.raw) + (sz); \ + (e) = (struct inotify_event*) ((uint8_t*) (e) + sizeof(struct inotify_event) + (e)->len)) + +union inotify_event_buffer { + struct inotify_event ev; + uint8_t raw[INOTIFY_EVENT_MAX]; +}; + +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/list.h b/src/libnm-systemd-shared/src/basic/list.h index 256b7187..e488fff9 100644 --- a/src/libnm-systemd-shared/src/basic/list.h +++ b/src/libnm-systemd-shared/src/basic/list.h @@ -142,11 +142,8 @@ #define LIST_FOREACH_SAFE(name,i,n,head) \ for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n)) -#define LIST_FOREACH_BEFORE(name,i,p) \ - for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev) - -#define LIST_FOREACH_AFTER(name,i,p) \ - for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next) +#define LIST_FOREACH_BACKWARDS(name,i,p) \ + for ((i) = (p); (i); (i) = (i)->name##_prev) /* Iterate through all the members of the list p is included in, but skip over p */ #define LIST_FOREACH_OTHERS(name,i,p) \ diff --git a/src/libnm-systemd-shared/src/basic/log.h b/src/libnm-systemd-shared/src/basic/log.h index 9ed01698..b11eac21 100644 --- a/src/libnm-systemd-shared/src/basic/log.h +++ b/src/libnm-systemd-shared/src/basic/log.h @@ -32,6 +32,15 @@ typedef enum LogTarget{ #define IS_SYNTHETIC_ERRNO(val) ((val) >> 30 & 1) #define ERRNO_VALUE(val) (abs(val) & 255) +/* The callback function to be invoked when syntax warnings are seen + * in the unit files. */ +typedef void (*log_syntax_callback_t)(const char *unit, int level, void *userdata); +void set_log_syntax_callback(log_syntax_callback_t cb, void *userdata); + +static inline void clear_log_syntax_callback(dummy_t *dummy) { + set_log_syntax_callback(/* cb= */ NULL, /* userdata= */ NULL); +} + const char *log_target_to_string(LogTarget target) _const_; LogTarget log_target_from_string(const char *s) _pure_; void log_set_target(LogTarget target); @@ -273,26 +282,24 @@ _noreturn_ void log_assert_failed( #if 0 /* NM_IGNORED */ _noreturn_ void log_assert_failed_unreachable( - const char *text, const char *file, int line, const char *func); #else /* NM_IGNORED */ -#define log_assert_failed_unreachable(text, file, line, func) \ - G_STMT_START \ - { \ - log_internal(LOG_CRIT, \ - 0, \ - file, \ - line, \ - func, \ - "Code should not be reached '%s' at %s:%u, function %s(). Aborting.", \ - text, \ - file, \ - line, \ - func); \ - g_assert_not_reached(); \ - } \ +#define log_assert_failed_unreachable(file, line, func) \ + G_STMT_START \ + { \ + log_internal(LOG_CRIT, \ + 0, \ + file, \ + line, \ + func, \ + "Code should not be reached at %s:%u, function %s(). Aborting.", \ + file, \ + line, \ + func); \ + g_assert_not_reached(); \ + } \ G_STMT_END #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/macro.h b/src/libnm-systemd-shared/src/basic/macro.h index b0852ce5..33d28332 100644 --- a/src/libnm-systemd-shared/src/basic/macro.h +++ b/src/libnm-systemd-shared/src/basic/macro.h @@ -20,30 +20,14 @@ #define _sentinel_ __attribute__((__sentinel__)) #define _destructor_ __attribute__((__destructor__)) #define _deprecated_ __attribute__((__deprecated__)) -#define _packed_ __attribute__((__packed__)) #define _malloc_ __attribute__((__malloc__)) #define _weak_ __attribute__((__weak__)) -#define _likely_(x) (__builtin_expect(!!(x), 1)) -#define _unlikely_(x) (__builtin_expect(!!(x), 0)) #define _public_ __attribute__((__visibility__("default"))) #define _hidden_ __attribute__((__visibility__("hidden"))) #define _weakref_(x) __attribute__((__weakref__(#x))) #define _alignas_(x) __attribute__((__aligned__(__alignof(x)))) #define _alignptr_ __attribute__((__aligned__(sizeof(void*)))) -#if __GNUC__ >= 7 -#define _fallthrough_ __attribute__((__fallthrough__)) -#else -#define _fallthrough_ -#endif -/* Define C11 noreturn without <stdnoreturn.h> and even on older gcc - * compiler versions */ -#ifndef _noreturn_ -#if __STDC_VERSION__ >= 201112L -#define _noreturn_ _Noreturn -#else -#define _noreturn_ __attribute__((__noreturn__)) -#endif -#endif +#define _warn_unused_result_ __attribute__((__warn_unused_result__)) #if !defined(HAS_FEATURE_MEMORY_SANITIZER) # if defined(__has_feature) @@ -171,6 +155,20 @@ #define ALIGN8_PTR(p) ((void*) ALIGN8((unsigned long) (p))) static inline size_t ALIGN_TO(size_t l, size_t ali) { + /* Check that alignment is exponent of 2 */ +#if SIZE_MAX == UINT_MAX + assert(__builtin_popcount(ali) == 1); +#elif SIZE_MAX == ULONG_MAX + assert(__builtin_popcountl(ali) == 1); +#elif SIZE_MAX == ULLONG_MAX + assert(__builtin_popcountll(ali) == 1); +#else +#error "Unexpected size_t" +#endif + + if (l > SIZE_MAX - (ali - 1)) + return SIZE_MAX; /* indicate overflow */ + return ((l + ali - 1) & ~(ali - 1)); } @@ -218,13 +216,6 @@ static inline size_t GREEDY_ALLOC_ROUND_UP(size_t l) { } /* - * STRLEN - return the length of a string literal, minus the trailing NUL byte. - * Contrary to strlen(), this is a constant expression. - * @x: a string literal. - */ -#define STRLEN(x) (sizeof(""x"") - 1) - -/* * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. @@ -290,8 +281,8 @@ static inline int __coverity_check_and_return__(int condition) { #define assert(expr) assert_message_se(expr, #expr) #endif -#define assert_not_reached(t) \ - log_assert_failed_unreachable(t, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__) +#define assert_not_reached() \ + log_assert_failed_unreachable(PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__) #define assert_return(expr, r) \ do { \ @@ -350,15 +341,15 @@ static inline int __coverity_check_and_return__(int condition) { * negative '-' prefix (hence works correctly on signed * types). Includes space for the trailing NUL. */ #define DECIMAL_STR_MAX(type) \ - (2+(sizeof(type) <= 1 ? 3 : \ - sizeof(type) <= 2 ? 5 : \ - sizeof(type) <= 4 ? 10 : \ - sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)]))) + (2U+(sizeof(type) <= 1 ? 3U : \ + sizeof(type) <= 2 ? 5U : \ + sizeof(type) <= 4 ? 10U : \ + sizeof(type) <= 8 ? 20U : sizeof(int[-2*(sizeof(type) > 8)]))) #define DECIMAL_STR_WIDTH(x) \ ({ \ typeof(x) _x_ = (x); \ - unsigned ans = 1; \ + size_t ans = 1; \ while ((_x_ /= 10) != 0) \ ans++; \ ans; \ @@ -491,4 +482,10 @@ static inline size_t size_add(size_t x, size_t y) { return y >= SIZE_MAX - x ? SIZE_MAX : x + y; } +typedef struct { + int _empty[0]; +} dummy_t; + +assert_cc(sizeof(dummy_t) == 0); + #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 7ee7c94e..2d41a26a 100644 --- a/src/libnm-systemd-shared/src/basic/memory-util.c +++ b/src/libnm-systemd-shared/src/basic/memory-util.c @@ -20,26 +20,25 @@ size_t page_size(void) { return pgsz; } -bool memeqzero(const void *data, size_t length) { - /* Does the buffer consist entirely of NULs? +bool memeqbyte(uint8_t byte, const void *data, size_t length) { + /* Does the buffer consist entirely of the same specific byte value? * Copied from https://github.com/systemd/casync/, copied in turn from * https://github.com/rustyrussell/ccan/blob/master/ccan/mem/mem.c#L92, * which is licensed CC-0. */ const uint8_t *p = data; - size_t i; /* Check first 16 bytes manually */ - for (i = 0; i < 16; i++, length--) { + for (size_t i = 0; i < 16; i++, length--) { if (length == 0) return true; - if (p[i]) + if (p[i] != byte) return false; } - /* Now we know first 16 bytes are NUL, memcmp with self. */ - return memcmp(data, p + i, length) == 0; + /* Now we know first 16 bytes match, memcmp() with self. */ + return memcmp(data, p + 16, length) == 0; } #if !HAVE_EXPLICIT_BZERO diff --git a/src/libnm-systemd-shared/src/basic/memory-util.h b/src/libnm-systemd-shared/src/basic/memory-util.h index 0b04278a..9f37431f 100644 --- a/src/libnm-systemd-shared/src/basic/memory-util.h +++ b/src/libnm-systemd-shared/src/basic/memory-util.h @@ -47,7 +47,9 @@ static inline int memcmp_nn(const void *s1, size_t n1, const void *s2, size_t n2 #define zero(x) (memzero(&(x), sizeof(x))) -bool memeqzero(const void *data, size_t length); +bool memeqbyte(uint8_t byte, const void *data, size_t length); + +#define memeqzero(data, length) memeqbyte(0x00, data, length) #define eqzero(x) memeqzero(x, sizeof(x)) @@ -71,6 +73,16 @@ static inline void *memmem_safe(const void *haystack, size_t haystacklen, const return memmem(haystack, haystacklen, needle, needlelen); } +static inline void *mempmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { + const uint8_t *p; + + p = memmem_safe(haystack, haystacklen, needle, needlelen); + if (!p) + return NULL; + + return (uint8_t*) p + needlelen; +} + #if HAVE_EXPLICIT_BZERO static inline void* explicit_bzero_safe(void *p, size_t l) { if (l > 0) diff --git a/src/libnm-systemd-shared/src/basic/missing_syscall.h b/src/libnm-systemd-shared/src/basic/missing_syscall.h index ac3cab29..aeb994f0 100644 --- a/src/libnm-systemd-shared/src/basic/missing_syscall.h +++ b/src/libnm-systemd-shared/src/basic/missing_syscall.h @@ -43,6 +43,26 @@ static inline int missing_pivot_root(const char *new_root, const char *put_old) /* ======================================================================= */ +#if !HAVE_IOPRIO_GET +static inline int missing_ioprio_get(int which, int who) { + return syscall(__NR_ioprio_get, which, who); +} + +# define ioprio_get missing_ioprio_get +#endif + +/* ======================================================================= */ + +#if !HAVE_IOPRIO_SET +static inline int missing_ioprio_set(int which, int who, int ioprio) { + return syscall(__NR_ioprio_set, which, who, ioprio); +} + +# define ioprio_set missing_ioprio_set +#endif + +/* ======================================================================= */ + #if !HAVE_MEMFD_CREATE static inline int missing_memfd_create(const char *name, unsigned int flags) { # ifdef __NR_memfd_create diff --git a/src/libnm-systemd-shared/src/basic/parse-util.c b/src/libnm-systemd-shared/src/basic/parse-util.c index 13758108..f81bf1da 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.c +++ b/src/libnm-systemd-shared/src/basic/parse-util.c @@ -4,7 +4,6 @@ #include <errno.h> #include <inttypes.h> -#include <linux/oom.h> #include <net/if.h> #include <stdio.h> #include <stdlib.h> @@ -738,7 +737,7 @@ int parse_oom_score_adjust(const char *s, int *ret) { if (r < 0) return r; - if (v < OOM_SCORE_ADJ_MIN || v > OOM_SCORE_ADJ_MAX) + if (!oom_score_adjust_is_valid(v)) return -ERANGE; *ret = v; @@ -748,13 +747,13 @@ int parse_oom_score_adjust(const char *s, int *ret) { int store_loadavg_fixed_point(unsigned long i, unsigned long f, loadavg_t *ret) { assert(ret); - if (i >= (~0UL << FSHIFT)) + if (i >= (~0UL << LOADAVG_PRECISION_BITS)) return -ERANGE; - i = i << FSHIFT; - f = DIV_ROUND_UP((f << FSHIFT), 100); + i = i << LOADAVG_PRECISION_BITS; + f = DIV_ROUND_UP((f << LOADAVG_PRECISION_BITS), 100); - if (f >= FIXED_1) + if (f >= LOADAVG_FIXED_POINT_1_0) return -ERANGE; *ret = i | f; diff --git a/src/libnm-systemd-shared/src/basic/parse-util.h b/src/libnm-systemd-shared/src/basic/parse-util.h index 16542769..3dc5e140 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.h +++ b/src/libnm-systemd-shared/src/basic/parse-util.h @@ -3,9 +3,6 @@ #include <inttypes.h> #include <limits.h> -#if 0 /* NM_IGNORED */ -#include <linux/loadavg.h> -#endif /* NM_IGNORED */ #include <stddef.h> #include <stdint.h> #include <sys/types.h> @@ -138,6 +135,14 @@ int parse_ip_prefix_length(const char *s, int *ret); int parse_oom_score_adjust(const char *s, int *ret); +/* Implement floating point using fixed integers, to improve performance when + * calculating load averages. These macros can be used to extract the integer + * and decimal parts of a value. */ +#define LOADAVG_PRECISION_BITS 11 +#define LOADAVG_FIXED_POINT_1_0 (1 << LOADAVG_PRECISION_BITS) +#define LOADAVG_INT_SIDE(x) ((x) >> LOADAVG_PRECISION_BITS) +#define LOADAVG_DECIMAL_SIDE(x) LOADAVG_INT_SIDE(((x) & (LOADAVG_FIXED_POINT_1_0 - 1)) * 100) + /* Given a Linux load average (e.g. decimal number 34.89 where 34 is passed as i and 89 is passed as f), convert it * to a loadavg_t. */ int store_loadavg_fixed_point(unsigned long i, unsigned long f, loadavg_t *ret); diff --git a/src/libnm-systemd-shared/src/basic/path-util.c b/src/libnm-systemd-shared/src/basic/path-util.c index 55e4d317..92c55048 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.c +++ b/src/libnm-systemd-shared/src/basic/path-util.c @@ -15,6 +15,7 @@ #undef basename #include "alloc-util.h" +#include "chase-symlinks.h" #include "extract-word.h" #include "fd-util.h" #include "fs-util.h" @@ -472,8 +473,10 @@ char *path_startswith_full(const char *path, const char *prefix, bool accept_dot int path_compare(const char *a, const char *b) { int r; - assert(a); - assert(b); + /* Order NULL before non-NULL */ + r = CMP(!!a, !!b); + if (r != 0) + return r; /* A relative path and an absolute path must not compare as equal. * Which one is sorted before the other does not really matter. @@ -520,15 +523,9 @@ int path_compare(const char *a, const char *b) { } } -bool path_equal(const char *a, const char *b) { - return path_compare(a, b) == 0; -} - -#if 0 /* NM_IGNORED */ bool path_equal_or_files_same(const char *a, const char *b, int flags) { return path_equal(a, b) || files_same(a, b, flags) > 0; } -#endif /* NM_IGNORED */ bool path_equal_filename(const char *a, const char *b) { _cleanup_free_ char *a_basename = NULL, *b_basename = NULL; @@ -641,7 +638,11 @@ static int check_x_access(const char *path, int *ret_fd) { return r; r = access_fd(fd, X_OK); - if (r < 0) + if (r == -ENOSYS) { + /* /proc is not mounted. Fallback to access(). */ + if (access(path, X_OK) < 0) + return -errno; + } else if (r < 0) return r; if (ret_fd) @@ -650,31 +651,54 @@ static int check_x_access(const char *path, int *ret_fd) { return 0; } -int find_executable_full(const char *name, bool use_path_envvar, char **ret_filename, int *ret_fd) { - int last_error, r; - const char *p = NULL; +static int find_executable_impl(const char *name, const char *root, char **ret_filename, int *ret_fd) { + _cleanup_close_ int fd = -1; + _cleanup_free_ char *path_name = NULL; + int r; assert(name); - if (is_path(name)) { - _cleanup_close_ int fd = -1; - - r = check_x_access(name, ret_fd ? &fd : NULL); + /* Function chase_symlinks() is invoked only when root is not NULL, as using it regardless of + * root value would alter the behavior of existing callers for example: /bin/sleep would become + * /usr/bin/sleep when find_executables is called. Hence, this function should be invoked when + * needed to avoid unforeseen regression or other complicated changes. */ + if (root) { + r = chase_symlinks(name, + root, + CHASE_PREFIX_ROOT, + &path_name, + /* ret_fd= */ NULL); /* prefix root to name in case full paths are not specified */ if (r < 0) return r; - if (ret_filename) { - r = path_make_absolute_cwd(name, ret_filename); - if (r < 0) - return r; - } + name = path_name; + } - if (ret_fd) - *ret_fd = TAKE_FD(fd); + r = check_x_access(name, ret_fd ? &fd : NULL); + if (r < 0) + return r; - return 0; + if (ret_filename) { + r = path_make_absolute_cwd(name, ret_filename); + if (r < 0) + return r; } + if (ret_fd) + *ret_fd = TAKE_FD(fd); + + 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 last_error = -ENOENT, r = 0; + const char *p = NULL; + + assert(name); + + if (is_path(name)) + return find_executable_impl(name, root, ret_filename, ret_fd); + if (use_path_envvar) /* Plain getenv, not secure_getenv, because we want to actually allow the user to pick the * binary. */ @@ -682,12 +706,31 @@ int find_executable_full(const char *name, bool use_path_envvar, char **ret_file if (!p) p = DEFAULT_PATH; - last_error = -ENOENT; + if (exec_search_path) { + char **element; + + STRV_FOREACH(element, exec_search_path) { + _cleanup_free_ char *full_path = NULL; + if (!path_is_absolute(*element)) + continue; + full_path = path_join(*element, name); + if (!full_path) + return -ENOMEM; + + r = find_executable_impl(full_path, root, ret_filename, ret_fd); + if (r < 0) { + if (r != -EACCES) + last_error = r; + continue; + } + return 0; + } + return last_error; + } /* Resolve a single-component name to a full path */ for (;;) { _cleanup_free_ char *element = NULL; - _cleanup_close_ int fd = -1; r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS); if (r < 0) @@ -701,7 +744,7 @@ int find_executable_full(const char *name, bool use_path_envvar, char **ret_file if (!path_extend(&element, name)) return -ENOMEM; - r = check_x_access(element, ret_fd ? &fd : NULL); + r = find_executable_impl(element, root, ret_filename, ret_fd); if (r < 0) { /* PATH entries which we don't have access to are ignored, as per tradition. */ if (r != -EACCES) @@ -710,11 +753,6 @@ int find_executable_full(const char *name, bool use_path_envvar, char **ret_file } /* Found it! */ - if (ret_filename) - *ret_filename = path_simplify(TAKE_PTR(element)); - if (ret_fd) - *ret_fd = TAKE_FD(fd); - return 0; } @@ -722,7 +760,7 @@ int find_executable_full(const char *name, bool use_path_envvar, char **ret_file } bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) { - bool changed = false; + bool changed = false, originally_unset; const char* const* i; assert(timestamp); @@ -730,6 +768,8 @@ bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool upd if (!paths) return false; + originally_unset = *timestamp == 0; + STRV_FOREACH(i, paths) { struct stat stats; usec_t u; @@ -739,11 +779,11 @@ bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool upd u = timespec_load(&stats.st_mtim); - /* first check */ + /* check first */ if (*timestamp >= u) continue; - log_debug("timestamp of '%s' changed", *i); + log_debug(originally_unset ? "Loaded timestamp for '%s'." : "Timestamp of '%s' changed.", *i); /* update timestamp */ if (update) { diff --git a/src/libnm-systemd-shared/src/basic/path-util.h b/src/libnm-systemd-shared/src/basic/path-util.h index 8a7d5e25..fc41c869 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.h +++ b/src/libnm-systemd-shared/src/basic/path-util.h @@ -64,7 +64,11 @@ static inline char* path_startswith(const char *path, const char *prefix) { return path_startswith_full(path, prefix, true); } int path_compare(const char *a, const char *b) _pure_; -bool path_equal(const char *a, const char *b) _pure_; + +static inline bool path_equal(const char *a, const char *b) { + return path_compare(a, b) == 0; +} + bool path_equal_or_files_same(const char *a, const char *b, int flags); /* Compares only the last portion of the input paths, ie: the filenames */ bool path_equal_filename(const char *a, const char *b); @@ -97,9 +101,9 @@ int path_strv_make_absolute_cwd(char **l); char** path_strv_resolve(char **l, const char *root); char** path_strv_resolve_uniq(char **l, const char *root); -int find_executable_full(const char *name, 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); static inline int find_executable(const char *name, char **ret_filename) { - return find_executable_full(name, true, ret_filename, NULL); + return find_executable_full(name, /* root= */ NULL, NULL, true, ret_filename, NULL); } bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update); diff --git a/src/libnm-systemd-shared/src/basic/prioq.c b/src/libnm-systemd-shared/src/basic/prioq.c index 19a9bc57..2f8bdf0f 100644 --- a/src/libnm-systemd-shared/src/basic/prioq.c +++ b/src/libnm-systemd-shared/src/basic/prioq.c @@ -175,6 +175,16 @@ int prioq_put(Prioq *q, void *data, unsigned *idx) { return 0; } +int prioq_ensure_put(Prioq **q, compare_func_t compare_func, void *data, unsigned *idx) { + int r; + + r = prioq_ensure_allocated(q, compare_func); + if (r < 0) + return r; + + return prioq_put(*q, data, idx); +} + static void remove_item(Prioq *q, struct prioq_item *i) { struct prioq_item *l; diff --git a/src/libnm-systemd-shared/src/basic/prioq.h b/src/libnm-systemd-shared/src/basic/prioq.h index 7c766476..508db880 100644 --- a/src/libnm-systemd-shared/src/basic/prioq.h +++ b/src/libnm-systemd-shared/src/basic/prioq.h @@ -16,6 +16,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Prioq*, prioq_free); int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func); int prioq_put(Prioq *q, void *data, unsigned *idx); +int prioq_ensure_put(Prioq **q, compare_func_t compare_func, void *data, unsigned *idx); int prioq_remove(Prioq *q, void *data, unsigned *idx); int prioq_reshuffle(Prioq *q, void *data, unsigned *idx); diff --git a/src/libnm-systemd-shared/src/basic/process-util.c b/src/libnm-systemd-shared/src/basic/process-util.c index d538dda7..96d0912c 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.c +++ b/src/libnm-systemd-shared/src/basic/process-util.c @@ -31,7 +31,6 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" -#include "ioprio.h" #include "locale-util.h" #include "log.h" #include "macro.h" @@ -649,20 +648,23 @@ int get_process_environ(pid_t pid, char **env) { return 0; } -int get_process_ppid(pid_t pid, pid_t *_ppid) { - int r; +int get_process_ppid(pid_t pid, pid_t *ret) { _cleanup_free_ char *line = NULL; long unsigned ppid; const char *p; + int r; assert(pid >= 0); - assert(_ppid); if (pid == 0 || pid == getpid_cached()) { - *_ppid = getppid(); + if (ret) + *ret = getppid(); return 0; } + if (pid == 1) /* PID 1 has no parent, shortcut this case */ + return -EADDRNOTAVAIL; + p = procfs_file_alloca(pid, "stat"); r = read_one_line_file(p, &line); if (r == -ENOENT) @@ -670,9 +672,8 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) { 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 */ + /* 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) @@ -686,10 +687,17 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) { &ppid) != 1) return -EIO; - if ((long unsigned) (pid_t) ppid != ppid) + /* If ppid is zero the process has no parent. Which might be the case for PID 1 but also for + * processes originating in other namespaces that are inserted into a pidns. Return a recognizable + * error in this case. */ + if (ppid == 0) + return -EADDRNOTAVAIL; + + if ((pid_t) ppid < 0 || (long unsigned) (pid_t) ppid != ppid) return -ERANGE; - *_ppid = (pid_t) ppid; + if (ret) + *ret = (pid_t) ppid; return 0; } @@ -826,7 +834,7 @@ int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout) { /* Assuming we woke due to the child exiting. */ if (waitid(P_PID, pid, &status, WEXITED|WNOHANG) == 0) { if (status.si_pid == pid) { - /* This is the correct child.*/ + /* This is the correct child. */ if (status.si_code == CLD_EXITED) return (status.si_status == 0) ? 0 : -EPROTO; else @@ -1036,30 +1044,6 @@ bool is_main_thread(void) { } #if 0 /* NM_IGNORED */ -_noreturn_ void freeze(void) { - - log_close(); - - /* Make sure nobody waits for us on a socket anymore */ - (void) close_all_fds(NULL, 0); - - sync(); - - /* Let's not freeze right away, but keep reaping zombies. */ - for (;;) { - int r; - siginfo_t si = {}; - - r = waitid(P_ALL, 0, &si, WEXITED); - if (r < 0 && errno != EINTR) - break; - } - - /* waitid() failed with an unexpected error, things are really borked. Freeze now! */ - for (;;) - pause(); -} - bool oom_score_adjust_is_valid(int oa) { return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX; } @@ -1272,14 +1256,14 @@ static void restore_sigsetp(sigset_t **ssp) { int safe_fork_full( const char *name, - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid) { pid_t original_pid, pid; sigset_t saved_ss, ss; - _cleanup_(restore_sigsetp) sigset_t *saved_ssp = NULL; + _unused_ _cleanup_(restore_sigsetp) sigset_t *saved_ssp = NULL; bool block_signals = false, block_all = false; int prio, r; @@ -1467,7 +1451,7 @@ int safe_fork_full( int namespace_fork( const char *outer_name, const char *inner_name, - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, @@ -1483,7 +1467,8 @@ int namespace_fork( * process. This ensures that we are fully a member of the destination namespace, with pidns an all, so that * /proc/self/fd works correctly. */ - r = safe_fork_full(outer_name, except_fds, n_except_fds, (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid); + r = safe_fork_full(outer_name, except_fds, n_except_fds, + (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid); if (r < 0) return r; if (r == 0) { @@ -1518,89 +1503,31 @@ int namespace_fork( return 1; } -int fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret_pid, const char *path, ...) { - bool stdout_is_tty, stderr_is_tty; - size_t n, i; - va_list ap; - char **l; - int r; +int set_oom_score_adjust(int value) { + char t[DECIMAL_STR_MAX(int)]; - assert(path); + xsprintf(t, "%i", value); - /* Spawns a temporary TTY agent, making sure it goes away when we go away */ + return write_string_file("/proc/self/oom_score_adj", t, + WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER); +} - r = safe_fork_full(name, - except, - n_except, - FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG, - ret_pid); +int get_oom_score_adjust(int *ret) { + _cleanup_free_ char *t; + int r, a; + + r = read_virtual_file("/proc/self/oom_score_adj", SIZE_MAX, &t, NULL); if (r < 0) return r; - if (r > 0) - return 0; - - /* In the child: */ - stdout_is_tty = isatty(STDOUT_FILENO); - stderr_is_tty = isatty(STDERR_FILENO); + delete_trailing_chars(t, WHITESPACE); - if (!stdout_is_tty || !stderr_is_tty) { - int fd; + assert_se(safe_atoi(t, &a) >= 0); + assert_se(oom_score_adjust_is_valid(a)); - /* Detach from stdout/stderr. and reopen - * /dev/tty for them. This is important to - * ensure that when systemctl is started via - * popen() or a similar call that expects to - * read EOF we actually do generate EOF and - * not delay this indefinitely by because we - * keep an unused copy of stdin around. */ - fd = open("/dev/tty", O_WRONLY); - if (fd < 0) { - log_error_errno(errno, "Failed to open /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - if (!stdout_is_tty && dup2(fd, STDOUT_FILENO) < 0) { - log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - if (!stderr_is_tty && dup2(fd, STDERR_FILENO) < 0) { - log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - safe_close_above_stdio(fd); - } - - (void) rlimit_nofile_safe(); - - /* Count arguments */ - va_start(ap, path); - for (n = 0; va_arg(ap, char*); n++) - ; - va_end(ap); - - /* Allocate strv */ - l = newa(char*, n + 1); - - /* Fill in arguments */ - va_start(ap, path); - for (i = 0; i <= n; i++) - l[i] = va_arg(ap, char*); - va_end(ap); - - execv(path, l); - _exit(EXIT_FAILURE); -} - -int set_oom_score_adjust(int value) { - char t[DECIMAL_STR_MAX(int)]; - - sprintf(t, "%i", value); - - return write_string_file("/proc/self/oom_score_adj", t, - WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER); + if (ret) + *ret = a; + return 0; } int pidfd_get_pid(int fd, pid_t *ret) { diff --git a/src/libnm-systemd-shared/src/basic/process-util.h b/src/libnm-systemd-shared/src/basic/process-util.h index 21e4eb91..bc16d328 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.h +++ b/src/libnm-systemd-shared/src/basic/process-util.h @@ -13,8 +13,8 @@ #include "alloc-util.h" #include "format-util.h" -#include "ioprio.h" #include "macro.h" +#include "missing_ioprio.h" #include "time-util.h" #define procfs_file_alloca(pid, field) \ @@ -82,8 +82,6 @@ int pid_from_same_root_fs(pid_t pid); bool is_main_thread(void); -_noreturn_ void freeze(void); - bool oom_score_adjust_is_valid(int oa); #ifndef PERSONALITY_INVALID @@ -170,17 +168,16 @@ typedef enum ForkFlags { FORK_NEW_USERNS = 1 << 13, /* Run child in its own user namespace */ } ForkFlags; -int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid); +int safe_fork_full(const char *name, int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid); static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) { return safe_fork_full(name, 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 fork_agent(const char *name, const int except[], size_t n_except, pid_t *pid, const char *path, ...) _sentinel_; +int namespace_fork(const char *outer_name, const char *inner_name, 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); +int get_oom_score_adjust(int *ret); /* The highest possibly (theoretic) pid_t value on this architecture. */ #define PID_T_MAX ((pid_t) INT32_MAX) diff --git a/src/libnm-systemd-shared/src/basic/random-util.c b/src/libnm-systemd-shared/src/basic/random-util.c index 4af1fff5..063784dc 100644 --- a/src/libnm-systemd-shared/src/basic/random-util.c +++ b/src/libnm-systemd-shared/src/basic/random-util.c @@ -190,7 +190,7 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { * invocations or so. That's because we don't really care about the quality here. We * generally prefer using RDRAND if the caller allows us to, since this way we won't upset * the kernel's random subsystem by accessing it before the pool is initialized (after all it - * will kmsg log about every attempt to do so)..*/ + * will kmsg log about every attempt to do so). */ for (;;) { unsigned long u; size_t m; diff --git a/src/libnm-systemd-shared/src/basic/set.h b/src/libnm-systemd-shared/src/basic/set.h index 0f867393..5cae1316 100644 --- a/src/libnm-systemd-shared/src/basic/set.h +++ b/src/libnm-systemd-shared/src/basic/set.h @@ -26,7 +26,7 @@ static inline Set* set_free_free(Set *s) { /* no set_free_free_free */ -#define set_copy(s) ((Set*) _hashmap_copy(HASHMAP_BASE(h) HASHMAP_DEBUG_SRC_ARGS)) +#define set_copy(s) ((Set*) _hashmap_copy(HASHMAP_BASE(s) HASHMAP_DEBUG_SRC_ARGS)) int _set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); #define set_ensure_allocated(h, ops) _set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) diff --git a/src/libnm-systemd-shared/src/basic/signal-util.c b/src/libnm-systemd-shared/src/basic/signal-util.c index a31834ab..f96eb4d4 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.c +++ b/src/libnm-systemd-shared/src/basic/signal-util.c @@ -241,7 +241,6 @@ int signal_from_string(const char *s) { void nop_signal_handler(int sig) { /* nothing here */ } -#endif /* NM_IGNORED */ int signal_is_blocked(int sig) { sigset_t ss; @@ -257,3 +256,39 @@ int signal_is_blocked(int sig) { return r; } + +int pop_pending_signal_internal(int sig, ...) { + sigset_t ss; + va_list ap; + int r; + + if (sig < 0) /* Empty list? */ + return -EINVAL; + + if (sigemptyset(&ss) < 0) + return -errno; + + /* Add first signal (if the signal is zero, we'll silently skip it, to make it easier to build + * parameter lists where some element are sometimes off, similar to how sigset_add_many_ap() handles + * this.) */ + if (sig > 0 && sigaddset(&ss, sig) < 0) + return -errno; + + /* Add all other signals */ + va_start(ap, sig); + r = sigset_add_many_ap(&ss, ap); + va_end(ap); + if (r < 0) + return r; + + r = sigtimedwait(&ss, NULL, &(struct timespec) { 0, 0 }); + if (r < 0) { + if (errno == EAGAIN) + return 0; + + return -errno; + } + + return r; /* Returns the signal popped */ +} +#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/signal-util.h b/src/libnm-systemd-shared/src/basic/signal-util.h index 37271d7a..36372c19 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.h +++ b/src/libnm-systemd-shared/src/basic/signal-util.h @@ -62,3 +62,6 @@ static inline const char* signal_to_string_with_check(int n) { } int signal_is_blocked(int sig); + +int pop_pending_signal_internal(int sig, ...); +#define pop_pending_signal(...) pop_pending_signal_internal(__VA_ARGS__, -1) diff --git a/src/libnm-systemd-shared/src/basic/socket-util.c b/src/libnm-systemd-shared/src/basic/socket-util.c index da36c280..49a4df44 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.c +++ b/src/libnm-systemd-shared/src/basic/socket-util.c @@ -112,7 +112,7 @@ int socket_address_verify(const SocketAddress *a, bool strict) { /* If there's no embedded NUL byte, then the size needs to match the whole * structure or the structure with one extra NUL byte suffixed. (Yeah, Linux is awful, * and considers both equivalent: getsockname() even extends sockaddr_un beyond its - * size if the path is non NUL terminated.)*/ + * size if the path is non NUL terminated.) */ if (!IN_SET(a->size, sizeof(a->sockaddr.un.sun_path), sizeof(a->sockaddr.un.sun_path)+1)) return -EINVAL; } @@ -460,23 +460,23 @@ int sockaddr_pretty( if (r < 0) return -ENOMEM; } else { - char a[INET6_ADDRSTRLEN], ifname[IF_NAMESIZE + 1]; + char a[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &sa->in6.sin6_addr, a, sizeof(a)); - if (sa->in6.sin6_scope_id != 0) - format_ifname_full(sa->in6.sin6_scope_id, ifname, FORMAT_IFNAME_IFINDEX); if (include_port) { - r = asprintf(&p, + if (asprintf(&p, "[%s]:%u%s%s", a, be16toh(sa->in6.sin6_port), sa->in6.sin6_scope_id != 0 ? "%" : "", - sa->in6.sin6_scope_id != 0 ? ifname : ""); - if (r < 0) + FORMAT_IFNAME_FULL(sa->in6.sin6_scope_id, FORMAT_IFNAME_IFINDEX)) < 0) return -ENOMEM; } else { - p = sa->in6.sin6_scope_id != 0 ? strjoin(a, "%", ifname) : strdup(a); + if (sa->in6.sin6_scope_id != 0) + p = strjoin(a, "%", FORMAT_IFNAME_FULL(sa->in6.sin6_scope_id, FORMAT_IFNAME_IFINDEX)); + else + p = strdup(a); if (!p) return -ENOMEM; } @@ -754,6 +754,22 @@ static const char* const ip_tos_table[] = { DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); #endif /* NM_IGNORED */ +bool ifname_valid_char(char a) { + if ((unsigned char) a >= 127U) + return false; + + if ((unsigned char) a <= 32U) + return false; + + if (IN_SET(a, + ':', /* colons are used by the legacy "alias" interface logic */ + '/', /* slashes cannot work, since we need to use network interfaces in sysfs paths, and in paths slashes are separators */ + '%')) /* %d is used in the kernel's weird foo%d format string naming feature which we really really don't want to ever run into by accident */ + return false; + + return true; +} + bool ifname_valid_full(const char *p, IfnameValidFlags flags) { bool numeric = true; @@ -783,20 +799,11 @@ bool ifname_valid_full(const char *p, IfnameValidFlags flags) { /* Let's refuse "all" and "default" as interface name, to avoid collisions with the special sysctl * directories /proc/sys/net/{ipv4,ipv6}/conf/{all,default} */ - if (STR_IN_SET(p, "all", "default")) + if (!FLAGS_SET(flags, IFNAME_VALID_SPECIAL) && STR_IN_SET(p, "all", "default")) return false; for (const char *t = p; *t; t++) { - if ((unsigned char) *t >= 127U) - return false; - - if ((unsigned char) *t <= 32U) - return false; - - if (IN_SET(*t, - ':', /* colons are used by the legacy "alias" interface logic */ - '/', /* slashes cannot work, since we need to use network interfaces in sysfs paths, and in paths slashes are separators */ - '%')) /* %d is used in the kernel's weird foo%d format string naming feature which we really really don't want to ever run into by accident */ + if (!ifname_valid_char(*t)) return false; numeric = numeric && (*t >= '0' && *t <= '9'); @@ -921,7 +928,7 @@ int getpeergroups(int fd, gid_t **ret) { ssize_t send_one_fd_iov_sa( int transport_fd, int fd, - struct iovec *iov, size_t iovlen, + const struct iovec *iov, size_t iovlen, const struct sockaddr *sa, socklen_t len, int flags) { @@ -929,7 +936,7 @@ ssize_t send_one_fd_iov_sa( struct msghdr mh = { .msg_name = (struct sockaddr*) sa, .msg_namelen = len, - .msg_iov = iov, + .msg_iov = (struct iovec *)iov, .msg_iovlen = iovlen, }; ssize_t k; @@ -1204,7 +1211,7 @@ int sockaddr_un_set_path(struct sockaddr_un *ret, const char *path) { /* Don't allow paths larger than the space in sockaddr_un. Note that we are a tiny bit more restrictive than * the kernel is: we insist on NUL termination (both for abstract namespace and regular file system socket * addresses!), which the kernel doesn't. We do this to reduce chance of incompatibility with other apps that - * do not expect non-NUL terminated file system path*/ + * do not expect non-NUL terminated file system path. */ if (l+1 > sizeof(ret->sun_path)) return -EINVAL; @@ -1238,7 +1245,7 @@ int socket_bind_to_ifname(int fd, const char *ifname) { } int socket_bind_to_ifindex(int fd, int ifindex) { - char ifname[IF_NAMESIZE + 1]; + char ifname[IF_NAMESIZE]; int r; assert(fd >= 0); @@ -1256,8 +1263,9 @@ int socket_bind_to_ifindex(int fd, int ifindex) { return r; /* Fall back to SO_BINDTODEVICE on kernels < 5.0 which didn't have SO_BINDTOIFINDEX */ - if (!format_ifname(ifindex, ifname)) - return -errno; + r = format_ifname(ifindex, ifname); + if (r < 0) + return r; return socket_bind_to_ifname(fd, ifname); } diff --git a/src/libnm-systemd-shared/src/basic/socket-util.h b/src/libnm-systemd-shared/src/basic/socket-util.h index a8a915be..4caa27c8 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.h +++ b/src/libnm-systemd-shared/src/basic/socket-util.h @@ -137,10 +137,12 @@ int ip_tos_to_string_alloc(int i, char **s); int ip_tos_from_string(const char *s); typedef enum { - IFNAME_VALID_ALTERNATIVE = 1 << 0, - IFNAME_VALID_NUMERIC = 1 << 1, - _IFNAME_VALID_ALL = IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC, + IFNAME_VALID_ALTERNATIVE = 1 << 0, /* Allow "altnames" too */ + IFNAME_VALID_NUMERIC = 1 << 1, /* Allow decimal formatted ifindexes too */ + IFNAME_VALID_SPECIAL = 1 << 2, /* Allow the special names "all" and "default" */ + _IFNAME_VALID_ALL = IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC | IFNAME_VALID_SPECIAL, } IfnameValidFlags; +bool ifname_valid_char(char a); bool ifname_valid_full(const char *p, IfnameValidFlags flags); static inline bool ifname_valid(const char *p) { return ifname_valid_full(p, 0); @@ -154,7 +156,7 @@ int getpeergroups(int fd, gid_t **ret); ssize_t send_one_fd_iov_sa( int transport_fd, int fd, - struct iovec *iov, size_t iovlen, + const struct iovec *iov, size_t iovlen, const struct sockaddr *sa, socklen_t len, int flags); int send_one_fd_sa(int transport_fd, @@ -246,7 +248,7 @@ struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t leng _len = sizeof(struct sockaddr_vm); \ break; \ default: \ - assert_not_reached("invalid socket family"); \ + assert_not_reached(); \ } \ _len; \ }) @@ -278,6 +280,28 @@ 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. */ +struct timeval_large { + uint64_t tvl_sec, tvl_usec; +}; +struct timespec_large { + uint64_t tvl_sec, tvl_nsec; +}; + +/* glibc duplicates timespec/timeval on certain 32bit archs, once in 32bit and once in 64bit. + * 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 \ + ((sizeof(struct timeval) == sizeof(struct timeval_large)) ? \ + CMSG_SPACE(sizeof(struct timeval)) : \ + CMSG_SPACE(sizeof(struct timeval)) + \ + CMSG_SPACE(sizeof(struct timeval_large))) +#define CMSG_SPACE_TIMESPEC \ + ((sizeof(struct timespec) == sizeof(struct timespec_large)) ? \ + CMSG_SPACE(sizeof(struct timespec)) : \ + CMSG_SPACE(sizeof(struct timespec)) + \ + CMSG_SPACE(sizeof(struct timespec_large))) + ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags); int socket_get_family(int fd, int *ret); diff --git a/src/libnm-systemd-shared/src/basic/stat-util.c b/src/libnm-systemd-shared/src/basic/stat-util.c index 5112cbeb..133828bc 100644 --- a/src/libnm-systemd-shared/src/basic/stat-util.c +++ b/src/libnm-systemd-shared/src/basic/stat-util.c @@ -10,10 +10,11 @@ #include <unistd.h> #include "alloc-util.h" +#include "chase-symlinks.h" #include "dirent-util.h" +#include "errno-util.h" #include "fd-util.h" #include "fileio.h" -#include "fs-util.h" #include "macro.h" #include "missing_fs.h" #include "missing_magic.h" @@ -159,6 +160,7 @@ int path_is_read_only_fs(const char *path) { return false; } +#endif /* NM_IGNORED */ int files_same(const char *filea, const char *fileb, int flags) { struct stat a, b; @@ -176,6 +178,7 @@ int files_same(const char *filea, const char *fileb, int flags) { a.st_ino == b.st_ino; } +#if 0 /* NM_IGNORED */ bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) { assert(s); assert_cc(sizeof(statfs_f_type_t) >= sizeof(s->f_type)); diff --git a/src/libnm-systemd-shared/src/basic/stdio-util.h b/src/libnm-systemd-shared/src/basic/stdio-util.h index d45d3c1a..f3b213c5 100644 --- a/src/libnm-systemd-shared/src/basic/stdio-util.h +++ b/src/libnm-systemd-shared/src/basic/stdio-util.h @@ -11,8 +11,13 @@ #include "macro.h" #include "memory-util.h" -#define snprintf_ok(buf, len, fmt, ...) \ - ((size_t) snprintf(buf, len, fmt, __VA_ARGS__) < (len)) +#define snprintf_ok(buf, len, fmt, ...) \ + ({ \ + char *_buf = (buf); \ + size_t _len = (len); \ + int _snpf = snprintf(_buf, _len, (fmt), __VA_ARGS__); \ + _snpf >= 0 && (size_t) _snpf < _len ? _buf : NULL; \ + }) #define xsprintf(buf, fmt, ...) \ assert_message_se(snprintf_ok(buf, ELEMENTSOF(buf), fmt, __VA_ARGS__), "xsprintf: " #buf "[] must be big enough") @@ -60,7 +65,7 @@ do { \ (void) va_arg(ap, long double); \ break; \ default: \ - assert_not_reached("Unknown format string argument."); \ + assert_not_reached(); \ } \ } \ } while (false) diff --git a/src/libnm-systemd-shared/src/basic/string-util.c b/src/libnm-systemd-shared/src/basic/string-util.c index c991a396..fea22ec8 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.c +++ b/src/libnm-systemd-shared/src/basic/string-util.c @@ -1155,4 +1155,20 @@ int string_contains_word_strv(const char *string, const char *separators, char * *ret_word = found; return !!found; } + +bool streq_skip_trailing_chars(const char *s1, const char *s2, const char *ok) { + if (!s1 && !s2) + return true; + if (!s1 || !s2) + return false; + + if (!ok) + ok = WHITESPACE; + + for (; *s1 && *s2; s1++, s2++) + if (*s1 != *s2) + break; + + return in_charset(s1, ok) && in_charset(s2, ok); +} #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/string-util.h b/src/libnm-systemd-shared/src/basic/string-util.h index 9155e50b..775d694c 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.h +++ b/src/libnm-systemd-shared/src/basic/string-util.h @@ -189,22 +189,6 @@ static inline void strncpy_exact(char *buf, const char *src, size_t buf_len) { } REENABLE_WARNING; -/* Like startswith(), but operates on arbitrary memory blocks */ -static inline void *memory_startswith(const void *p, size_t sz, const char *token) { - assert(token); - - size_t n = strlen(token); - if (sz < n) - return NULL; - - assert(p); - - if (memcmp(p, token, n) != 0) - return NULL; - - return (uint8_t*) p + n; -} - /* Like startswith_no_case(), but operates on arbitrary memory blocks. * It works only for ASCII strings. */ @@ -242,3 +226,5 @@ int string_contains_word_strv(const char *string, const char *separators, char * static inline int string_contains_word(const char *string, const char *separators, const char *word) { return string_contains_word_strv(string, separators, STRV_MAKE(word), NULL); } + +bool streq_skip_trailing_chars(const char *s1, const char *s2, const char *ok); diff --git a/src/libnm-systemd-shared/src/basic/strv.c b/src/libnm-systemd-shared/src/basic/strv.c index e24ef535..1d840923 100644 --- a/src/libnm-systemd-shared/src/basic/strv.c +++ b/src/libnm-systemd-shared/src/basic/strv.c @@ -305,6 +305,24 @@ int strv_split_full(char ***t, const char *s, const char *separators, ExtractFla } #if 0 /* NM_IGNORED */ +int strv_split_and_extend_full(char ***t, const char *s, const char *separators, bool filter_duplicates, ExtractFlags flags) { + _cleanup_strv_free_ char **l = NULL; + int r; + + assert(t); + assert(s); + + r = strv_split_full(&l, s, separators, flags); + if (r < 0) + return r; + + r = strv_extend_strv(t, l, filter_duplicates); + if (r < 0) + return r; + + return (int) strv_length(*t); +} + int strv_split_colon_pairs(char ***t, const char *s) { _cleanup_strv_free_ char **l = NULL; size_t n = 0; @@ -831,6 +849,7 @@ char **strv_reverse(char **l) { return l; } +#if 0 /* NM_IGNORED */ char **strv_shell_escape(char **l, const char *bad) { char **s; @@ -850,6 +869,7 @@ char **strv_shell_escape(char **l, const char *bad) { return l; } +#endif /* NM_IGNORED */ bool strv_fnmatch_full(char* const* patterns, const char *s, int flags, size_t *matched_pos) { for (size_t i = 0; patterns && patterns[i]; i++) diff --git a/src/libnm-systemd-shared/src/basic/strv.h b/src/libnm-systemd-shared/src/basic/strv.h index 911528fa..a56ef941 100644 --- a/src/libnm-systemd-shared/src/basic/strv.h +++ b/src/libnm-systemd-shared/src/basic/strv.h @@ -83,6 +83,9 @@ static inline char **strv_split(const char *s, const char *separators) { return ret; } +int strv_split_and_extend_full(char ***t, const char *s, const char *separators, bool filter_duplicates, ExtractFlags flags); +#define strv_split_and_extend(t, s, sep, dup) strv_split_and_extend_full(t, s, sep, dup, 0) + int strv_split_newlines_full(char ***ret, const char *s, ExtractFlags flags); static inline char **strv_split_newlines(const char *s) { char **ret; @@ -233,9 +236,11 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space); #define strv_free_and_replace(a, b) \ ({ \ - strv_free(a); \ - (a) = (b); \ - (b) = NULL; \ + char ***_a = &(a); \ + char ***_b = &(b); \ + strv_free(*_a); \ + (*_a) = (*_b); \ + (*_b) = NULL; \ 0; \ }) diff --git a/src/libnm-systemd-shared/src/basic/time-util.c b/src/libnm-systemd-shared/src/basic/time-util.c index 4b7042f7..21e7b24a 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.c +++ b/src/libnm-systemd-shared/src/basic/time-util.c @@ -432,48 +432,66 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) { s = "left"; } - if (d >= USEC_PER_YEAR) - snprintf(buf, l, USEC_FMT " years " USEC_FMT " months %s", - d / USEC_PER_YEAR, - (d % USEC_PER_YEAR) / USEC_PER_MONTH, s); - else if (d >= USEC_PER_MONTH) - snprintf(buf, l, USEC_FMT " months " USEC_FMT " days %s", - d / USEC_PER_MONTH, - (d % USEC_PER_MONTH) / USEC_PER_DAY, s); - else if (d >= USEC_PER_WEEK) - snprintf(buf, l, USEC_FMT " weeks " USEC_FMT " days %s", - d / USEC_PER_WEEK, - (d % USEC_PER_WEEK) / USEC_PER_DAY, s); - else if (d >= 2*USEC_PER_DAY) - snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s); + if (d >= USEC_PER_YEAR) { + usec_t years = d / USEC_PER_YEAR; + usec_t months = (d % USEC_PER_YEAR) / USEC_PER_MONTH; + + (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s", + years, + years == 1 ? "year" : "years", + months, + months == 1 ? "month" : "months", + s); + } else if (d >= USEC_PER_MONTH) { + usec_t months = d / USEC_PER_MONTH; + usec_t days = (d % USEC_PER_MONTH) / USEC_PER_DAY; + + (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s", + months, + months == 1 ? "month" : "months", + days, + days == 1 ? "day" : "days", + s); + } else if (d >= USEC_PER_WEEK) { + usec_t weeks = d / USEC_PER_WEEK; + usec_t days = (d % USEC_PER_WEEK) / USEC_PER_DAY; + + (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s", + weeks, + weeks == 1 ? "week" : "weeks", + days, + days == 1 ? "day" : "days", + s); + } else if (d >= 2*USEC_PER_DAY) + (void) snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s); else if (d >= 25*USEC_PER_HOUR) - snprintf(buf, l, "1 day " USEC_FMT "h %s", - (d - USEC_PER_DAY) / USEC_PER_HOUR, s); + (void) snprintf(buf, l, "1 day " USEC_FMT "h %s", + (d - USEC_PER_DAY) / USEC_PER_HOUR, s); else if (d >= 6*USEC_PER_HOUR) - snprintf(buf, l, USEC_FMT "h %s", - d / USEC_PER_HOUR, s); + (void) snprintf(buf, l, USEC_FMT "h %s", + d / USEC_PER_HOUR, s); else if (d >= USEC_PER_HOUR) - snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s", - d / USEC_PER_HOUR, - (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s); + (void) snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s", + d / USEC_PER_HOUR, + (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s); else if (d >= 5*USEC_PER_MINUTE) - snprintf(buf, l, USEC_FMT "min %s", - d / USEC_PER_MINUTE, s); + (void) snprintf(buf, l, USEC_FMT "min %s", + d / USEC_PER_MINUTE, s); else if (d >= USEC_PER_MINUTE) - snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s", - d / USEC_PER_MINUTE, - (d % USEC_PER_MINUTE) / USEC_PER_SEC, s); + (void) snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s", + d / USEC_PER_MINUTE, + (d % USEC_PER_MINUTE) / USEC_PER_SEC, s); else if (d >= USEC_PER_SEC) - snprintf(buf, l, USEC_FMT "s %s", - d / USEC_PER_SEC, s); + (void) snprintf(buf, l, USEC_FMT "s %s", + d / USEC_PER_SEC, s); else if (d >= USEC_PER_MSEC) - snprintf(buf, l, USEC_FMT "ms %s", - d / USEC_PER_MSEC, s); + (void) snprintf(buf, l, USEC_FMT "ms %s", + d / USEC_PER_MSEC, s); else if (d > 0) - snprintf(buf, l, USEC_FMT"us %s", - d, s); + (void) snprintf(buf, l, USEC_FMT"us %s", + d, s); else - snprintf(buf, l, "now"); + (void) snprintf(buf, l, "now"); buf[l-1] = 0; return buf; @@ -539,14 +557,12 @@ char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { /* Let's see if we should shows this in dot notation */ if (t < USEC_PER_MINUTE && b > 0) { - usec_t cc; - signed char j; + signed char j = 0; - j = 0; - for (cc = table[i].usec; cc > 1; cc /= 10) + for (usec_t cc = table[i].usec; cc > 1; cc /= 10) j++; - for (cc = accuracy; cc > 1; cc /= 10) { + for (usec_t cc = accuracy; cc > 1; cc /= 10) { b /= 10; j--; } @@ -1035,7 +1051,7 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { s = extract_multiplier(p + strspn(p, WHITESPACE), &multiplier); if (s == p && *s != '\0') - /* Don't allow '12.34.56', but accept '12.34 .56' or '12.34s.56'*/ + /* Don't allow '12.34.56', but accept '12.34 .56' or '12.34s.56' */ return -EINVAL; p = s; @@ -1063,7 +1079,7 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { r += k; } - /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge"*/ + /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */ if (b == e + 1) return -EINVAL; } @@ -1210,7 +1226,7 @@ int parse_nsec(const char *t, nsec_t *nsec) { s = extract_nsec_multiplier(p + strspn(p, WHITESPACE), &multiplier); if (s == p && *s != '\0') - /* Don't allow '12.34.56', but accept '12.34 .56' or '12.34s.56'*/ + /* Don't allow '12.34.56', but accept '12.34 .56' or '12.34s.56' */ return -EINVAL; p = s; @@ -1238,7 +1254,7 @@ int parse_nsec(const char *t, nsec_t *nsec) { r += k; } - /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge"*/ + /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */ if (b == e + 1) return -EINVAL; } @@ -1249,75 +1265,127 @@ int parse_nsec(const char *t, nsec_t *nsec) { return 0; } -int get_timezones(char ***ret) { +static int get_timezones_from_zone1970_tab(char ***ret) { _cleanup_fclose_ FILE *f = NULL; _cleanup_strv_free_ char **zones = NULL; - size_t n_zones = 0; int r; assert(ret); - zones = strv_new("UTC"); - if (!zones) - return -ENOMEM; + f = fopen("/usr/share/zoneinfo/zone1970.tab", "re"); + if (!f) + return -errno; - n_zones = 1; + for (;;) { + _cleanup_free_ char *line = NULL, *cc = NULL, *co = NULL, *tz = NULL; - f = fopen("/usr/share/zoneinfo/zone1970.tab", "re"); - if (f) { - for (;;) { - _cleanup_free_ char *line = NULL, *w = NULL; - char *p; - size_t k; + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) + break; - r = read_line(f, LONG_LINE_MAX, &line); - if (r < 0) - return r; - if (r == 0) - break; + const char *p = line; + + /* Line format is: + * 'country codes' 'coordinates' 'timezone' 'comments' */ + r = extract_many_words(&p, NULL, 0, &cc, &co, &tz, NULL); + if (r < 0) + continue; - p = strstrip(line); + /* Lines that start with # are comments. */ + if (*cc == '#') + continue; + + r = strv_extend(&zones, tz); + if (r < 0) + return r; + } - if (isempty(p) || *p == '#') - continue; + *ret = TAKE_PTR(zones); + return 0; +} - /* Skip over country code */ - p += strcspn(p, WHITESPACE); - p += strspn(p, WHITESPACE); +static int get_timezones_from_tzdata_zi(char ***ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **zones = NULL; + int r; - /* Skip over coordinates */ - p += strcspn(p, WHITESPACE); - p += strspn(p, WHITESPACE); + f = fopen("/usr/share/zoneinfo/tzdata.zi", "re"); + if (!f) + return -errno; - /* Found timezone name */ - k = strcspn(p, WHITESPACE); - if (k <= 0) - continue; + for (;;) { + _cleanup_free_ char *line = NULL, *type = NULL, *f1 = NULL, *f2 = NULL; - w = strndup(p, k); - if (!w) - return -ENOMEM; + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) + break; - if (!GREEDY_REALLOC(zones, n_zones + 2)) - return -ENOMEM; + const char *p = line; - zones[n_zones++] = TAKE_PTR(w); - zones[n_zones] = NULL; - } + /* The only lines we care about are Zone and Link lines. + * Zone line format is: + * 'Zone' 'timezone' ... + * Link line format is: + * 'Link' 'target' 'alias' + * See 'man zic' for more detail. */ + r = extract_many_words(&p, NULL, 0, &type, &f1, &f2, NULL); + if (r < 0) + continue; - strv_sort(zones); - strv_uniq(zones); + char *tz; + if (IN_SET(*type, 'Z', 'z')) + /* Zone lines have timezone in field 1. */ + tz = f1; + else if (IN_SET(*type, 'L', 'l')) + /* Link lines have timezone in field 2. */ + tz = f2; + else + /* Not a line we care about. */ + continue; - } else if (errno != ENOENT) - return -errno; + r = strv_extend(&zones, tz); + if (r < 0) + return r; + } *ret = TAKE_PTR(zones); + return 0; +} + +int get_timezones(char ***ret) { + _cleanup_strv_free_ char **zones = NULL; + int r; + + assert(ret); + + r = get_timezones_from_tzdata_zi(&zones); + if (r == -ENOENT) { + log_debug_errno(r, "Could not get timezone data from tzdata.zi, using zone1970.tab: %m"); + r = get_timezones_from_zone1970_tab(&zones); + if (r == -ENOENT) + log_debug_errno(r, "Could not get timezone data from zone1970.tab, using UTC: %m"); + } + if (r < 0 && r != -ENOENT) + return r; + /* Always include UTC */ + r = strv_extend(&zones, "UTC"); + if (r < 0) + return -ENOMEM; + + strv_sort(zones); + strv_uniq(zones); + + *ret = TAKE_PTR(zones); return 0; } #endif /* NM_IGNORED */ -bool timezone_is_valid(const char *name, int log_level) { +int verify_timezone(const char *name, int log_level) { bool slash = false; const char *p, *t; _cleanup_close_ int fd = -1; @@ -1325,26 +1393,26 @@ bool timezone_is_valid(const char *name, int log_level) { int r; if (isempty(name)) - return false; + return -EINVAL; /* Always accept "UTC" as valid timezone, since it's the fallback, even if user has no timezones installed. */ if (streq(name, "UTC")) - return true; + return 0; if (name[0] == '/') - return false; + return -EINVAL; for (p = name; *p; p++) { if (!(*p >= '0' && *p <= '9') && !(*p >= 'a' && *p <= 'z') && !(*p >= 'A' && *p <= 'Z') && !IN_SET(*p, '-', '_', '+', '/')) - return false; + return -EINVAL; if (*p == '/') { if (slash) - return false; + return -EINVAL; slash = true; } else @@ -1352,38 +1420,31 @@ bool timezone_is_valid(const char *name, int log_level) { } if (slash) - return false; + return -EINVAL; if (p - name >= PATH_MAX) - return false; + return -ENAMETOOLONG; t = strjoina("/usr/share/zoneinfo/", name); fd = open(t, O_RDONLY|O_CLOEXEC); - if (fd < 0) { - log_full_errno(log_level, errno, "Failed to open timezone file '%s': %m", t); - return false; - } + if (fd < 0) + return log_full_errno(log_level, errno, "Failed to open timezone file '%s': %m", t); r = fd_verify_regular(fd); - if (r < 0) { - log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t); - return false; - } + if (r < 0) + return log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t); r = loop_read_exact(fd, buf, 4, false); - if (r < 0) { - log_full_errno(log_level, r, "Failed to read from timezone file '%s': %m", t); - return false; - } + if (r < 0) + return log_full_errno(log_level, r, "Failed to read from timezone file '%s': %m", t); /* Magic from tzfile(5) */ - if (memcmp(buf, "TZif", 4) != 0) { - log_full(log_level, "Timezone file '%s' has wrong magic bytes", t); - return false; - } + if (memcmp(buf, "TZif", 4) != 0) + return log_full_errno(log_level, SYNTHETIC_ERRNO(EBADMSG), + "Timezone file '%s' has wrong magic bytes", t); - return true; + return 0; } bool clock_boottime_supported(void) { @@ -1445,7 +1506,7 @@ int get_timezone(char **ret) { r = readlink_malloc("/etc/localtime", &t); if (r == -ENOENT) { - /* If the symlink does not exist, assume "UTC", like glibc does*/ + /* If the symlink does not exist, assume "UTC", like glibc does */ z = strdup("UTC"); if (!z) return -ENOMEM; diff --git a/src/libnm-systemd-shared/src/basic/time-util.h b/src/libnm-systemd-shared/src/basic/time-util.h index cfde1898..895af882 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.h +++ b/src/libnm-systemd-shared/src/basic/time-util.h @@ -111,20 +111,31 @@ usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock); usec_t timespec_load(const struct timespec *ts) _pure_; nsec_t timespec_load_nsec(const struct timespec *ts) _pure_; -struct timespec *timespec_store(struct timespec *ts, usec_t u); -struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n); +struct timespec* timespec_store(struct timespec *ts, usec_t u); +struct timespec* timespec_store_nsec(struct timespec *ts, nsec_t n); usec_t timeval_load(const struct timeval *tv) _pure_; -struct timeval *timeval_store(struct timeval *tv, usec_t u); +struct timeval* timeval_store(struct timeval *tv, usec_t u); -char *format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style); -char *format_timestamp_relative(char *buf, size_t l, usec_t t); -char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy); +char* format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style) _warn_unused_result_; +char* format_timestamp_relative(char *buf, size_t l, usec_t t) _warn_unused_result_; +char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) _warn_unused_result_; -static inline char *format_timestamp(char *buf, size_t l, usec_t t) { +_warn_unused_result_ +static inline char* format_timestamp(char *buf, size_t l, usec_t t) { return format_timestamp_style(buf, l, t, TIMESTAMP_PRETTY); } +/* Note: the lifetime of the compound literal is the immediately surrounding block, + * see C11 ยง6.5.2.5, and + * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */ +#define FORMAT_TIMESTAMP(t) format_timestamp((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t) +#define FORMAT_TIMESTAMP_RELATIVE(t) \ + format_timestamp_relative((char[FORMAT_TIMESTAMP_RELATIVE_MAX]){}, FORMAT_TIMESTAMP_RELATIVE_MAX, t) +#define FORMAT_TIMESPAN(t, accuracy) format_timespan((char[FORMAT_TIMESPAN_MAX]){}, FORMAT_TIMESPAN_MAX, t, accuracy) +#define FORMAT_TIMESTAMP_STYLE(t, style) \ + format_timestamp_style((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t, style) + int parse_timestamp(const char *t, usec_t *usec); int parse_sec(const char *t, usec_t *usec); @@ -134,7 +145,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit); int parse_nsec(const char *t, nsec_t *nsec); int get_timezones(char ***l); -bool timezone_is_valid(const char *name, int log_level); +int verify_timezone(const char *name, int log_level); +static inline bool timezone_is_valid(const char *name, int log_level) { + return verify_timezone(name, log_level) >= 0; +} bool clock_boottime_supported(void); bool clock_supported(clockid_t clock); @@ -153,9 +167,8 @@ usec_t jiffies_to_usec(uint32_t jiffies); bool in_utc_timezone(void); static inline usec_t usec_add(usec_t a, usec_t b) { - - /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, and doesn't - * overflow. */ + /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, + * and doesn't overflow. */ if (a > USEC_INFINITY - b) /* overflow check */ return USEC_INFINITY; @@ -164,7 +177,6 @@ static inline usec_t usec_add(usec_t a, usec_t b) { } static inline usec_t usec_sub_unsigned(usec_t timestamp, usec_t delta) { - if (timestamp == USEC_INFINITY) /* Make sure infinity doesn't degrade */ return USEC_INFINITY; if (timestamp < delta) @@ -181,14 +193,14 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) { } #if SIZEOF_TIME_T == 8 -/* 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 ((usec_t) 253402214399000000) + /* 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 ((usec_t) 253402214399000000) #elif SIZEOF_TIME_T == 4 /* With a 32bit time_t we can't go beyond 2038... */ -#define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000) +# define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000) #else -#error "Yuck, time_t is neither 4 nor 8 bytes wide?" +# error "Yuck, time_t is neither 4 nor 8 bytes wide?" #endif int time_change_fd(void); diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.c b/src/libnm-systemd-shared/src/basic/tmpfile-util.c index fd470515..8a6404d3 100644 --- a/src/libnm-systemd-shared/src/basic/tmpfile-util.c +++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.c @@ -305,11 +305,7 @@ int link_tmpfile(int fd, const char *path, const char *target) { if (r < 0) return r; } else { - char proc_fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1]; - - xsprintf(proc_fd_path, "/proc/self/fd/%i", fd); - - if (linkat(AT_FDCWD, proc_fd_path, AT_FDCWD, target, AT_SYMLINK_FOLLOW) < 0) + if (linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), AT_FDCWD, target, AT_SYMLINK_FOLLOW) < 0) return -errno; } diff --git a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h index 96751860..20d8dabf 100644 --- a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h @@ -11,9 +11,26 @@ #define _const_ __attribute__((__const__)) #define _pure_ __attribute__((__pure__)) #define _section_(x) __attribute__((__section__(x))) +#define _packed_ __attribute__((__packed__)) #define _used_ __attribute__((__used__)) #define _unused_ __attribute__((__unused__)) #define _cleanup_(x) __attribute__((__cleanup__(x))) +#define _likely_(x) (__builtin_expect(!!(x), 1)) +#define _unlikely_(x) (__builtin_expect(!!(x), 0)) +#if __GNUC__ >= 7 +#define _fallthrough_ __attribute__((__fallthrough__)) +#else +#define _fallthrough_ +#endif +/* Define C11 noreturn without <stdnoreturn.h> and even on older gcc + * compiler versions */ +#ifndef _noreturn_ +#if __STDC_VERSION__ >= 201112L +#define _noreturn_ _Noreturn +#else +#define _noreturn_ __attribute__((__noreturn__)) +#endif +#endif #define XSTRINGIFY(x) #x #define STRINGIFY(x) XSTRINGIFY(x) @@ -34,7 +51,14 @@ #define CONCATENATE(x, y) XCONCATENATE(x, y) #ifdef SD_BOOT -#define assert(expr) do {} while (false) + #ifdef NDEBUG + #define assert(expr) + #define assert_not_reached() __builtin_unreachable() + #else + void efi_assert(const char *expr, const char *file, unsigned line, const char *function) _noreturn_; + #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); }) + #define assert_not_reached() efi_assert("Code should not be reached", __FILE__, __LINE__, __PRETTY_FUNCTION__) + #endif #endif #if defined(static_assert) @@ -70,12 +94,29 @@ UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \ }) -/* evaluates to (void) if _A or _B are not constant or of different types */ +#define IS_UNSIGNED_INTEGER_TYPE(type) \ + (__builtin_types_compatible_p(typeof(type), unsigned char) || \ + __builtin_types_compatible_p(typeof(type), unsigned short) || \ + __builtin_types_compatible_p(typeof(type), unsigned) || \ + __builtin_types_compatible_p(typeof(type), unsigned long) || \ + __builtin_types_compatible_p(typeof(type), unsigned long long)) + +#define IS_SIGNED_INTEGER_TYPE(type) \ + (__builtin_types_compatible_p(typeof(type), signed char) || \ + __builtin_types_compatible_p(typeof(type), signed short) || \ + __builtin_types_compatible_p(typeof(type), signed) || \ + __builtin_types_compatible_p(typeof(type), signed long) || \ + __builtin_types_compatible_p(typeof(type), signed long long)) + +/* Evaluates to (void) if _A or _B are not constant or of different types (being integers of different sizes + * is also OK as long as the signedness matches) */ #define CONST_MAX(_A, _B) \ (__builtin_choose_expr( \ __builtin_constant_p(_A) && \ __builtin_constant_p(_B) && \ - __builtin_types_compatible_p(typeof(_A), typeof(_B)), \ + (__builtin_types_compatible_p(typeof(_A), typeof(_B)) || \ + (IS_UNSIGNED_INTEGER_TYPE(_A) && IS_UNSIGNED_INTEGER_TYPE(_B)) || \ + (IS_SIGNED_INTEGER_TYPE(_A) && IS_SIGNED_INTEGER_TYPE(_B))), \ ((_A) > (_B)) ? (_A) : (_B), \ VOID_0)) @@ -216,3 +257,10 @@ (ptr) = NULL; \ _ptr_; \ }) + +/* + * STRLEN - return the length of a string literal, minus the trailing NUL byte. + * Contrary to strlen(), this is a constant expression. + * @x: a string literal. + */ +#define STRLEN(x) (sizeof(""x"") - sizeof(typeof(x[0]))) 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 c1abae6c..f1703926 100644 --- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c +++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c @@ -79,6 +79,7 @@ sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) { return (sd_char*) s + sl - pl; } +#if 0 /* NM_IGNORED */ #ifdef SD_BOOT static sd_bool isdigit(sd_char a) { return a >= '0' && a <= '9'; @@ -154,7 +155,7 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) { * Note that except for '~' prefixed segments, a string has more segments is newer. * So, this check must be after the '~' check. */ if (*a == '\0' || *b == '\0') - return strcmp(a, b); + return CMP(*a, *b); /* Handle '-', which separates version and release, e.g 123.4-3.1.fc33.x86_64 */ if (*a == '-' || *b == '-') { @@ -196,9 +197,9 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) { /* Find the leading numeric segments. One may be an empty string. So, * numeric segments are always newer than alpha segments. */ - for (aa = a; *aa != '\0' && isdigit(*aa); aa++) + for (aa = a; isdigit(*aa); aa++) ; - for (bb = b; *bb != '\0' && isdigit(*bb); bb++) + for (bb = b; isdigit(*bb); bb++) ; /* To compare numeric segments without parsing their values, first compare the @@ -213,9 +214,9 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) { return r; } else { /* Find the leading non-numeric segments. */ - for (aa = a; *aa != '\0' && is_alpha(*aa); aa++) + for (aa = a; is_alpha(*aa); aa++) ; - for (bb = b; *bb != '\0' && is_alpha(*bb); bb++) + for (bb = b; is_alpha(*bb); bb++) ; /* Note that the segments are usually not NUL-terminated. */ @@ -234,3 +235,4 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) { b = bb; } } +#endif /* NM_IGNORED */ 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 407cede4..7455c054 100644 --- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h @@ -16,6 +16,7 @@ #define strncmp(a, b, n) StrnCmp((a), (b), (n)) #define strcasecmp(a, b) StriCmp((a), (b)) #define STR_C(str) (L ## str) +#define memcmp(a, b, n) CompareMem(a, b, n) #else #define STR_C(str) (str) #endif @@ -65,3 +66,19 @@ static inline const sd_char *yes_no(sd_bool b) { } sd_int strverscmp_improved(const sd_char *a, const sd_char *b); + +/* Like startswith(), but operates on arbitrary memory blocks */ +static inline void *memory_startswith(const void *p, sd_size_t sz, const sd_char *token) { + assert(token); + + sd_size_t n = strlen(token) * sizeof(sd_char); + if (sz < n) + return NULL; + + assert(p); + + if (memcmp(p, token, n) != 0) + return NULL; + + return (uint8_t*) p + n; +} diff --git a/src/libnm-systemd-shared/src/shared/log-link.h b/src/libnm-systemd-shared/src/shared/log-link.h index 51eaa0c0..5f2b1763 100644 --- a/src/libnm-systemd-shared/src/shared/log-link.h +++ b/src/libnm-systemd-shared/src/shared/log-link.h @@ -17,24 +17,6 @@ log_interface_full_errno_zerook(ifname, level, _error, __VA_ARGS__); \ }) -#define log_interface_prefix_full_errno_zerook(prefix, ifname_expr, error, fmt, ...) \ - ({ \ - int _e = (error); \ - if (DEBUG_LOGGING) \ - log_interface_full_errno_zerook( \ - ifname_expr, \ - LOG_DEBUG, _e, prefix fmt, \ - ##__VA_ARGS__); \ - -ERRNO_VALUE(_e); \ - }) - -#define log_interface_prefix_full_errno(prefix, ifname_expr, error, fmt, ...) \ - ({ \ - int _error = (error); \ - ASSERT_NON_ZERO(_error); \ - log_interface_prefix_full_errno_zerook(prefix, ifname_expr, _error, fmt, ##__VA_ARGS__); \ - }) - /* * The following macros append INTERFACE= to the message. * The macros require a struct named 'Link' which contains 'char *ifname': diff --git a/src/libnm-systemd-shared/src/shared/web-util.c b/src/libnm-systemd-shared/src/shared/web-util.c index 35ba1a2e..85532bdf 100644 --- a/src/libnm-systemd-shared/src/shared/web-util.c +++ b/src/libnm-systemd-shared/src/shared/web-util.c @@ -45,16 +45,29 @@ bool http_etag_is_valid(const char *etag) { /**/ } #if 0 /* NM_IGNORED */ +bool file_url_is_valid(const char *url) { + const char *p; + + if (isempty(url)) + return false; + + p = startswith(url, "file:/"); + if (isempty(p)) + return false; + + return ascii_is_valid(p); +} + bool documentation_url_is_valid(const char *url) { const char *p; if (isempty(url)) return false; - if (http_url_is_valid(url)) + if (http_url_is_valid(url) || file_url_is_valid(url)) return true; - p = STARTSWITH_SET(url, "file:/", "info:", "man:"); + p = STARTSWITH_SET(url, "info:", "man:"); if (isempty(p)) return false; diff --git a/src/libnm-systemd-shared/src/shared/web-util.h b/src/libnm-systemd-shared/src/shared/web-util.h index ec54669f..88b4897b 100644 --- a/src/libnm-systemd-shared/src/shared/web-util.h +++ b/src/libnm-systemd-shared/src/shared/web-util.h @@ -6,6 +6,7 @@ #include "macro.h" bool http_url_is_valid(const char *url) _pure_; +bool file_url_is_valid(const char *url) _pure_; bool documentation_url_is_valid(const char *url) _pure_; |