diff options
| author | Michael Biebl <biebl@debian.org> | 2023-08-09 21:55:35 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2023-08-09 21:55:35 +0200 |
| commit | 05e4a733f2141995181a551854d5df929f084adf (patch) | |
| tree | 83bb937740a6667525ba0df046748ecaa829c269 /src/libnm-systemd-shared | |
| parent | 14b0f3a9dc9ea90d60a3b057350fd4d637dc021a (diff) | |
New upstream version 1.44.0 upstream/1.44.0
Diffstat (limited to 'src/libnm-systemd-shared')
92 files changed, 3530 insertions, 1889 deletions
diff --git a/src/libnm-systemd-shared/meson.build b/src/libnm-systemd-shared/meson.build index d8e7a31a..b32bd7f6 100644 --- a/src/libnm-systemd-shared/meson.build +++ b/src/libnm-systemd-shared/meson.build @@ -22,6 +22,7 @@ libnm_systemd_shared = static_library( 'src/basic/in-addr-util.c', 'src/basic/inotify-util.c', 'src/basic/io-util.c', + 'src/basic/label.c', 'src/basic/locale-util.c', 'src/basic/memory-util.c', 'src/basic/mempool.c', @@ -42,7 +43,6 @@ libnm_systemd_shared = static_library( 'src/basic/time-util.c', 'src/basic/tmpfile-util.c', 'src/basic/utf8.c', - 'src/basic/util.c', 'src/fundamental/sha256.c', 'src/fundamental/string-util-fundamental.c', 'src/shared/dns-domain.c', diff --git a/src/libnm-systemd-shared/sd-adapt-shared/def.h b/src/libnm-systemd-shared/sd-adapt-shared/argv-util.h index 637892c2..637892c2 100644 --- a/src/libnm-systemd-shared/sd-adapt-shared/def.h +++ b/src/libnm-systemd-shared/sd-adapt-shared/argv-util.h diff --git a/src/libnm-systemd-shared/sd-adapt-shared/chase.h b/src/libnm-systemd-shared/sd-adapt-shared/chase.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-shared/sd-adapt-shared/chase.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-shared/sd-adapt-shared/mallinfo-util.h b/src/libnm-systemd-shared/sd-adapt-shared/mallinfo-util.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-shared/sd-adapt-shared/mallinfo-util.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-shared/sd-adapt-shared/memstream-util.h b/src/libnm-systemd-shared/sd-adapt-shared/memstream-util.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-shared/sd-adapt-shared/memstream-util.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-shared/sd-adapt-shared/mountpoint-util.h b/src/libnm-systemd-shared/sd-adapt-shared/mountpoint-util.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-shared/sd-adapt-shared/mountpoint-util.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 ee5a0a24..de6edd02 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 @@ -28,12 +28,14 @@ /*****************************************************************************/ -#ifndef VALGRIND -#define VALGRIND 0 -#endif +#define HAVE_VALGRIND_VALGRIND_H 0 #define ENABLE_DEBUG_HASHMAP 0 +#define SD_BOOT 0 + +#define HAVE_SPLIT_USR 0 + /***************************************************************************** * The remainder of the header is only enabled when building the systemd code * itself. @@ -45,7 +47,7 @@ #include <sys/ioctl.h> #include <pthread.h> -#define ENABLE_GSHADOW FALSE +#define ENABLE_GSHADOW 0 #define HAVE_SECCOMP 0 diff --git a/src/libnm-systemd-shared/sd-adapt-shared/psi-util.h b/src/libnm-systemd-shared/sd-adapt-shared/psi-util.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/src/libnm-systemd-shared/sd-adapt-shared/psi-util.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/src/libnm-systemd-shared/sd-adapt-shared/unaligned-fundamental.h b/src/libnm-systemd-shared/sd-adapt-shared/unaligned-fundamental.h new file mode 100644 index 00000000..97d1609d --- /dev/null +++ b/src/libnm-systemd-shared/sd-adapt-shared/unaligned-fundamental.h @@ -0,0 +1,3 @@ +#pragma once + +#include "libnm-std-aux/unaligned-fundamental.h" diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.c b/src/libnm-systemd-shared/src/basic/alloc-util.c index e252b6ab..c07ab589 100644 --- a/src/libnm-systemd-shared/src/basic/alloc-util.c +++ b/src/libnm-systemd-shared/src/basic/alloc-util.c @@ -104,3 +104,7 @@ void* greedy_realloc0( return q; } + +void *expand_to_usable(void *ptr, size_t newsize _unused_) { + return ptr; +} diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.h b/src/libnm-systemd-shared/src/basic/alloc-util.h index b38db7d4..9a62381d 100644 --- a/src/libnm-systemd-shared/src/basic/alloc-util.h +++ b/src/libnm-systemd-shared/src/basic/alloc-util.h @@ -2,6 +2,7 @@ #pragma once #include <alloca.h> +#include <malloc.h> #include <stddef.h> #include <stdlib.h> #include <string.h> @@ -14,6 +15,7 @@ typedef void (*free_func_t)(void *p); typedef void* (*mfree_func_t)(void *p); +typedef void (*free_array_func_t)(void *p, size_t n); /* If for some reason more than 4M are allocated on the stack, let's abort immediately. It's better than * proceeding and smashing the stack limits. Note that by default RLIMIT_STACK is 8M on Linux. */ @@ -184,17 +186,35 @@ void* greedy_realloc0(void **p, size_t need, size_t size); # define msan_unpoison(r, s) #endif -/* This returns the number of usable bytes in a malloc()ed region as per malloc_usable_size(), in a way that - * is compatible with _FORTIFY_SOURCES. If _FORTIFY_SOURCES is used many memory operations will take the - * object size as returned by __builtin_object_size() into account. Hence, let's return the smaller size of - * malloc_usable_size() and __builtin_object_size() here, so that we definitely operate in safe territory by - * both the compiler's and libc's standards. Note that __builtin_object_size() evaluates to SIZE_MAX if the - * size cannot be determined, hence the MIN() expression should be safe with dynamically sized memory, - * too. Moreover, when NULL is passed malloc_usable_size() is documented to return zero, and - * __builtin_object_size() returns SIZE_MAX too, hence we also return a sensible value of 0 in this corner - * case. */ +/* Dummy allocator to tell the compiler that the new size of p is newsize. The implementation returns the + * pointer as is; the only reason for its existence is as a conduit for the _alloc_ attribute. This must not + * be inlined (hence a non-static function with _noinline_ because LTO otherwise tries to inline it) because + * gcc then loses the attributes on the function. + * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96503 */ +void *expand_to_usable(void *p, size_t newsize) _alloc_(2) _returns_nonnull_ _noinline_; + +static inline size_t malloc_sizeof_safe(void **xp) { + if (_unlikely_(!xp || !*xp)) + return 0; + + size_t sz = malloc_usable_size(*xp); + *xp = expand_to_usable(*xp, sz); + /* GCC doesn't see the _returns_nonnull_ when built with ubsan, so yet another hint to make it doubly + * clear that expand_to_usable won't return NULL. + * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79265 */ + if (!*xp) + assert_not_reached(); + return sz; +} + +/* This returns the number of usable bytes in a malloc()ed region as per malloc_usable_size(), which may + * return a value larger than the size that was actually allocated. Access to that additional memory is + * discouraged because it violates the C standard; a compiler cannot see that this as valid. To help the + * compiler out, the MALLOC_SIZEOF_SAFE macro 'allocates' the usable size using a dummy allocator function + * expand_to_usable. There is a possibility of malloc_usable_size() returning different values during the + * lifetime of an object, which may cause problems, but the glibc allocator does not do that at the moment. */ #define MALLOC_SIZEOF_SAFE(x) \ - MIN(malloc_usable_size(x), __builtin_object_size(x, 0)) + malloc_sizeof_safe((void**) &__builtin_choose_expr(__builtin_constant_p(x), (void*) { NULL }, (x))) /* Inspired by ELEMENTSOF() but operates on malloc()'ed memory areas: typesafely returns the number of items * that fit into the specified memory block */ diff --git a/src/libnm-systemd-shared/src/basic/cgroup-util.h b/src/libnm-systemd-shared/src/basic/cgroup-util.h index df6d5b7b..9b30ae03 100644 --- a/src/libnm-systemd-shared/src/basic/cgroup-util.h +++ b/src/libnm-systemd-shared/src/basic/cgroup-util.h @@ -9,7 +9,7 @@ #include <sys/statfs.h> #include <sys/types.h> -#include "def.h" +#include "constants.h" #include "set.h" #define SYSTEMD_CGROUP_CONTROLLER_LEGACY "name=systemd" @@ -239,7 +239,6 @@ int cg_get_attribute_as_uint64(const char *controller, const char *path, const c /* Does a parse_boolean() on the attribute contents and sets ret accordingly */ int cg_get_attribute_as_bool(const char *controller, const char *path, const char *attribute, bool *ret); -int cg_set_access(const char *controller, const char *path, uid_t uid, gid_t gid); int cg_get_owner(const char *controller, const char *path, uid_t *ret_uid); int cg_set_xattr(const char *controller, const char *path, const char *name, const void *value, size_t size, int flags); @@ -261,6 +260,7 @@ int cg_path_get_cgroupid(const char *path, uint64_t *ret); int cg_path_get_session(const char *path, char **session); int cg_path_get_owner_uid(const char *path, uid_t *uid); int cg_path_get_unit(const char *path, char **unit); +int cg_path_get_unit_path(const char *path, char **unit); int cg_path_get_user_unit(const char *path, char **unit); int cg_path_get_machine_name(const char *path, char **machine); int cg_path_get_slice(const char *path, char **slice); @@ -279,7 +279,8 @@ int cg_pid_get_user_slice(pid_t pid, char **slice); int cg_path_decode_unit(const char *cgroup, char **unit); -char *cg_escape(const char *p); +bool cg_needs_escape(const char *p); +int cg_escape(const char *p, char **ret); char *cg_unescape(const char *p) _pure_; bool cg_controller_is_valid(const char *p); diff --git a/src/libnm-systemd-shared/src/basic/constants.h b/src/libnm-systemd-shared/src/basic/constants.h new file mode 100644 index 00000000..3f96786d --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/constants.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#if !defined(HAS_FEATURE_MEMORY_SANITIZER) +# if defined(__has_feature) +# if __has_feature(memory_sanitizer) +# define HAS_FEATURE_MEMORY_SANITIZER 1 +# endif +# endif +# if !defined(HAS_FEATURE_MEMORY_SANITIZER) +# define HAS_FEATURE_MEMORY_SANITIZER 0 +# endif +#endif + +#if !defined(HAS_FEATURE_ADDRESS_SANITIZER) +# ifdef __SANITIZE_ADDRESS__ +# define HAS_FEATURE_ADDRESS_SANITIZER 1 +# elif defined(__has_feature) +# if __has_feature(address_sanitizer) +# define HAS_FEATURE_ADDRESS_SANITIZER 1 +# endif +# endif +# if !defined(HAS_FEATURE_ADDRESS_SANITIZER) +# define HAS_FEATURE_ADDRESS_SANITIZER 0 +# endif +#endif + +#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC) + +/* Many different things, but also system unit start/stop */ +#define DEFAULT_TIMEOUT_USEC (DEFAULT_TIMEOUT_SEC*USEC_PER_SEC) +/* User unit start/stop */ +#define DEFAULT_USER_TIMEOUT_USEC (DEFAULT_USER_TIMEOUT_SEC*USEC_PER_SEC) +/* Timeout for user confirmation on the console */ +#define DEFAULT_CONFIRM_USEC (30*USEC_PER_SEC) + +/* We use an extra-long timeout for the reload. This is because a reload or reexec means generators are rerun + * which are timed out after DEFAULT_TIMEOUT_USEC. Let's use twice that time here, so that the generators can + * have their timeout, and for everything else there's the same time budget in place. */ +#define DAEMON_RELOAD_TIMEOUT_SEC (DEFAULT_TIMEOUT_USEC * 2) + +#define DEFAULT_START_LIMIT_INTERVAL (10*USEC_PER_SEC) +#define DEFAULT_START_LIMIT_BURST 5 + +/* Wait for 1.5 seconds at maximum for freeze operation */ +#define FREEZE_TIMEOUT (1500 * USEC_PER_MSEC) + +/* The default time after which exit-on-idle services exit. This + * should be kept lower than the watchdog timeout, because otherwise + * the watchdog pings will keep the loop busy. */ +#define DEFAULT_EXIT_USEC (30*USEC_PER_SEC) + +/* The default value for the net.unix.max_dgram_qlen sysctl */ +#define DEFAULT_UNIX_MAX_DGRAM_QLEN 512 + +#define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT +#define SIGNALS_IGNORE SIGPIPE + +#define NOTIFY_FD_MAX 768 +#define NOTIFY_BUFFER_MAX PIPE_BUF + +#if HAVE_SPLIT_USR +# define _CONF_PATHS_SPLIT_USR_NULSTR(n) "/lib/" n "\0" +# define _CONF_PATHS_SPLIT_USR(n) , "/lib/" n +#else +# define _CONF_PATHS_SPLIT_USR_NULSTR(n) +# define _CONF_PATHS_SPLIT_USR(n) +#endif + +/* Return a nulstr for a standard cascade of configuration paths, suitable to pass to + * conf_files_list_nulstr() to implement drop-in directories for extending configuration files. */ +#define CONF_PATHS_NULSTR(n) \ + "/etc/" n "\0" \ + "/run/" n "\0" \ + "/usr/local/lib/" n "\0" \ + "/usr/lib/" n "\0" \ + _CONF_PATHS_SPLIT_USR_NULSTR(n) + +#define CONF_PATHS_USR(n) \ + "/etc/" n, \ + "/run/" n, \ + "/usr/local/lib/" n, \ + "/usr/lib/" n + +#define CONF_PATHS(n) \ + CONF_PATHS_USR(n) \ + _CONF_PATHS_SPLIT_USR(n) + +#define CONF_PATHS_USR_STRV(n) \ + STRV_MAKE(CONF_PATHS_USR(n)) + +#define CONF_PATHS_STRV(n) \ + STRV_MAKE(CONF_PATHS(n)) + +/* The limit for PID 1 itself (which is not inherited to children) */ +#define HIGH_RLIMIT_MEMLOCK (1024ULL*1024ULL*64ULL) + +/* Since kernel 5.16 the kernel default limit was raised to 8M. Let's adjust things on old kernels too, and + * in containers so that our children inherit that. */ +#define DEFAULT_RLIMIT_MEMLOCK (1024ULL*1024ULL*8ULL) + +#define PLYMOUTH_SOCKET { \ + .un.sun_family = AF_UNIX, \ + .un.sun_path = "\0/org/freedesktop/plymouthd", \ + } + +/* Path where PID1 listens for varlink subscriptions from systemd-oomd to notify of changes in ManagedOOM settings. */ +#define VARLINK_ADDR_PATH_MANAGED_OOM_SYSTEM "/run/systemd/io.system.ManagedOOM" +/* Path where systemd-oomd listens for varlink connections from user managers to report changes in ManagedOOM settings. */ +#define VARLINK_ADDR_PATH_MANAGED_OOM_USER "/run/systemd/oom/io.system.ManagedOOM" + +#define KERNEL_BASELINE_VERSION "4.15" diff --git a/src/libnm-systemd-shared/src/basic/env-file.c b/src/libnm-systemd-shared/src/basic/env-file.c index 266e4e79..db270bed 100644 --- a/src/libnm-systemd-shared/src/basic/env-file.c +++ b/src/libnm-systemd-shared/src/basic/env-file.c @@ -14,11 +14,17 @@ #include "tmpfile-util.h" #include "utf8.h" +typedef int (*push_env_func_t)( + const char *filename, + unsigned line, + const char *key, + char *value, + void *userdata); + static int parse_env_file_internal( FILE *f, const char *fname, - int (*push) (const char *filename, unsigned line, - const char *key, char *value, void *userdata), + push_env_func_t push, void *userdata) { size_t n_key = 0, n_value = 0, last_value_whitespace = SIZE_MAX, last_key_whitespace = SIZE_MAX; @@ -39,6 +45,9 @@ static int parse_env_file_internal( COMMENT_ESCAPE } state = PRE_KEY; + assert(f || fname); + assert(push); + if (f) r = read_full_stream(f, &contents, NULL); else @@ -276,6 +285,8 @@ static int check_utf8ness_and_warn( const char *filename, unsigned line, const char *key, char *value) { + assert(key); + if (!utf8_is_valid(key)) { _cleanup_free_ char *p = NULL; @@ -306,6 +317,8 @@ static int parse_env_file_push( va_list aq, *ap = userdata; int r; + assert(key); + r = check_utf8ness_and_warn(filename, line, key, value); if (r < 0) return r; @@ -319,8 +332,7 @@ static int parse_env_file_push( if (streq(key, k)) { va_end(aq); - free(*v); - *v = value; + free_and_replace(*v, value); return 1; } @@ -340,6 +352,26 @@ int parse_env_filev( int r; va_list aq; + assert(f || fname); + + va_copy(aq, ap); + r = parse_env_file_internal(f, fname, parse_env_file_push, &aq); + va_end(aq); + return r; +} + +#if 0 /* NM_IGNORED */ +int parse_env_file_fdv(int fd, const char *fname, va_list ap) { + _cleanup_fclose_ FILE *f = NULL; + va_list aq; + int r; + + assert(fd >= 0); + + r = fdopen_independent(fd, "re", &f); + if (r < 0) + return r; + va_copy(aq, ap); r = parse_env_file_internal(f, fname, parse_env_file_push, &aq); va_end(aq); @@ -354,6 +386,8 @@ int parse_env_file_sentinel( va_list ap; int r; + assert(f || fname); + va_start(ap, fname); r = parse_env_filev(f, fname, ap); va_end(ap); @@ -361,15 +395,34 @@ int parse_env_file_sentinel( return r; } -#if 0 /* NM_IGNORED */ +int parse_env_file_fd_sentinel( + int fd, + const char *fname, /* only used for logging */ + ...) { + + va_list ap; + int r; + + assert(fd >= 0); + + va_start(ap, fname); + r = parse_env_file_fdv(fd, fname, ap); + va_end(ap); + + return r; +} + static int load_env_file_push( const char *filename, unsigned line, const char *key, char *value, void *userdata) { + char ***m = userdata; char *p; int r; + assert(key); + r = check_utf8ness_and_warn(filename, line, key, value); if (r < 0) return r; @@ -386,15 +439,18 @@ static int load_env_file_push( return 0; } -int load_env_file(FILE *f, const char *fname, char ***rl) { +int load_env_file(FILE *f, const char *fname, char ***ret) { _cleanup_strv_free_ char **m = NULL; int r; + assert(f || fname); + assert(ret); + r = parse_env_file_internal(f, fname, load_env_file_push, &m); if (r < 0) return r; - *rl = TAKE_PTR(m); + *ret = TAKE_PTR(m); return 0; } @@ -406,6 +462,8 @@ static int load_env_file_push_pairs( char ***m = ASSERT_PTR(userdata); int r; + assert(key); + r = check_utf8ness_and_warn(filename, line, key, value); if (r < 0) return r; @@ -429,18 +487,34 @@ static int load_env_file_push_pairs( return strv_extend(m, ""); } -int load_env_file_pairs(FILE *f, const char *fname, char ***rl) { +int load_env_file_pairs(FILE *f, const char *fname, char ***ret) { _cleanup_strv_free_ char **m = NULL; int r; + assert(f || fname); + assert(ret); + r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m); if (r < 0) return r; - *rl = TAKE_PTR(m); + *ret = TAKE_PTR(m); return 0; } +int load_env_file_pairs_fd(int fd, const char *fname, char ***ret) { + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(fd >= 0); + + r = fdopen_independent(fd, "re", &f); + if (r < 0) + return r; + + return load_env_file_pairs(f, fname, ret); +} + static int merge_env_file_push( const char *filename, unsigned line, const char *key, char *value, @@ -449,6 +523,8 @@ static int merge_env_file_push( char ***env = ASSERT_PTR(userdata); char *expanded_value; + assert(key); + if (!value) { log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key); return 0; @@ -479,6 +555,9 @@ int merge_env_file( FILE *f, const char *fname) { + assert(env); + assert(f || fname); + /* NOTE: this function supports braceful and braceless variable expansions, * plus "extended" substitutions, unlike other exported parsing functions. */ @@ -489,6 +568,9 @@ int merge_env_file( static void write_env_var(FILE *f, const char *v) { const char *p; + assert(f); + assert(v); + p = strchr(v, '='); if (!p) { /* Fallback */ @@ -517,14 +599,15 @@ static void write_env_var(FILE *f, const char *v) { fputc_unlocked('\n', f); } -int write_env_file(const char *fname, char **l) { +int write_env_file_at(int dir_fd, const char *fname, char **l) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *p = NULL; int r; + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(fname); - r = fopen_temporary(fname, &f, &p); + r = fopen_temporary_at(dir_fd, fname, &f, &p); if (r < 0) return r; @@ -535,13 +618,13 @@ int write_env_file(const char *fname, char **l) { r = fflush_and_check(f); if (r >= 0) { - if (rename(p, fname) >= 0) + if (renameat(dir_fd, p, dir_fd, fname) >= 0) return 0; r = -errno; } - (void) unlink(p); + (void) unlinkat(dir_fd, p, 0); return r; } #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/env-file.h b/src/libnm-systemd-shared/src/basic/env-file.h index de475885..2465eedd 100644 --- a/src/libnm-systemd-shared/src/basic/env-file.h +++ b/src/libnm-systemd-shared/src/basic/env-file.h @@ -1,17 +1,25 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include <fcntl.h> #include <stdarg.h> #include <stdio.h> #include "macro.h" int parse_env_filev(FILE *f, const char *fname, va_list ap); +int parse_env_file_fdv(int fd, const char *fname, va_list ap); int parse_env_file_sentinel(FILE *f, const char *fname, ...) _sentinel_; #define parse_env_file(f, fname, ...) parse_env_file_sentinel(f, fname, __VA_ARGS__, NULL) -int load_env_file(FILE *f, const char *fname, char ***l); -int load_env_file_pairs(FILE *f, const char *fname, char ***l); +int parse_env_file_fd_sentinel(int fd, const char *fname, ...) _sentinel_; +#define parse_env_file_fd(fd, fname, ...) parse_env_file_fd_sentinel(fd, fname, __VA_ARGS__, NULL) +int load_env_file(FILE *f, const char *fname, char ***ret); +int load_env_file_pairs(FILE *f, const char *fname, char ***ret); +int load_env_file_pairs_fd(int fd, const char *fname, char ***ret); int merge_env_file(char ***env, FILE *f, const char *fname); -int write_env_file(const char *fname, char **l); +int write_env_file_at(int dir_fd, const char *fname, char **l); +static inline int write_env_file(const char *fname, char **l) { + return write_env_file_at(AT_FDCWD, fname, l); +} diff --git a/src/libnm-systemd-shared/src/basic/env-util.c b/src/libnm-systemd-shared/src/basic/env-util.c index f58d86a7..fa2753bc 100644 --- a/src/libnm-systemd-shared/src/basic/env-util.c +++ b/src/libnm-systemd-shared/src/basic/env-util.c @@ -139,20 +139,20 @@ bool strv_env_name_or_assignment_is_valid(char **l) { return true; } -static int env_append(char **r, char ***k, char **a) { - assert(r); +static int env_append(char **e, char ***k, char **a) { + assert(e); assert(k); - assert(*k >= r); + assert(*k >= e); if (!a) return 0; - /* Expects the following arguments: 'r' shall point to the beginning of an strv we are going to append to, 'k' + /* Expects the following arguments: 'e' shall point to the beginning of an strv we are going to append to, 'k' * to a pointer pointing to the NULL entry at the end of the same array. 'a' shall point to another strv. * - * This call adds every entry of 'a' to 'r', either overriding an existing matching entry, or appending to it. + * This call adds every entry of 'a' to 'e', either overriding an existing matching entry, or appending to it. * - * This call assumes 'r' has enough pre-allocated space to grow by all of 'a''s items. */ + * This call assumes 'e' has enough pre-allocated space to grow by all of 'a''s items. */ for (; *a; a++) { char **j, *c; @@ -162,7 +162,7 @@ static int env_append(char **r, char ***k, char **a) { if ((*a)[n] == '=') n++; - for (j = r; j < *k; j++) + for (j = e; j < *k; j++) if (strneq(*j, *a, n)) break; @@ -269,7 +269,7 @@ static bool env_entry_has_name(const char *entry, const char *name) { char **strv_env_delete(char **x, size_t n_lists, ...) { size_t n, i = 0; - char **r; + _cleanup_strv_free_ char **t = NULL; va_list ap; /* Deletes every entry from x that is mentioned in the other @@ -277,8 +277,8 @@ char **strv_env_delete(char **x, size_t n_lists, ...) { n = strv_length(x); - r = new(char*, n+1); - if (!r) + t = new(char*, n+1); + if (!t) return NULL; STRV_FOREACH(k, x) { @@ -293,11 +293,9 @@ char **strv_env_delete(char **x, size_t n_lists, ...) { } va_end(ap); - r[i] = strdup(*k); - if (!r[i]) { - strv_free(r); + t[i] = strdup(*k); + if (!t[i]) return NULL; - } i++; continue; @@ -306,11 +304,11 @@ char **strv_env_delete(char **x, size_t n_lists, ...) { va_end(ap); } - r[i] = NULL; + t[i] = NULL; assert(i <= n); - return r; + return TAKE_PTR(t); } char **strv_env_unset(char **l, const char *p) { @@ -462,6 +460,48 @@ int strv_env_assign(char ***l, const char *key, const char *value) { return strv_env_replace_consume(l, p); } +int _strv_env_assign_many(char ***l, ...) { + va_list ap; + int r; + + assert(l); + + va_start(ap, l); + for (;;) { + const char *key, *value; + + key = va_arg(ap, const char *); + if (!key) + break; + + if (!env_name_is_valid(key)) { + va_end(ap); + return -EINVAL; + } + + value = va_arg(ap, const char *); + if (!value) { + strv_env_unset(*l, key); + continue; + } + + char *p = strjoin(key, "=", value); + if (!p) { + va_end(ap); + return -ENOMEM; + } + + r = strv_env_replace_consume(l, p); + if (r < 0) { + va_end(ap); + return r; + } + } + va_end(ap); + + return 0; +} + char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) { assert(name); @@ -488,6 +528,7 @@ char *strv_env_get(char **l, const char *name) { return strv_env_get_n(l, name, strlen(name), 0); } +#endif /* NM_IGNORED */ char *strv_env_pairs_get(char **l, const char *name) { char *result = NULL; @@ -501,6 +542,7 @@ char *strv_env_pairs_get(char **l, const char *name) { return result; } +#if 0 /* NM_IGNORED */ char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { int k = 0; @@ -549,7 +591,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { const char *e, *word = format, *test_value = NULL; /* test_value is initialized to appease gcc */ char *k; - _cleanup_free_ char *r = NULL; + _cleanup_free_ char *s = NULL; size_t i, len = 0; /* len is initialized to appease gcc */ int nest = 0; @@ -565,31 +607,31 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { case CURLY: if (*e == '{') { - k = strnappend(r, word, e-word-1); + k = strnappend(s, word, e-word-1); if (!k) return NULL; - free_and_replace(r, k); + free_and_replace(s, k); word = e-1; state = VARIABLE; nest++; } else if (*e == '$') { - k = strnappend(r, word, e-word); + k = strnappend(s, word, e-word); if (!k) return NULL; - free_and_replace(r, k); + free_and_replace(s, k); word = e+1; state = WORD; } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) { - k = strnappend(r, word, e-word-1); + k = strnappend(s, word, e-word-1); if (!k) return NULL; - free_and_replace(r, k); + free_and_replace(s, k); word = e-1; state = VARIABLE_RAW; @@ -604,7 +646,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { t = strv_env_get_n(env, word+2, e-word-2, flags); - if (!strextend(&r, t)) + if (!strextend(&s, t)) return NULL; word = e+1; @@ -657,7 +699,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { else if (!t && state == DEFAULT_VALUE) t = v = replace_env_n(test_value, e-test_value, env, flags); - if (!strextend(&r, t)) + if (!strextend(&s, t)) return NULL; word = e+1; @@ -673,7 +715,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { t = strv_env_get_n(env, word+1, e-word-1, flags); - if (!strextend(&r, t)) + if (!strextend(&s, t)) return NULL; word = e--; @@ -689,13 +731,13 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { assert(flags & REPLACE_ENV_ALLOW_BRACELESS); t = strv_env_get_n(env, word+1, e-word-1, flags); - return strjoin(r, t); + return strjoin(s, t); } else - return strnappend(r, word, e-word); + return strnappend(s, word, e-word); } char **replace_env_argv(char **argv, char **env) { - char **ret; + _cleanup_strv_free_ char **ret = NULL; size_t k = 0, l = 0; l = strv_length(argv); @@ -709,7 +751,8 @@ char **replace_env_argv(char **argv, char **env) { /* If $FOO appears as single word, replace it by the split up variable */ if ((*i)[0] == '$' && !IN_SET((*i)[1], '{', '$')) { char *e; - char **w, **m = NULL; + char **w; + _cleanup_strv_free_ char **m = NULL; size_t q; e = strv_env_get(env, *i+1); @@ -719,11 +762,9 @@ char **replace_env_argv(char **argv, char **env) { r = strv_split_full(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE); if (r < 0) { ret[k] = NULL; - strv_free(ret); return NULL; } - } else - m = NULL; + } q = strv_length(m); l = l + q - 1; @@ -731,15 +772,13 @@ char **replace_env_argv(char **argv, char **env) { w = reallocarray(ret, l + 1, sizeof(char *)); if (!w) { ret[k] = NULL; - strv_free(ret); - strv_free(m); return NULL; } ret = w; if (m) { memcpy(ret + k, m, q * sizeof(char*)); - free(m); + m = mfree(m); } k += q; @@ -748,15 +787,13 @@ char **replace_env_argv(char **argv, char **env) { /* If ${FOO} appears as part of a word, replace it by the variable as-is */ ret[k] = replace_env(*i, env, 0); - if (!ret[k]) { - strv_free(ret); + if (!ret[k]) return NULL; - } k++; } ret[k] = NULL; - return ret; + return TAKE_PTR(ret); } #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 b927ac7a..b0ff5a11 100644 --- a/src/libnm-systemd-shared/src/basic/env-util.h +++ b/src/libnm-systemd-shared/src/basic/env-util.h @@ -49,6 +49,8 @@ int strv_env_replace_consume(char ***l, char *p); /* In place ... */ int strv_env_replace_strdup(char ***l, const char *assignment); int strv_env_replace_strdup_passthrough(char ***l, const char *assignment); int strv_env_assign(char ***l, const char *key, const char *value); +int _strv_env_assign_many(char ***l, ...) _sentinel_; +#define strv_env_assign_many(l, ...) _strv_env_assign_many(l, __VA_ARGS__, NULL) char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_; char *strv_env_get(char **x, const char *n) _pure_; diff --git a/src/libnm-systemd-shared/src/basic/escape.c b/src/libnm-systemd-shared/src/basic/escape.c index cc317dfb..6d2c1d4d 100644 --- a/src/libnm-systemd-shared/src/basic/escape.c +++ b/src/libnm-systemd-shared/src/basic/escape.c @@ -448,44 +448,51 @@ char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFl } char* octescape(const char *s, size_t len) { - char *r, *t; - const char *f; + char *buf, *t; - /* Escapes all chars in bad, in addition to \ and " chars, - * in \nnn style escaping. */ + /* Escapes all chars in bad, in addition to \ and " chars, in \nnn style escaping. */ - r = new(char, len * 4 + 1); - if (!r) + assert(s || len == 0); + + t = buf = new(char, len * 4 + 1); + if (!buf) return NULL; - for (f = s, t = r; f < s + len; f++) { + for (size_t i = 0; i < len; i++) { + uint8_t u = (uint8_t) s[i]; - if (*f < ' ' || *f >= 127 || IN_SET(*f, '\\', '"')) { + if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"')) { *(t++) = '\\'; - *(t++) = '0' + (*f >> 6); - *(t++) = '0' + ((*f >> 3) & 8); - *(t++) = '0' + (*f & 8); + *(t++) = '0' + (u >> 6); + *(t++) = '0' + ((u >> 3) & 7); + *(t++) = '0' + (u & 7); } else - *(t++) = *f; + *(t++) = u; } *t = 0; - - return r; - + return buf; } static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) { assert(bad); + assert(t); + assert(s); - for (; *s; s++) - if (char_is_cc(*s)) - t += cescape_char(*s, t); - else { + while (*s) { + int l = utf8_encoded_valid_unichar(s, SIZE_MAX); + + if (char_is_cc(*s) || l < 0) + t += cescape_char(*(s++), t); + else if (l == 1) { if (*s == '\\' || strchr(bad, *s)) *(t++) = '\\'; - *(t++) = *s; + *(t++) = *(s++); + } else { + t = mempcpy(t, s, l); + s += l; } + } return t; } @@ -514,11 +521,16 @@ char* shell_maybe_quote(const char *s, ShellEscapeFlags flags) { if (FLAGS_SET(flags, SHELL_ESCAPE_EMPTY) && isempty(s)) return strdup("\"\""); /* We don't use $'' here in the POSIX mode. "" is fine too. */ - for (p = s; *p; p++) - if (char_is_cc(*p) || + for (p = s; *p; ) { + int l = utf8_encoded_valid_unichar(p, SIZE_MAX); + + if (char_is_cc(*p) || l < 0 || strchr(WHITESPACE SHELL_NEED_QUOTES, *p)) break; + p += l; + } + if (!*p) return strdup(s); diff --git a/src/libnm-systemd-shared/src/basic/fd-util.c b/src/libnm-systemd-shared/src/basic/fd-util.c index 61a5bb78..a0e2f4eb 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.c +++ b/src/libnm-systemd-shared/src/basic/fd-util.c @@ -23,6 +23,7 @@ #include "missing_fcntl.h" #include "missing_fs.h" #include "missing_syscall.h" +#include "mountpoint-util.h" #include "parse-util.h" #include "path-util.h" #include "process-util.h" @@ -31,7 +32,6 @@ #include "stat-util.h" #include "stdio-util.h" #include "tmpfile-util.h" -#include "util.h" /* The maximum number of iterations in the loop to close descriptors in the fallback case * when /proc/self/fd/ is inaccessible. */ @@ -59,11 +59,9 @@ int close_nointr(int fd) { } int safe_close(int fd) { - /* - * Like close_nointr() but cannot fail. Guarantees errno is - * unchanged. Is a NOP with negative fds passed, and returns - * -1, so that it can be used in this syntax: + * Like close_nointr() but cannot fail. Guarantees errno is unchanged. Is a noop for negative fds, + * and returns -EBADF, so that it can be used in this syntax: * * fd = safe_close(fd); */ @@ -79,7 +77,7 @@ int safe_close(int fd) { assert_se(close_nointr(fd) != -EBADF); } - return -1; + return -EBADF; } void safe_close_pair(int p[static 2]) { @@ -177,12 +175,35 @@ int fd_cloexec(int fd, bool cloexec) { } #if 0 /* NM_IGNORED */ +int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec) { + int ret = 0, r; + + assert(n_fds == 0 || fds); + + for (size_t i = 0; i < n_fds; i++) { + if (fds[i] < 0) /* Skip gracefully over already invalidated fds */ + continue; + + r = fd_cloexec(fds[i], cloexec); + if (r < 0 && ret >= 0) /* Continue going, but return first error */ + ret = r; + else + ret = 1; /* report if we did anything */ + } + + return ret; +} + _pure_ static bool fd_in_set(int fd, const int fdset[], size_t n_fdset) { assert(n_fdset == 0 || fdset); - for (size_t i = 0; i < n_fdset; i++) + for (size_t i = 0; i < n_fdset; i++) { + if (fdset[i] < 0) + continue; + if (fdset[i] == fd) return true; + } return false; } @@ -229,7 +250,7 @@ static int close_all_fds_frugal(const int except[], size_t n_except) { "Refusing to loop over %d potential fds.", max_fd); - for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) { + for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -EBADF) { int q; if (fd_in_set(fd, except, n_except)) @@ -255,6 +276,10 @@ static int close_all_fds_special_case(const int except[], size_t n_except) { if (!have_close_range) return 0; + if (n_except == 1 && except[0] < 0) /* Minor optimization: if we only got one fd, and it's invalid, + * we got none */ + n_except = 0; + switch (n_except) { case 0: @@ -389,12 +414,13 @@ int close_all_fds(const int except[], size_t n_except) { return close_all_fds_frugal(except, n_except); /* ultimate fallback if /proc/ is not available */ FOREACH_DIRENT(de, d, return -errno) { - int fd = -1, q; + int fd = -EBADF, q; if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN)) continue; - if (safe_atoi(de->d_name, &fd) < 0) + fd = parse_fd(de->d_name); + if (fd < 0) /* Let's better ignore this, just in case */ continue; @@ -479,7 +505,8 @@ void cmsg_close_all(struct msghdr *mh) { CMSG_FOREACH(cmsg, mh) if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) - close_many((int*) ((void*) CMSG_DATA(cmsg)), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int)); + close_many(CMSG_TYPED_DATA(cmsg, int), + (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int)); } bool fdname_is_valid(const char *s) { @@ -513,6 +540,11 @@ bool fdname_is_valid(const char *s) { int fd_get_path(int fd, char **ret) { int r; + assert(fd >= 0 || fd == AT_FDCWD); + + if (fd == AT_FDCWD) + return safe_getcwd(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 @@ -611,25 +643,23 @@ int fd_move_above_stdio(int fd) { #if 0 /* NM_IGNORED */ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd) { - - int fd[3] = { /* Put together an array of fds we work on */ - original_input_fd, - original_output_fd, - original_error_fd - }; - - int r, i, - null_fd = -1, /* if we open /dev/null, we store the fd to it here */ - copy_fd[3] = { -1, -1, -1 }; /* This contains all fds we duplicate here temporarily, and hence need to close at the end */ + int fd[3] = { original_input_fd, /* Put together an array of fds we work on */ + original_output_fd, + original_error_fd }, + null_fd = -EBADF, /* If we open /dev/null, we store the fd to it here */ + copy_fd[3] = { -EBADF, -EBADF, -EBADF }, /* This contains all fds we duplicate here + * temporarily, and hence need to close at the end. */ + r; bool null_readable, null_writable; - /* Sets up stdin, stdout, stderr with the three file descriptors passed in. If any of the descriptors is - * specified as -1 it will be connected with /dev/null instead. If any of the file descriptors is passed as - * itself (e.g. stdin as STDIN_FILENO) it is left unmodified, but the O_CLOEXEC bit is turned off should it be - * on. + /* Sets up stdin, stdout, stderr with the three file descriptors passed in. If any of the descriptors + * is specified as -EBADF it will be connected with /dev/null instead. If any of the file descriptors + * is passed as itself (e.g. stdin as STDIN_FILENO) it is left unmodified, but the O_CLOEXEC bit is + * turned off should it be on. * - * Note that if any of the passed file descriptors are > 2 they will be closed — both on success and on - * failure! Thus, callers should assume that when this function returns the input fds are invalidated. + * Note that if any of the passed file descriptors are > 2 they will be closed — both on success and + * on failure! Thus, callers should assume that when this function returns the input fds are + * invalidated. * * Note that when this function fails stdin/stdout/stderr might remain half set up! * @@ -665,7 +695,7 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_ } /* Let's assemble fd[] with the fds to install in place of stdin/stdout/stderr */ - for (i = 0; i < 3; i++) { + for (int i = 0; i < 3; i++) { if (fd[i] < 0) fd[i] = null_fd; /* A negative parameter means: connect this one to /dev/null */ @@ -681,10 +711,10 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_ } } - /* At this point we now have the fds to use in fd[], and they are all above the stdio range, so that we - * have freedom to move them around. If the fds already were at the right places then the specific fds are - * -1. Let's now move them to the right places. This is the point of no return. */ - for (i = 0; i < 3; i++) { + /* At this point we now have the fds to use in fd[], and they are all above the stdio range, so that + * we have freedom to move them around. If the fds already were at the right places then the specific + * fds are -EBADF. Let's now move them to the right places. This is the point of no return. */ + for (int i = 0; i < 3; i++) { if (fd[i] == i) { @@ -715,7 +745,7 @@ finish: safe_close_above_stdio(original_error_fd); /* Close the copies we moved > 2 */ - for (i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) safe_close(copy_fd[i]); /* Close our null fd, if it's > 2 */ @@ -723,27 +753,47 @@ finish: return r; } +#endif /* NM_IGNORED */ int fd_reopen(int fd, int flags) { int new_fd, r; + assert(fd >= 0 || fd == AT_FDCWD); + /* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to * turn O_RDWR fds into O_RDONLY fds. * * This doesn't work on sockets (since they cannot be open()ed, ever). * - * This implicitly resets the file read index to 0. */ - - if (FLAGS_SET(flags, O_DIRECTORY)) { + * This implicitly resets the file read index to 0. + * + * If AT_FDCWD is specified as file descriptor gets an fd to the current cwd. + * + * If the specified file descriptor refers to a symlink via O_PATH, then this function cannot be used + * to follow that symlink. Because we cannot have non-O_PATH fds to symlinks reopening it without + * O_PATH will always result in -ELOOP. Or in other words: if you have an O_PATH fd to a symlink you + * can reopen it only if you pass O_PATH again. */ + + if (FLAGS_SET(flags, O_NOFOLLOW)) + /* O_NOFOLLOW is not allowed in fd_reopen(), because after all this is primarily implemented + * via a symlink-based interface in /proc/self/fd. Let's refuse this here early. Note that + * the kernel would generate ELOOP here too, hence this manual check is mostly redundant – + * the only reason we add it here is so that the O_DIRECTORY special case (see below) behaves + * the same way as the non-O_DIRECTORY case. */ + return -ELOOP; + + if (FLAGS_SET(flags, O_DIRECTORY) || fd == AT_FDCWD) { /* If we shall reopen the fd as directory we can just go via "." and thus bypass the whole * magic /proc/ directory, and make ourselves independent of that being mounted. */ - new_fd = openat(fd, ".", flags); + new_fd = openat(fd, ".", flags | O_DIRECTORY); if (new_fd < 0) return -errno; return new_fd; } + assert(fd >= 0); + new_fd = open(FORMAT_PROC_FD_PATH(fd), flags); if (new_fd < 0) { if (errno != ENOENT) @@ -761,6 +811,50 @@ int fd_reopen(int fd, int flags) { return new_fd; } +#if 0 /* NM_IGNORED */ +int fd_reopen_condition( + int fd, + int flags, + int mask, + int *ret_new_fd) { + + int r, new_fd; + + assert(fd >= 0); + + /* Invokes fd_reopen(fd, flags), but only if the existing F_GETFL flags don't match the specified + * flags (masked by the specified mask). This is useful for converting O_PATH fds into real fds if + * needed, but only then. */ + + r = fcntl(fd, F_GETFL); + if (r < 0) + return -errno; + + if ((r & mask) == (flags & mask)) { + *ret_new_fd = -EBADF; + return fd; + } + + new_fd = fd_reopen(fd, flags); + if (new_fd < 0) + return new_fd; + + *ret_new_fd = new_fd; + return new_fd; +} + +int fd_is_opath(int fd) { + int r; + + assert(fd >= 0); + + r = fcntl(fd, F_GETFL); + if (r < 0) + return -errno; + + return FLAGS_SET(r, O_PATH); +} + int read_nr_open(void) { _cleanup_free_ char *nr_open = NULL; int r; @@ -805,4 +899,89 @@ int fd_get_diskseq(int fd, uint64_t *ret) { return 0; } + +int path_is_root_at(int dir_fd, const char *path) { + STRUCT_NEW_STATX_DEFINE(st); + STRUCT_NEW_STATX_DEFINE(pst); + _cleanup_close_ int fd = -EBADF; + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + + if (!isempty(path)) { + fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); + if (fd < 0) + return -errno; + + dir_fd = fd; + } + + r = statx_fallback(dir_fd, ".", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st.sx); + if (r == -ENOTDIR) + return false; + if (r < 0) + return r; + + r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx); + if (r < 0) + return r; + + /* First, compare inode. If these are different, the fd does not point to the root directory "/". */ + if (!statx_inode_same(&st.sx, &pst.sx)) + return false; + + /* Even if the parent directory has the same inode, the fd may not point to the root directory "/", + * and we also need to check that the mount ids are the same. Otherwise, a construct like the + * following could be used to trick us: + * + * $ mkdir /tmp/x /tmp/x/y + * $ mount --bind /tmp/x /tmp/x/y + * + * Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old + * kernel is used without /proc mounted. In that case, let's assume that we do not have such spurious + * mount points in an early boot stage, and silently skip the following check. */ + + if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) { + int mntid; + + r = path_get_mnt_id_at(dir_fd, "", &mntid); + if (r == -ENOSYS) + return true; /* skip the mount ID check */ + if (r < 0) + return r; + assert(mntid >= 0); + + st.nsx.stx_mnt_id = mntid; + st.nsx.stx_mask |= STATX_MNT_ID; + } + + if (!FLAGS_SET(pst.nsx.stx_mask, STATX_MNT_ID)) { + int mntid; + + r = path_get_mnt_id_at(dir_fd, "..", &mntid); + if (r == -ENOSYS) + return true; /* skip the mount ID check */ + if (r < 0) + return r; + assert(mntid >= 0); + + pst.nsx.stx_mnt_id = mntid; + pst.nsx.stx_mask |= STATX_MNT_ID; + } + + return statx_mount_same(&st.nsx, &pst.nsx); +} + +const char *accmode_to_string(int flags) { + switch (flags & O_ACCMODE) { + case O_RDONLY: + return "ro"; + case O_WRONLY: + return "wo"; + case O_RDWR: + return "rw"; + default: + return NULL; + } +} #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 d9896e27..c870a1b8 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.h +++ b/src/libnm-systemd-shared/src/basic/fd-util.h @@ -2,6 +2,7 @@ #pragma once #include <dirent.h> +#include <fcntl.h> #include <stdbool.h> #include <stdio.h> #include <sys/socket.h> @@ -15,14 +16,15 @@ /* Make sure we can distinguish fd 0 and NULL */ #define FD_TO_PTR(fd) INT_TO_PTR((fd)+1) #define PTR_TO_FD(p) (PTR_TO_INT(p)-1) +#define PIPE_EBADF { -EBADF, -EBADF } int close_nointr(int fd); int safe_close(int fd); void safe_close_pair(int p[static 2]); static inline int safe_close_above_stdio(int fd) { - if (fd < 3) /* Don't close stdin/stdout/stderr, but still invalidate the fd by returning -1 */ - return -1; + if (fd < 3) /* Don't close stdin/stdout/stderr, but still invalidate the fd by returning -EBADF. */ + return -EBADF; return safe_close(fd); } @@ -56,6 +58,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL); int fd_nonblock(int fd, bool nonblock); int fd_cloexec(int fd, bool cloexec); +int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec); int get_max_fd(void); @@ -72,30 +75,16 @@ int fd_get_path(int fd, char **ret); int move_fd(int from, int to, int cloexec); -enum { - ACQUIRE_NO_DEV_NULL = 1 << 0, - ACQUIRE_NO_MEMFD = 1 << 1, - ACQUIRE_NO_PIPE = 1 << 2, - ACQUIRE_NO_TMPFILE = 1 << 3, - ACQUIRE_NO_REGULAR = 1 << 4, -}; - int fd_move_above_stdio(int fd); int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd); static inline int make_null_stdio(void) { - return rearrange_stdio(-1, -1, -1); + return rearrange_stdio(-EBADF, -EBADF, -EBADF); } -/* Like TAKE_PTR() but for file descriptors, resetting them to -1 */ -#define TAKE_FD(fd) \ - ({ \ - int *_fd_ = &(fd); \ - int _ret_ = *_fd_; \ - *_fd_ = -1; \ - _ret_; \ - }) +/* Like TAKE_PTR() but for file descriptors, resetting them to -EBADF */ +#define TAKE_FD(fd) TAKE_GENERIC(fd, int, -EBADF) /* Like free_and_replace(), but for file descriptors */ #define close_and_replace(a, b) \ @@ -107,9 +96,19 @@ static inline int make_null_stdio(void) { }) int fd_reopen(int fd, int flags); +int fd_reopen_condition(int fd, int flags, int mask, int *ret_new_fd); +int fd_is_opath(int fd); int read_nr_open(void); int fd_get_diskseq(int fd, uint64_t *ret); +int path_is_root_at(int dir_fd, const char *path); +static inline int dir_fd_is_root(int dir_fd) { + return path_is_root_at(dir_fd, NULL); +} +static inline int dir_fd_is_root_or_cwd(int dir_fd) { + return dir_fd == AT_FDCWD ? true : path_is_root_at(dir_fd, NULL); +} + /* The maximum length a buffer for a /proc/self/fd/<fd> path needs */ #define PROC_FD_PATH_MAX \ (STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)) @@ -123,3 +122,13 @@ static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int f #define FORMAT_PROC_FD_PATH(fd) \ format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd)) + +const char *accmode_to_string(int flags); + +/* Like ASSERT_PTR, but for fds */ +#define ASSERT_FD(fd) \ + ({ \ + int _fd_ = (fd); \ + assert(_fd_ >= 0); \ + _fd_; \ + }) diff --git a/src/libnm-systemd-shared/src/basic/fileio.c b/src/libnm-systemd-shared/src/basic/fileio.c index d996bc02..908a0309 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.c +++ b/src/libnm-systemd-shared/src/basic/fileio.c @@ -15,7 +15,7 @@ #include <unistd.h> #include "alloc-util.h" -#include "chase-symlinks.h" +#include "chase.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -23,6 +23,7 @@ #include "log.h" #include "macro.h" #include "mkdir.h" +#include "nulstr-util.h" #include "parse-util.h" #include "path-util.h" #include "socket-util.h" @@ -45,19 +46,6 @@ * 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); - - FILE *f = fopen(path, options); - if (!f) - return -errno; - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); - - *ret = f; - return 0; -} - int fdopen_unlocked(int fd, const char *options, FILE **ret) { assert(ret); @@ -80,7 +68,7 @@ int take_fdopen_unlocked(int *fd, const char *options, FILE **ret) { if (r < 0) return r; - *fd = -1; + *fd = -EBADF; return 0; } @@ -92,7 +80,7 @@ FILE* take_fdopen(int *fd, const char *options) { if (!f) return NULL; - *fd = -1; + *fd = -EBADF; return f; } @@ -104,7 +92,7 @@ DIR* take_fdopendir(int *dfd) { if (!d) return NULL; - *dfd = -1; + *dfd = -EBADF; return d; } @@ -137,7 +125,7 @@ int write_string_stream_ts( const struct timespec *ts) { bool needs_nl; - int r, fd = -1; + int r, fd = -EBADF; assert(f); assert(line); @@ -212,7 +200,8 @@ int write_string_stream_ts( return 0; } -static int write_string_file_atomic( +static int write_string_file_atomic_at( + int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags, @@ -228,7 +217,7 @@ static int write_string_file_atomic( /* Note that we'd really like to use O_TMPFILE here, but can't really, since we want replacement * semantics here, and O_TMPFILE can't offer that. i.e. rename() replaces but linkat() doesn't. */ - r = fopen_temporary(fn, &f, &p); + r = fopen_temporary_at(dir_fd, fn, &f, &p); if (r < 0) return r; @@ -240,7 +229,7 @@ static int write_string_file_atomic( if (r < 0) goto fail; - if (rename(p, fn) < 0) { + if (renameat(dir_fd, p, dir_fd, fn) < 0) { r = -errno; goto fail; } @@ -255,18 +244,20 @@ static int write_string_file_atomic( return 0; fail: - (void) unlink(p); + (void) unlinkat(dir_fd, p, 0); return r; } -int write_string_file_ts( +int write_string_file_ts_at( + int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts) { _cleanup_fclose_ FILE *f = NULL; - int q, r, fd; + _cleanup_close_ int fd = -EBADF; + int q, r; assert(fn); assert(line); @@ -275,7 +266,7 @@ int write_string_file_ts( assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC))); if (flags & WRITE_STRING_FILE_MKDIR_0755) { - r = mkdir_parents(fn, 0755); + r = mkdirat_parents(dir_fd, fn, 0755); if (r < 0) return r; } @@ -283,7 +274,7 @@ int write_string_file_ts( if (flags & WRITE_STRING_FILE_ATOMIC) { assert(flags & WRITE_STRING_FILE_CREATE); - r = write_string_file_atomic(fn, line, flags, ts); + r = write_string_file_atomic_at(dir_fd, fn, line, flags, ts); if (r < 0) goto fail; @@ -292,22 +283,20 @@ 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_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_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY), - (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666)); + fd = openat(dir_fd, 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_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY), + (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666)); if (fd < 0) { r = -errno; goto fail; } - r = fdopen_unlocked(fd, "w", &f); - if (r < 0) { - safe_close(fd); + r = take_fdopen_unlocked(&fd, "w", &f); + if (r < 0) goto fail; - } if (flags & WRITE_STRING_FILE_DISABLE_BUFFER) setvbuf(f, NULL, _IONBF, 0); @@ -353,21 +342,22 @@ int write_string_filef( return write_string_file(fn, p, flags); } -int read_one_line_file(const char *fn, char **line) { +int read_one_line_file_at(int dir_fd, const char *filename, char **ret) { _cleanup_fclose_ FILE *f = NULL; int r; - assert(fn); - assert(line); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(filename); + assert(ret); - r = fopen_unlocked(fn, "re", &f); + r = fopen_unlocked_at(dir_fd, filename, "re", 0, &f); if (r < 0) return r; - return read_line(f, LONG_LINE_MAX, line); + return read_line(f, LONG_LINE_MAX, ret); } -int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { +int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *buf = NULL; size_t l, k; @@ -385,7 +375,7 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { if (!buf) return -ENOMEM; - r = fopen_unlocked(fn, "re", &f); + r = fopen_unlocked_at(dir_fd, fn, "re", 0, &f); if (r < 0) return r; @@ -504,7 +494,7 @@ int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *r * 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).*/ + * we have one free byte in it for the trailing NUL we add below). */ truncated = true; break; } @@ -558,7 +548,7 @@ int read_virtual_file_at( char **ret_contents, size_t *ret_size) { - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); @@ -760,62 +750,19 @@ int read_full_file_full( size_t *ret_size) { _cleanup_fclose_ FILE *f = NULL; + XfopenFlags xflags = XFOPEN_UNLOCKED; int r; assert(filename); assert(ret_contents); - r = xfopenat(dir_fd, filename, "re", 0, &f); - if (r < 0) { - _cleanup_close_ int sk = -1; - - /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */ - if (r != -ENXIO) - return r; - - /* If this is enabled, let's try to connect to it */ - if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET)) - return -ENXIO; - - /* Seeking is not supported on AF_UNIX sockets */ - if (offset != UINT64_MAX) - return -ENXIO; + if (FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET) && /* If this is enabled, let's try to connect to it */ + offset == UINT64_MAX) /* Seeking is not supported on AF_UNIX sockets */ + xflags |= XFOPEN_SOCKET; - sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); - if (sk < 0) - return -errno; - - if (bind_name) { - /* If the caller specified a socket name to bind to, do so before connecting. This is - * useful to communicate some minor, short meta-information token from the client to - * the server. */ - union sockaddr_union bsa; - - r = sockaddr_un_set_path(&bsa.un, bind_name); - if (r < 0) - return r; - - if (bind(sk, &bsa.sa, r) < 0) - return -errno; - } - - r = connect_unix_path(sk, dir_fd, filename); - if (IN_SET(r, -ENOTSOCK, -EINVAL)) /* propagate original error if this is not a socket after all */ - return -ENXIO; - if (r < 0) - return r; - - if (shutdown(sk, SHUT_WR) < 0) - return -errno; - - f = fdopen(sk, "r"); - if (!f) - return -errno; - - TAKE_FD(sk); - } - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + r = xfopenat_full(dir_fd, filename, "re", 0, xflags, bind_name, &f); + if (r < 0) + return r; return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size); } @@ -851,7 +798,6 @@ int executable_is_script(const char *path, char **interpreter) { *interpreter = ans; return 1; } -#endif /* NM_IGNORED */ /** * Retrieve one field from a file like /proc/self/status. pattern @@ -864,7 +810,6 @@ int executable_is_script(const char *path, char **interpreter) { int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field) { _cleanup_free_ char *status = NULL; char *t, *f; - size_t len; int r; assert(terminator); @@ -916,9 +861,7 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin t--; } - len = strcspn(t, terminator); - - f = strndup(t, len); + f = strdupcspn(t, terminator); if (!f) return -ENOMEM; @@ -927,8 +870,7 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin } DIR *xopendirat(int fd, const char *name, int flags) { - int nfd; - DIR *d; + _cleanup_close_ int nfd = -EBADF; assert(!(flags & O_CREAT)); @@ -939,14 +881,9 @@ DIR *xopendirat(int fd, const char *name, int flags) { if (nfd < 0) return NULL; - d = fdopendir(nfd); - if (!d) { - safe_close(nfd); - return NULL; - } - - return d; + return take_fdopendir(&nfd); } +#endif /* NM_IGNORED */ int fopen_mode_to_flags(const char *mode) { const char *p; @@ -994,38 +931,144 @@ int fopen_mode_to_flags(const char *mode) { return flags; } -int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) { +static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) { FILE *f; /* A combination of fopen() with openat() */ - if (dir_fd == AT_FDCWD && flags == 0) { + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); + assert(mode); + assert(ret); + + if (dir_fd == AT_FDCWD && open_flags == 0) f = fopen(path, mode); - if (!f) - return -errno; - } else { - int fd, mode_flags; + else { + _cleanup_close_ int fd = -EBADF; + int mode_flags; mode_flags = fopen_mode_to_flags(mode); if (mode_flags < 0) return mode_flags; - fd = openat(dir_fd, path, mode_flags | flags); + fd = openat(dir_fd, path, mode_flags | open_flags); if (fd < 0) return -errno; - f = fdopen(fd, mode); - if (!f) { - safe_close(fd); + f = take_fdopen(&fd, mode); + } + if (!f) + return -errno; + + *ret = f; + return 0; +} + +static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_name, FILE **ret) { + _cleanup_close_ int sk = -EBADF; + FILE *f; + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); + assert(ret); + + sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (sk < 0) + return -errno; + + if (bind_name) { + /* If the caller specified a socket name to bind to, do so before connecting. This is + * useful to communicate some minor, short meta-information token from the client to + * the server. */ + union sockaddr_union bsa; + + r = sockaddr_un_set_path(&bsa.un, bind_name); + if (r < 0) + return r; + + if (bind(sk, &bsa.sa, r) < 0) return -errno; - } } + r = connect_unix_path(sk, dir_fd, path); + if (r < 0) + return r; + + if (shutdown(sk, SHUT_WR) < 0) + return -errno; + + f = take_fdopen(&sk, "r"); + if (!f) + return -errno; + + *ret = f; + return 0; +} + +int xfopenat_full( + int dir_fd, + const char *path, + const char *mode, + int open_flags, + XfopenFlags flags, + const char *bind_name, + FILE **ret) { + + FILE *f = NULL; /* avoid false maybe-uninitialized warning */ + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); + assert(mode); + assert(ret); + + r = xfopenat_regular(dir_fd, path, mode, open_flags, &f); + if (r == -ENXIO && FLAGS_SET(flags, XFOPEN_SOCKET)) { + /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */ + r = xfopenat_unix_socket(dir_fd, path, bind_name, &f); + if (IN_SET(r, -ENOTSOCK, -EINVAL)) + return -ENXIO; /* propagate original error if this is not a socket after all */ + } + if (r < 0) + return r; + + if (FLAGS_SET(flags, XFOPEN_UNLOCKED)) + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + *ret = f; return 0; } #if 0 /* NM_IGNORED */ +int fdopen_independent(int fd, const char *mode, FILE **ret) { + _cleanup_close_ int copy_fd = -EBADF; + _cleanup_fclose_ FILE *f = NULL; + int mode_flags; + + assert(fd >= 0); + assert(mode); + assert(ret); + + /* A combination of fdopen() + fd_reopen(). i.e. reopens the inode the specified fd points to and + * returns a FILE* for it */ + + mode_flags = fopen_mode_to_flags(mode); + if (mode_flags < 0) + return mode_flags; + + copy_fd = fd_reopen(fd, mode_flags); + if (copy_fd < 0) + return copy_fd; + + f = take_fdopen(©_fd, mode); + if (!f) + return -errno; + + *ret = TAKE_PTR(f); + return 0; +} + static int search_and_fopen_internal( const char *path, const char *mode, @@ -1256,7 +1299,7 @@ typedef enum EndOfLineMarker { static EndOfLineMarker categorize_eol(char c, ReadLineFlags flags) { - if (!IN_SET(flags, READ_LINE_ONLY_NUL)) { + if (!FLAGS_SET(flags, READ_LINE_ONLY_NUL)) { if (c == '\n') return EOL_TEN; if (c == '\r') diff --git a/src/libnm-systemd-shared/src/basic/fileio.h b/src/libnm-systemd-shared/src/basic/fileio.h index 9151d823..769bf394 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.h +++ b/src/libnm-systemd-shared/src/basic/fileio.h @@ -43,7 +43,6 @@ typedef enum { 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); int fdopen_unlocked(int fd, const char *options, FILE **ret); int take_fdopen_unlocked(int *fd, const char *options, FILE **ret); FILE* take_fdopen(int *fd, const char *options); @@ -55,15 +54,27 @@ int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) { return write_string_stream_ts(f, line, flags, NULL); } -int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts); +int write_string_file_ts_at(int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts); +static inline int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts) { + return write_string_file_ts_at(AT_FDCWD, fn, line, flags, ts); +} +static inline int write_string_file_at(int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags) { + return write_string_file_ts_at(dir_fd, fn, line, flags, NULL); +} static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) { return write_string_file_ts(fn, line, flags, NULL); } int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4); -int read_one_line_file(const char *filename, char **line); +int read_one_line_file_at(int dir_fd, const char *filename, char **ret); +static inline int read_one_line_file(const char *filename, char **ret) { + return read_one_line_file_at(AT_FDCWD, filename, ret); +} int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size); +static inline int read_full_file_at(int dir_fd, const char *filename, char **ret_contents, size_t *ret_size) { + return read_full_file_full(dir_fd, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size); +} static inline int read_full_file(const char *filename, char **ret_contents, size_t *ret_size) { return read_full_file_full(AT_FDCWD, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size); } @@ -82,14 +93,41 @@ static inline int read_full_stream(FILE *f, char **ret_contents, size_t *ret_siz return read_full_stream_full(f, NULL, UINT64_MAX, SIZE_MAX, 0, ret_contents, ret_size); } -int verify_file(const char *fn, const char *blob, bool accept_extra_nl); +int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl); +static inline int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { + return verify_file_at(AT_FDCWD, fn, blob, accept_extra_nl); +} int executable_is_script(const char *path, char **interpreter); int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field); DIR *xopendirat(int dirfd, const char *name, int flags); -int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret); + +typedef enum XfopenFlags { + XFOPEN_UNLOCKED = 1 << 0, /* call __fsetlocking(FSETLOCKING_BYCALLER) after opened */ + XFOPEN_SOCKET = 1 << 1, /* also try to open unix socket */ +} XfopenFlags; + +int xfopenat_full( + int dir_fd, + const char *path, + const char *mode, + int open_flags, + XfopenFlags flags, + const char *bind_name, + FILE **ret); +static inline int xfopenat(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) { + return xfopenat_full(dir_fd, path, mode, open_flags, 0, NULL, ret); +} +static inline int fopen_unlocked_at(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) { + return xfopenat_full(dir_fd, path, mode, open_flags, XFOPEN_UNLOCKED, NULL, ret); +} +static inline int fopen_unlocked(const char *path, const char *mode, FILE **ret) { + return fopen_unlocked_at(AT_FDCWD, path, mode, 0, ret); +} + +int fdopen_independent(int fd, const char *mode, FILE **ret); int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **ret, char **ret_path); int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **ret, char **ret_path); diff --git a/src/libnm-systemd-shared/src/basic/fs-util.c b/src/libnm-systemd-shared/src/basic/fs-util.c index 23f8d403..32fd849d 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.c +++ b/src/libnm-systemd-shared/src/basic/fs-util.c @@ -5,6 +5,7 @@ #include <errno.h> #include <stddef.h> #include <stdlib.h> +#include <sys/file.h> #include <linux/falloc.h> #include <linux/magic.h> #include <unistd.h> @@ -15,6 +16,8 @@ #include "fileio.h" #include "fs-util.h" #include "hostname-util.h" +#include "label.h" +#include "lock-util.h" #include "log.h" #include "macro.h" #include "missing_fcntl.h" @@ -34,12 +37,6 @@ #include "tmpfile-util.h" #include "umask-util.h" #include "user-util.h" -#include "util.h" - -int unlink_noerrno(const char *path) { - PROTECT_ERRNO; - return RET_NERRNO(unlink(path)); -} #if 0 /* NM_IGNORED */ int rmdir_parents(const char *path, const char *stop) { @@ -180,37 +177,41 @@ int readlink_value(const char *p, char **ret) { return 0; } -int readlink_and_make_absolute(const char *p, char **r) { +int readlink_and_make_absolute(const char *p, char **ret) { _cleanup_free_ char *target = NULL; - char *k; - int j; + int r; assert(p); - assert(r); - - j = readlink_malloc(p, &target); - if (j < 0) - return j; + assert(ret); - k = file_in_same_dir(p, target); - if (!k) - return -ENOMEM; + r = readlink_malloc(p, &target); + if (r < 0) + return r; - *r = k; - return 0; + return file_in_same_dir(p, target, ret); } -int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { - _cleanup_close_ int fd = -1; +int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid) { + _cleanup_close_ int fd = -EBADF; - assert(path); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW); /* Let's acquire an O_PATH fd, as precaution to change - * mode/owner on the same file */ - if (fd < 0) - return -errno; + if (path) { + /* Let's acquire an O_PATH fd, as precaution to change mode/owner on the same file */ + fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) + return -errno; + dir_fd = fd; - return fchmod_and_chown(fd, mode, uid, gid); + } else if (dir_fd == AT_FDCWD) { + /* Let's acquire an O_PATH fd of the current directory */ + fd = openat(dir_fd, ".", O_PATH|O_CLOEXEC|O_NOFOLLOW|O_DIRECTORY); + if (fd < 0) + return -errno; + dir_fd = fd; + } + + return fchmod_and_chown(dir_fd, mode, uid, gid); } int fchmod_and_chown_with_fallback(int fd, const char *path, mode_t mode, uid_t uid, gid_t gid) { @@ -308,7 +309,7 @@ int fchmod_opath(int fd, mode_t m) { } int futimens_opath(int fd, const struct timespec ts[2]) { - /* Similar to fchmod_path() but for futimens() */ + /* Similar to fchmod_opath() but for futimens() */ if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, 0) < 0) { if (errno != ENOENT) @@ -356,7 +357,7 @@ 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) { - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; int r, ret; assert(path); @@ -680,11 +681,11 @@ void unlink_tempfilep(char (*p)[]) { * successfully created. We ignore both the rare case where the * original suffix is used and unlink failures. */ if (!endswith(*p, ".XXXXXX")) - (void) unlink_noerrno(*p); + (void) unlink(*p); } int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) { - _cleanup_close_ int truncate_fd = -1; + _cleanup_close_ int truncate_fd = -EBADF; struct stat st; off_t l, bs; @@ -778,7 +779,7 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) { * punch-hole/truncate this to release the disk space. */ bs = MAX(st.st_blksize, 512); - l = DIV_ROUND_UP(st.st_size, bs) * bs; /* Round up to next block size */ + l = ROUND_UP(st.st_size, bs); /* Round up to next block size */ if (fallocate(truncate_fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, 0, l) >= 0) return 0; /* Successfully punched a hole! 😊 */ @@ -792,12 +793,23 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) { return 0; } -int open_parent(const char *path, int flags, mode_t mode) { +int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) { _cleanup_free_ char *parent = NULL; int r; + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); + r = path_extract_directory(path, &parent); - if (r < 0) + if (r == -EDESTADDRREQ) { + parent = strdup("."); + if (!parent) + return -ENOMEM; + } else if (r == -EADDRNOTAVAIL) { + parent = strdup(path); + if (!parent) + return -ENOMEM; + } else if (r < 0) return r; /* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an @@ -808,7 +820,7 @@ int open_parent(const char *path, int flags, mode_t mode) { else if (!FLAGS_SET(flags, O_TMPFILE)) flags |= O_DIRECTORY|O_RDONLY; - return RET_NERRNO(open(parent, flags, mode)); + return RET_NERRNO(openat(dir_fd, parent, flags, mode)); } #endif /* NM_IGNORED */ @@ -816,14 +828,14 @@ int conservative_renameat( int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { - _cleanup_close_ int old_fd = -1, new_fd = -1; + _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF; struct stat old_stat, new_stat; - /* Renames the old path to thew new path, much like renameat() — except if both are regular files and + /* Renames the old path to the new path, much like renameat() — except if both are regular files and * have the exact same contents and basic file attributes already. In that case remove the new file * instead. This call is useful for reducing inotify wakeups on files that are updated but don't * actually change. This function is written in a style that we rather rename too often than suppress - * too much. i.e. whenever we are in doubt we rather rename than fail. After all reducing inotify + * too much. I.e. whenever we are in doubt, we rather rename than fail. After all reducing inotify * events is an optimization only, not more. */ old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW); @@ -912,7 +924,7 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) { /* On EINTR try a couple of times more, but protect against busy looping * (not more than 16 times per 10s) */ - rl = (RateLimit) { 10 * USEC_PER_SEC, 16 }; + rl = (const RateLimit) { 10 * USEC_PER_SEC, 16 }; while (ratelimit_below(&rl)) { r = posix_fallocate(fd, offset, size); if (r != EINTR) @@ -998,9 +1010,8 @@ int parse_cifs_service( } int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { - _cleanup_close_ int fd = -1, parent_fd = -1; - _cleanup_free_ char *fname = NULL; - bool made; + _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF; + _cleanup_free_ char *fname = NULL, *parent = NULL; int r; /* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can @@ -1015,19 +1026,13 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { /* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following * flags actually make sense to specify: O_CLOEXEC, O_EXCL, O_NOATIME, O_PATH */ - if (isempty(path)) - return -EINVAL; - - if (!filename_is_valid(path)) { - _cleanup_free_ char *parent = NULL; - - /* If this is not a valid filename, it's a path. Let's open the parent directory then, so - * that we can pin it, and operate below it. */ - - r = path_extract_directory(path, &parent); - if (r < 0) + /* If this is not a valid filename, it's a path. Let's open the parent directory then, so + * that we can pin it, and operate below it. */ + r = path_extract_directory(path, &parent); + if (r < 0) { + if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL)) return r; - + } else { r = path_extract_filename(path, &fname); if (r < 0) return r; @@ -1040,33 +1045,11 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { path = fname; } - r = RET_NERRNO(mkdirat(dirfd, path, mode)); - if (r == -EEXIST) { - if (FLAGS_SET(flags, O_EXCL)) - return -EEXIST; - - made = false; - } else if (r < 0) - return r; - else - made = true; - - fd = RET_NERRNO(openat(dirfd, path, (flags & ~O_EXCL)|O_DIRECTORY|O_NOFOLLOW)); - if (fd < 0) { - if (fd == -ENOENT) /* We got ENOENT? then someone else immediately removed it after we - * created it. In that case let's return immediately without unlinking - * anything, because there simply isn't anything to unlink anymore. */ - return -ENOENT; - if (fd == -ELOOP) /* is a symlink? exists already → created by someone else, don't unlink */ - return -EEXIST; - if (fd == -ENOTDIR) /* not a directory? exists already → created by someone else, don't unlink */ - return -EEXIST; - - if (made) - (void) unlinkat(dirfd, path, AT_REMOVEDIR); - + fd = xopenat(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, /* xopen_flags = */ 0, mode); + if (IN_SET(fd, -ELOOP, -ENOTDIR)) + return -EEXIST; + if (fd < 0) return fd; - } return TAKE_FD(fd); } @@ -1117,3 +1100,120 @@ int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, b return -EEXIST; } } + +int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) { + _cleanup_close_ int fd = -EBADF; + bool made = false; + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + + if (isempty(path)) { + assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL)); + return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW); + } + + if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) { + r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG); + if (r < 0) + return r; + } + + if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) { + r = RET_NERRNO(mkdirat(dir_fd, path, mode)); + if (r == -EEXIST) { + if (FLAGS_SET(open_flags, O_EXCL)) + return -EEXIST; + + made = false; + } else if (r < 0) + return r; + else + made = true; + + if (FLAGS_SET(xopen_flags, XO_LABEL)) { + r = label_ops_post(dir_fd, path); + if (r < 0) + return r; + } + + open_flags &= ~(O_EXCL|O_CREAT); + xopen_flags &= ~XO_LABEL; + } + + fd = RET_NERRNO(openat(dir_fd, path, open_flags, mode)); + if (fd < 0) { + if (IN_SET(fd, + /* We got ENOENT? then someone else immediately removed it after we + * created it. In that case let's return immediately without unlinking + * anything, because there simply isn't anything to unlink anymore. */ + -ENOENT, + /* is a symlink? exists already → created by someone else, don't unlink */ + -ELOOP, + /* not a directory? exists already → created by someone else, don't unlink */ + -ENOTDIR)) + return fd; + + if (made) + (void) unlinkat(dir_fd, path, AT_REMOVEDIR); + + return fd; + } + + if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) { + r = label_ops_post(dir_fd, path); + if (r < 0) + return r; + } + + return TAKE_FD(fd); +} + +#if 0 /* NM_IGNORED */ +int xopenat_lock( + int dir_fd, + const char *path, + int open_flags, + XOpenFlags xopen_flags, + mode_t mode, + LockType locktype, + int operation) { + + _cleanup_close_ int fd = -EBADF; + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); + assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH)); + + /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with + * the same error here). */ + if (FLAGS_SET(open_flags, O_DIRECTORY) && locktype != LOCK_BSD) + return -EBADF; + + for (;;) { + struct stat st; + + fd = xopenat(dir_fd, path, open_flags, xopen_flags, mode); + if (fd < 0) + return fd; + + r = lock_generic(fd, locktype, operation); + if (r < 0) + return r; + + /* If we acquired the lock, let's check if the file/directory still exists in the file + * system. If not, then the previous exclusive owner removed it and then closed it. In such a + * case our acquired lock is worthless, hence try again. */ + + if (fstat(fd, &st) < 0) + return -errno; + if (st.st_nlink > 0) + break; + + fd = safe_close(fd); + } + + return TAKE_FD(fd); +} +#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/fs-util.h b/src/libnm-systemd-shared/src/basic/fs-util.h index c4dffc48..a19836d1 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.h +++ b/src/libnm-systemd-shared/src/basic/fs-util.h @@ -12,6 +12,7 @@ #include "alloc-util.h" #include "errno-util.h" +#include "lock-util.h" #include "time-util.h" #include "user-util.h" @@ -22,8 +23,6 @@ #define PTR_TO_MODE(p) ((mode_t) ((uintptr_t) (p)-1)) #define MODE_TO_PTR(u) ((void *) ((uintptr_t) (u)+1)) -int unlink_noerrno(const char *path); - int rmdir_parents(const char *path, const char *stop); int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); @@ -33,7 +32,10 @@ int readlink_malloc(const char *p, char **r); int readlink_value(const char *p, char **ret); int readlink_and_make_absolute(const char *p, char **r); -int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); +int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid); +static inline int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { + return chmod_and_chown_at(AT_FDCWD, path, mode, uid, gid); +} int fchmod_and_chown_with_fallback(int fd, const char *path, mode_t mode, uid_t uid, gid_t gid); static inline int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) { return fchmod_and_chown_with_fallback(fd, NULL, mode, uid, gid); /* no fallback */ @@ -96,7 +98,7 @@ static inline char* unlink_and_free(char *p) { if (!p) return NULL; - (void) unlink_noerrno(p); + (void) unlink(p); return mfree(p); } DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free); @@ -112,7 +114,10 @@ typedef enum UnlinkDeallocateFlags { int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags); -int open_parent(const char *path, int flags, mode_t mode); +int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode); +static inline int open_parent(const char *path, int flags, mode_t mode) { + return open_parent_at(AT_FDCWD, path, flags, mode); +} int conservative_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); static inline int conservative_rename(const char *oldpath, const char *newpath) { @@ -126,3 +131,11 @@ int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode); int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created); + +typedef enum XOpenFlags { + XO_LABEL = 1 << 0, +} XOpenFlags; + +int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode); + +int xopenat_lock(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode, LockType locktype, int operation); diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.c b/src/libnm-systemd-shared/src/basic/glyph-util.c index 5289cffe..1ea8a645 100644 --- a/src/libnm-systemd-shared/src/basic/glyph-util.c +++ b/src/libnm-systemd-shared/src/basic/glyph-util.c @@ -11,15 +11,15 @@ bool emoji_enabled(void) { static int cached_emoji_enabled = -1; if (cached_emoji_enabled < 0) { - int val; - - val = getenv_bool("SYSTEMD_EMOJI"); - if (val < 0) - cached_emoji_enabled = - is_locale_utf8() && - !STRPTR_IN_SET(getenv("TERM"), "dumb", "linux"); - else - cached_emoji_enabled = val; + int val = getenv_bool("SYSTEMD_EMOJI"); + if (val >= 0) + return (cached_emoji_enabled = val); + + const char *term = getenv("TERM"); + if (!term || STR_IN_SET(term, "dumb", "linux")) + return (cached_emoji_enabled = false); + + cached_emoji_enabled = is_locale_utf8(); } return cached_emoji_enabled; @@ -73,6 +73,7 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_RECYCLING] = "~", [SPECIAL_GLYPH_DOWNLOAD] = "\\", [SPECIAL_GLYPH_SPARKLES] = "*", + [SPECIAL_GLYPH_WARNING_SIGN] = "!", }, /* UTF-8 */ @@ -126,10 +127,11 @@ const char *special_glyph(SpecialGlyph code) { /* This emoji is a single character cell glyph in Unicode, and two in ASCII */ [SPECIAL_GLYPH_TOUCH] = u8"👆", /* actually called: BACKHAND INDEX POINTING UP */ - /* These three emojis are single character cell glyphs in Unicode and also in ASCII. */ + /* These four emojis are single character cell glyphs in Unicode and also in ASCII. */ [SPECIAL_GLYPH_RECYCLING] = u8"♻️", /* actually called: UNIVERSAL RECYCLNG SYMBOL */ [SPECIAL_GLYPH_DOWNLOAD] = u8"⤵️", /* actually called: RIGHT ARROW CURVING DOWN */ [SPECIAL_GLYPH_SPARKLES] = u8"✨", + [SPECIAL_GLYPH_WARNING_SIGN] = u8"⚠️", }, }; diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.h b/src/libnm-systemd-shared/src/basic/glyph-util.h index 621d7a85..b6463962 100644 --- a/src/libnm-systemd-shared/src/basic/glyph-util.h +++ b/src/libnm-systemd-shared/src/basic/glyph-util.h @@ -44,6 +44,7 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_RECYCLING, SPECIAL_GLYPH_DOWNLOAD, SPECIAL_GLYPH_SPARKLES, + SPECIAL_GLYPH_WARNING_SIGN, _SPECIAL_GLYPH_MAX, _SPECIAL_GLYPH_INVALID = -EINVAL, } SpecialGlyph; diff --git a/src/libnm-systemd-shared/src/basic/hash-funcs.h b/src/libnm-systemd-shared/src/basic/hash-funcs.h index c14302ec..be642892 100644 --- a/src/libnm-systemd-shared/src/basic/hash-funcs.h +++ b/src/libnm-systemd-shared/src/basic/hash-funcs.h @@ -102,7 +102,7 @@ extern const struct hash_ops uint64_hash_ops; /* On some archs dev_t is 32bit, and on others 64bit. And sometimes it's 64bit on 32bit archs, and sometimes 32bit on * 64bit archs. Yuck! */ #if SIZEOF_DEV_T != 8 -void devt_hash_func(const dev_t *p, struct siphash *state) _pure_; +void devt_hash_func(const dev_t *p, struct siphash *state); #else #define devt_hash_func uint64_hash_func #endif diff --git a/src/libnm-systemd-shared/src/basic/hashmap.c b/src/libnm-systemd-shared/src/basic/hashmap.c index d0b950e0..356200cf 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.c +++ b/src/libnm-systemd-shared/src/basic/hashmap.c @@ -7,10 +7,14 @@ #include <pthread.h> #include <stdint.h> #include <stdlib.h> +#if HAVE_VALGRIND_VALGRIND_H +# include <valgrind/valgrind.h> +#endif #include "alloc-util.h" #include "fileio.h" #include "hashmap.h" +#include "logarithm.h" #include "macro.h" #include "memory-util.h" #include "mempool.h" @@ -275,29 +279,34 @@ static _used_ const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX }, }; -#if VALGRIND -_destructor_ static void cleanup_pools(void) { - _cleanup_free_ char *t = NULL; +#if 0 /* NM_IGNORED */ +void hashmap_trim_pools(void) { int r; - /* Be nice to valgrind */ + /* The pool is only allocated by the main thread, but the memory can be passed to other + * threads. Let's clean up if we are the main thread and no other threads are live. */ - /* The pool is only allocated by the main thread, but the memory can - * be passed to other threads. Let's clean up if we are the main thread - * and no other threads are live. */ - /* We build our own is_main_thread() here, which doesn't use C11 - * TLS based caching of the result. That's because valgrind apparently - * doesn't like malloc() (which C11 TLS internally uses) to be called - * from a GCC destructors. */ + /* We build our own is_main_thread() here, which doesn't use C11 TLS based caching of the + * result. That's because valgrind apparently doesn't like TLS to be used from a GCC destructor. */ if (getpid() != gettid()) - return; + return (void) log_debug("Not cleaning up memory pools, not in main thread."); - r = get_proc_field("/proc/self/status", "Threads", WHITESPACE, &t); - if (r < 0 || !streq(t, "1")) - return; + r = get_process_threads(0); + if (r < 0) + return (void) log_debug_errno(r, "Failed to determine number of threads, not cleaning up memory pools: %m"); + if (r != 1) + return (void) log_debug("Not cleaning up memory pools, running in multi-threaded process."); + + mempool_trim(&hashmap_pool); + mempool_trim(&ordered_hashmap_pool); +} +#endif /* NM_IGNORED */ - mempool_drop(&hashmap_pool); - mempool_drop(&ordered_hashmap_pool); +#if HAVE_VALGRIND_VALGRIND_H +_destructor_ static void cleanup_pools(void) { + /* Be nice to valgrind */ + if (RUNNING_ON_VALGRIND) + hashmap_trim_pools(); } #endif @@ -374,8 +383,9 @@ static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) { } static struct hashmap_base_entry* bucket_at(HashmapBase *h, unsigned idx) { - return (struct hashmap_base_entry*) (void *) - ((uint8_t*) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size); + return CAST_ALIGN_PTR( + struct hashmap_base_entry, + (uint8_t *) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size); } static struct plain_hashmap_entry* plain_bucket_at(Hashmap *h, unsigned idx) { @@ -774,7 +784,7 @@ static struct HashmapBase* hashmap_base_new(const struct hash_ops *hash_ops, enu HashmapBase *h; const struct hashmap_type_info *hi = &hashmap_type_info[type]; - bool use_pool = mempool_enabled && mempool_enabled(); + bool use_pool = mempool_enabled && mempool_enabled(); /* mempool_enabled is a weak symbol */ h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size); if (!h) @@ -1753,7 +1763,7 @@ HashmapBase* _hashmap_copy(HashmapBase *h HASHMAP_DEBUG_PARAMS) { } if (r < 0) - return _hashmap_free(copy, false, false); + return _hashmap_free(copy, NULL, NULL); return copy; } diff --git a/src/libnm-systemd-shared/src/basic/hashmap.h b/src/libnm-systemd-shared/src/basic/hashmap.h index 91b3fe86..68d9b81c 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.h +++ b/src/libnm-systemd-shared/src/basic/hashmap.h @@ -7,7 +7,6 @@ #include "hash-funcs.h" #include "macro.h" -#include "util.h" /* * A hash table implementation. As a minor optimization a NULL hashmap object @@ -444,3 +443,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free); DEFINE_TRIVIAL_CLEANUP_FUNC(IteratedCache*, iterated_cache_free); #define _cleanup_iterated_cache_free_ _cleanup_(iterated_cache_freep) + +void hashmap_trim_pools(void); diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.c b/src/libnm-systemd-shared/src/basic/hexdecoct.c index 570da688..d41d2ea0 100644 --- a/src/libnm-systemd-shared/src/basic/hexdecoct.c +++ b/src/libnm-systemd-shared/src/basic/hexdecoct.c @@ -61,11 +61,13 @@ char *hexmem(const void *p, size_t l) { const uint8_t *x; char *r, *z; + assert(p || l == 0); + z = r = new(char, l * 2 + 1); if (!r) return NULL; - for (x = p; x < (const uint8_t*) p + l; x++) { + for (x = p; x && x < (const uint8_t*) p + l; x++) { *(z++) = hexchar(*x >> 4); *(z++) = hexchar(*x & 15); } @@ -110,12 +112,17 @@ static int unhex_next(const char **p, size_t *l) { return r; } -int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_len) { +int unhexmem_full( + const char *p, + size_t l, + bool secure, + void **ret, + size_t *ret_len) { + _cleanup_free_ uint8_t *buf = NULL; size_t buf_size; const char *x; uint8_t *z; - int r; assert(p || l == 0); @@ -128,22 +135,20 @@ int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_ if (!buf) return -ENOMEM; + CLEANUP_ERASE_PTR(secure ? &buf : NULL, buf_size); + for (x = p, z = buf;;) { int a, b; a = unhex_next(&x, &l); if (a == -EPIPE) /* End of string */ break; - if (a < 0) { - r = a; - goto on_failure; - } + if (a < 0) + return a; b = unhex_next(&x, &l); - if (b < 0) { - r = b; - goto on_failure; - } + if (b < 0) + return b; *(z++) = (uint8_t) a << 4 | (uint8_t) b; } @@ -156,12 +161,6 @@ int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_ *ret = TAKE_PTR(buf); return 0; - -on_failure: - if (secure) - explicit_bzero_safe(buf, buf_size); - - return r; } #if 0 /* NM_IGNORED */ @@ -591,121 +590,136 @@ ssize_t base64mem_full( const void *p, size_t l, size_t line_break, - char **out) { + char **ret) { const uint8_t *x; - char *r, *z; + char *b, *z; size_t m; assert(p || l == 0); - assert(out); assert(line_break > 0); + assert(ret); /* three input bytes makes four output bytes, padding is added so we must round up */ m = 4 * (l + 2) / 3 + 1; - if (line_break != SIZE_MAX) m += m / line_break; - z = r = malloc(m); - if (!r) + z = b = malloc(m); + if (!b) return -ENOMEM; - for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) { + for (x = p; x && 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); + maybe_line_break(&z, b, line_break); *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ - maybe_line_break(&z, r, line_break); + maybe_line_break(&z, b, line_break); *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ - maybe_line_break(&z, r, line_break); + maybe_line_break(&z, b, line_break); *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */ - maybe_line_break(&z, r, line_break); + maybe_line_break(&z, b, line_break); *(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */ } switch (l % 3) { case 2: - maybe_line_break(&z, r, line_break); + maybe_line_break(&z, b, line_break); *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ - maybe_line_break(&z, r, line_break); + maybe_line_break(&z, b, line_break); *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ - maybe_line_break(&z, r, line_break); + maybe_line_break(&z, b, line_break); *(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */ - maybe_line_break(&z, r, line_break); + maybe_line_break(&z, b, line_break); *(z++) = '='; - break; + case 1: - maybe_line_break(&z, r, line_break); + maybe_line_break(&z, b, line_break); *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ - maybe_line_break(&z, r, line_break); + maybe_line_break(&z, b, line_break); *(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */ - maybe_line_break(&z, r, line_break); + maybe_line_break(&z, b, line_break); *(z++) = '='; - maybe_line_break(&z, r, line_break); + maybe_line_break(&z, b, line_break); *(z++) = '='; - break; } *z = 0; - *out = r; - assert(z >= r); /* Let static analyzers know that the answer is non-negative. */ - return z - r; + *ret = b; + + assert(z >= b); /* Let static analyzers know that the answer is non-negative. */ + return z - b; } -static int base64_append_width( - char **prefix, int plen, - char sep, int indent, - const void *p, size_t l, - int width) { +static ssize_t base64_append_width( + char **prefix, + size_t plen, + char sep, + size_t indent, + const void *p, + size_t l, + size_t width) { _cleanup_free_ char *x = NULL; char *t, *s; - ssize_t len, avail, line, lines; + size_t lines; + ssize_t len; + + assert(prefix); + assert(*prefix || plen == 0); + assert(p || l == 0); len = base64mem(p, l, &x); - if (len <= 0) + if (len < 0) return len; + if (len == 0) + return plen; lines = DIV_ROUND_UP(len, width); - if ((size_t) plen >= SSIZE_MAX - 1 - 1 || + if (plen >= SSIZE_MAX - 1 - 1 || lines > (SSIZE_MAX - plen - 1 - 1) / (indent + width + 1)) return -ENOMEM; - t = realloc(*prefix, (ssize_t) plen + 1 + 1 + (indent + width + 1) * lines); + t = realloc(*prefix, plen + 1 + 1 + (indent + width + 1) * lines); if (!t) return -ENOMEM; - t[plen] = sep; + s = t + plen; + for (size_t line = 0; line < lines; line++) { + size_t act = MIN(width, (size_t) len); - for (line = 0, s = t + plen + 1, avail = len; line < lines; line++) { - int act = MIN(width, avail); + if (line > 0) + sep = '\n'; - if (line > 0 || sep == '\n') { - memset(s, ' ', indent); - s += indent; + if (s > t) { + *s++ = sep; + if (sep == '\n') + s = mempset(s, ' ', indent); } s = mempcpy(s, x + width * line, act); - *(s++) = line < lines - 1 ? '\n' : '\0'; - avail -= act; + len -= act; } - assert(avail == 0); + assert(len == 0); + *s = '\0'; *prefix = t; - return 0; + return s - t; } -int base64_append( - char **prefix, int plen, - const void *p, size_t l, - int indent, int width) { +ssize_t base64_append( + char **prefix, + size_t plen, + const void *p, + size_t l, + size_t indent, + size_t width) { if (plen > width / 2 || plen + indent > width) /* leave indent on the left, keep last column free */ - return base64_append_width(prefix, plen, '\n', indent, p, l, width - indent - 1); + return base64_append_width(prefix, plen, '\n', indent, p, l, width - indent); else /* leave plen on the left, keep last column free */ return base64_append_width(prefix, plen, ' ', plen + 1, p, l, width - plen - 1); @@ -754,12 +768,17 @@ static int unbase64_next(const char **p, size_t *l) { return ret; } -int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_size) { +int unbase64mem_full( + const char *p, + size_t l, + bool secure, + void **ret, + size_t *ret_size) { + _cleanup_free_ uint8_t *buf = NULL; const char *x; uint8_t *z; size_t len; - int r; assert(p || l == 0); @@ -774,60 +793,44 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r if (!buf) return -ENOMEM; + CLEANUP_ERASE_PTR(secure ? &buf : NULL, len); + for (x = p, z = buf;;) { int a, b, c, d; /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */ a = unbase64_next(&x, &l); if (a == -EPIPE) /* End of string */ break; - if (a < 0) { - r = a; - goto on_failure; - } - if (a == INT_MAX) { /* Padding is not allowed at the beginning of a 4ch block */ - r = -EINVAL; - goto on_failure; - } + if (a < 0) + return a; + if (a == INT_MAX) /* Padding is not allowed at the beginning of a 4ch block */ + return -EINVAL; b = unbase64_next(&x, &l); - if (b < 0) { - r = b; - goto on_failure; - } - if (b == INT_MAX) { /* Padding is not allowed at the second character of a 4ch block either */ - r = -EINVAL; - goto on_failure; - } + if (b < 0) + return b; + if (b == INT_MAX) /* Padding is not allowed at the second character of a 4ch block either */ + return -EINVAL; c = unbase64_next(&x, &l); - if (c < 0) { - r = c; - goto on_failure; - } + if (c < 0) + return c; d = unbase64_next(&x, &l); - if (d < 0) { - r = d; - goto on_failure; - } + if (d < 0) + return d; if (c == INT_MAX) { /* Padding at the third character */ - if (d != INT_MAX) { /* If the third character is padding, the fourth must be too */ - r = -EINVAL; - goto on_failure; - } + if (d != INT_MAX) /* If the third character is padding, the fourth must be too */ + return -EINVAL; /* b == 00YY0000 */ - if (b & 15) { - r = -EINVAL; - goto on_failure; - } + if (b & 15) + return -EINVAL; - if (l > 0) { /* Trailing rubbish? */ - r = -ENAMETOOLONG; - goto on_failure; - } + if (l > 0) /* Trailing rubbish? */ + return -ENAMETOOLONG; *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ break; @@ -835,15 +838,11 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r if (d == INT_MAX) { /* c == 00ZZZZ00 */ - if (c & 3) { - r = -EINVAL; - goto on_failure; - } + if (c & 3) + return -EINVAL; - if (l > 0) { /* Trailing rubbish? */ - r = -ENAMETOOLONG; - goto on_failure; - } + if (l > 0) /* Trailing rubbish? */ + return -ENAMETOOLONG; *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ @@ -857,18 +856,14 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r *z = 0; + assert((size_t) (z - buf) <= len); + if (ret_size) *ret_size = (size_t) (z - buf); if (ret) *ret = TAKE_PTR(buf); return 0; - -on_failure: - if (secure) - explicit_bzero_safe(buf, len); - - return r; } #if 0 /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.h b/src/libnm-systemd-shared/src/basic/hexdecoct.h index 5218f786..319b21a1 100644 --- a/src/libnm-systemd-shared/src/basic/hexdecoct.h +++ b/src/libnm-systemd-shared/src/basic/hexdecoct.h @@ -38,9 +38,13 @@ 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); +ssize_t base64_append( + char **prefix, + size_t plen, + const void *p, + size_t l, + size_t margin, + size_t width); int unbase64mem_full(const char *p, size_t l, bool secure, void **mem, size_t *len); static inline int unbase64mem(const char *p, size_t l, void **mem, size_t *len) { return unbase64mem_full(p, l, false, mem, len); diff --git a/src/libnm-systemd-shared/src/basic/hostname-util.c b/src/libnm-systemd-shared/src/basic/hostname-util.c index 1e94ce13..26e196f3 100644 --- a/src/libnm-systemd-shared/src/basic/hostname-util.c +++ b/src/libnm-systemd-shared/src/basic/hostname-util.c @@ -65,7 +65,7 @@ int gethostname_full(GetHostnameFlags flags, char **ret) { } if (FLAGS_SET(flags, GET_HOSTNAME_SHORT)) - buf = strndup(s, strcspn(s, ".")); + buf = strdupcspn(s, "."); else buf = strdup(s); if (!buf) @@ -197,6 +197,7 @@ bool is_localhost(const char *hostname) { endswith_no_case(hostname, ".localhost.localdomain."); } +#if 0 /* NM_IGNORED */ int get_pretty_hostname(char **ret) { _cleanup_free_ char *n = NULL; int r; @@ -213,3 +214,4 @@ int get_pretty_hostname(char **ret) { *ret = TAKE_PTR(n); return 0; } +#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/hostname-util.h b/src/libnm-systemd-shared/src/basic/hostname-util.h index a00b8523..bcac3d9f 100644 --- a/src/libnm-systemd-shared/src/basic/hostname-util.h +++ b/src/libnm-systemd-shared/src/basic/hostname-util.h @@ -60,4 +60,12 @@ static inline bool is_outbound_hostname(const char *hostname) { return STRCASE_IN_SET(hostname, "_outbound", "_outbound."); } +static inline bool is_dns_stub_hostname(const char *hostname) { + return STRCASE_IN_SET(hostname, "_localdnsstub", "_localdnsstub."); +} + +static inline bool is_dns_proxy_stub_hostname(const char *hostname) { + return STRCASE_IN_SET(hostname, "_localdnsproxy", "_localdnsproxy."); +} + int get_pretty_hostname(char **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 b25c803b..b863aec3 100644 --- a/src/libnm-systemd-shared/src/basic/in-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/in-addr-util.c @@ -13,13 +13,13 @@ #include "alloc-util.h" #include "errno-util.h" #include "in-addr-util.h" +#include "logarithm.h" #include "macro.h" #include "parse-util.h" #include "random-util.h" #include "stdio-util.h" #include "string-util.h" #include "strxcpyx.h" -#include "util.h" bool in4_addr_is_null(const struct in_addr *a) { assert(a); @@ -907,14 +907,6 @@ int in_addr_prefix_from_string_auto_internal( break; case PREFIXLEN_REFUSE: return -ENOANO; /* To distinguish this error from others. */ - case PREFIXLEN_LEGACY: - if (family == AF_INET) { - r = in4_addr_default_prefixlen(&buffer.in, &k); - if (r < 0) - return r; - } else - k = 0; - break; default: assert_not_reached(); } @@ -930,7 +922,7 @@ int in_addr_prefix_from_string_auto_internal( } -static void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state) { +void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state) { assert(a); assert(state); @@ -938,7 +930,7 @@ static void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state); } -static int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y) { +int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y) { int r; assert(x); @@ -951,7 +943,18 @@ static int in_addr_data_compare_func(const struct in_addr_data *x, const struct return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family)); } -DEFINE_HASH_OPS(in_addr_data_hash_ops, struct in_addr_data, in_addr_data_hash_func, in_addr_data_compare_func); +DEFINE_HASH_OPS( + in_addr_data_hash_ops, + struct in_addr_data, + in_addr_data_hash_func, + in_addr_data_compare_func); + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + in_addr_data_hash_ops_free, + struct in_addr_data, + in_addr_data_hash_func, + in_addr_data_compare_func, + free); void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state) { assert(addr); @@ -967,7 +970,12 @@ int in6_addr_compare_func(const struct in6_addr *a, const struct in6_addr *b) { return memcmp(a, b, sizeof(*a)); } -DEFINE_HASH_OPS(in6_addr_hash_ops, struct in6_addr, in6_addr_hash_func, in6_addr_compare_func); +DEFINE_HASH_OPS( + in6_addr_hash_ops, + struct in6_addr, + in6_addr_hash_func, + in6_addr_compare_func); + DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( in6_addr_hash_ops_free, struct in6_addr, 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 19fa35f1..200b9eb6 100644 --- a/src/libnm-systemd-shared/src/basic/in-addr-util.h +++ b/src/libnm-systemd-shared/src/basic/in-addr-util.h @@ -8,7 +8,6 @@ #include "hash-funcs.h" #include "macro.h" -#include "util.h" union in_addr_union { struct in_addr in; @@ -154,7 +153,6 @@ int in_addr_prefix_from_string(const char *p, int family, union in_addr_union *r typedef enum InAddrPrefixLenMode { PREFIXLEN_FULL, /* Default to prefixlen of address size, 32 for IPv4 or 128 for IPv6, if not specified. */ PREFIXLEN_REFUSE, /* Fail with -ENOANO if prefixlen is not specified. */ - PREFIXLEN_LEGACY, /* Default to legacy default prefixlen calculation from address if not specified. */ } InAddrPrefixLenMode; int in_addr_prefix_from_string_auto_internal(const char *p, InAddrPrefixLenMode mode, int *ret_family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen); @@ -178,10 +176,13 @@ static inline size_t FAMILY_ADDRESS_SIZE(int family) { * See also oss-fuzz#11344. */ #define IN_ADDR_NULL ((union in_addr_union) { .in6 = {} }) +void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state); +int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y); void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state); 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_data_hash_ops_free; extern const struct hash_ops in6_addr_hash_ops; extern const struct hash_ops in6_addr_hash_ops_free; diff --git a/src/libnm-systemd-shared/src/basic/io-util.c b/src/libnm-systemd-shared/src/basic/io-util.c index fc65a5e7..0c480091 100644 --- a/src/libnm-systemd-shared/src/basic/io-util.c +++ b/src/libnm-systemd-shared/src/basic/io-util.c @@ -167,6 +167,21 @@ int ppoll_usec(struct pollfd *fds, size_t nfds, usec_t timeout) { assert(fds || nfds == 0); + /* This is a wrapper around ppoll() that does primarily two things: + * + * ✅ Takes a usec_t instead of a struct timespec + * + * ✅ Guarantees that if an invalid fd is specified we return EBADF (i.e. converts POLLNVAL to + * EBADF). This is done because EBADF is a programming error usually, and hence should bubble up + * as error, and not be eaten up as non-error POLLNVAL event. + * + * ⚠️ ⚠️ ⚠️ Note that this function does not add any special handling for EINTR. Don't forget + * poll()/ppoll() will return with EINTR on any received signal always, there is no automatic + * restarting via SA_RESTART available. Thus, typically you want to handle EINTR not as an error, + * but just as reason to restart things, under the assumption you use a more appropriate mechanism + * to handle signals, such as signalfd() or signal handlers. ⚠️ ⚠️ ⚠️ + */ + if (nfds == 0) return 0; @@ -194,6 +209,9 @@ int fd_wait_for_event(int fd, int event, usec_t timeout) { }; int r; + /* ⚠️ ⚠️ ⚠️ Keep in mind you almost certainly want to handle -EINTR gracefully in the caller, see + * ppoll_usec() above! ⚠️ ⚠️ ⚠️ */ + r = ppoll_usec(&pollfd, 1, timeout); if (r <= 0) return r; @@ -351,4 +369,14 @@ size_t iovw_size(struct iovec_wrapper *iovw) { return n; } + +void iovec_array_free(struct iovec *iov, size_t n) { + if (!iov) + return; + + for (size_t i = 0; i < n; i++) + free(iov[i].iov_base); + + free(iov); +} #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/io-util.h b/src/libnm-systemd-shared/src/basic/io-util.h index 39728e06..3ad82679 100644 --- a/src/libnm-systemd-shared/src/basic/io-util.h +++ b/src/libnm-systemd-shared/src/basic/io-util.h @@ -74,10 +74,13 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) { } -#define IOVEC_INIT(base, len) { .iov_base = (base), .iov_len = (len) } -#define IOVEC_MAKE(base, len) (struct iovec) IOVEC_INIT(base, len) -#define IOVEC_INIT_STRING(string) IOVEC_INIT((char*) string, strlen(string)) -#define IOVEC_MAKE_STRING(string) (struct iovec) IOVEC_INIT_STRING(string) +#define IOVEC_NULL (struct iovec) {} +#define IOVEC_MAKE(base, len) (struct iovec) { .iov_base = (base), .iov_len = (len) } +#define IOVEC_MAKE_STRING(string) \ + ({ \ + char *_s = (char*) (string); \ + IOVEC_MAKE(_s, strlen(_s)); \ + }) char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value); char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value); @@ -91,8 +94,19 @@ struct iovec_wrapper *iovw_new(void); struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw); struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw); void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors); + int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); +static inline int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { + /* Move data into iovw or free on error */ + int r = iovw_put(iovw, data, len); + if (r < 0) + free(data); + return r; +} + int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value); int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value); void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new); size_t iovw_size(struct iovec_wrapper *iovw); + +void iovec_array_free(struct iovec *iov, size_t n); diff --git a/src/libnm-systemd-shared/src/basic/label.c b/src/libnm-systemd-shared/src/basic/label.c new file mode 100644 index 00000000..a08a238f --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/label.c @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-shared.h" + +#include <errno.h> +#include <stddef.h> + +#include "label.h" + +static const LabelOps *label_ops = NULL; + +int label_ops_set(const LabelOps *ops) { + if (label_ops) + return -EBUSY; + + label_ops = ops; + return 0; +} + +int label_ops_pre(int dir_fd, const char *path, mode_t mode) { + if (!label_ops || !label_ops->pre) + return 0; + + return label_ops->pre(dir_fd, path, mode); +} + +int label_ops_post(int dir_fd, const char *path) { + if (!label_ops || !label_ops->post) + return 0; + + return label_ops->post(dir_fd, path); +} diff --git a/src/libnm-systemd-shared/src/basic/label.h b/src/libnm-systemd-shared/src/basic/label.h new file mode 100644 index 00000000..9644e435 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/label.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <sys/types.h> + +typedef struct LabelOps { + int (*pre)(int dir_fd, const char *path, mode_t mode); + int (*post)(int dir_fd, const char *path); +} LabelOps; + +int label_ops_set(const LabelOps *label_ops); + +int label_ops_pre(int dir_fd, const char *path, mode_t mode); +int label_ops_post(int dir_fd, const char *path); diff --git a/src/libnm-systemd-shared/src/basic/list.h b/src/libnm-systemd-shared/src/basic/list.h index ca300396..e4e5dff3 100644 --- a/src/libnm-systemd-shared/src/basic/list.h +++ b/src/libnm-systemd-shared/src/basic/list.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "macro.h" - /* The head of the linked list. Use this in the structure that shall * contain the head of the linked list */ #define LIST_HEAD(t,name) \ @@ -28,26 +26,27 @@ /* Prepend an item to the list */ #define LIST_PREPEND(name,head,item) \ - do { \ + ({ \ typeof(*(head)) **_head = &(head), *_item = (item); \ assert(_item); \ if ((_item->name##_next = *_head)) \ _item->name##_next->name##_prev = _item; \ _item->name##_prev = NULL; \ *_head = _item; \ - } while (false) + _item; \ + }) /* Append an item to the list */ #define LIST_APPEND(name,head,item) \ - do { \ + ({ \ typeof(*(head)) **_hhead = &(head), *_tail; \ - LIST_FIND_TAIL(name, *_hhead, _tail); \ + _tail = LIST_FIND_TAIL(name, *_hhead); \ LIST_INSERT_AFTER(name, *_hhead, _tail, item); \ - } while (false) + }) /* Remove an item from the list */ #define LIST_REMOVE(name,head,item) \ - do { \ + ({ \ typeof(*(head)) **_head = &(head), *_item = (item); \ assert(_item); \ if (_item->name##_next) \ @@ -59,37 +58,30 @@ *_head = _item->name##_next; \ } \ _item->name##_next = _item->name##_prev = NULL; \ - } while (false) + _item; \ + }) /* Find the head of the list */ -#define LIST_FIND_HEAD(name,item,head) \ - do { \ +#define LIST_FIND_HEAD(name,item) \ + ({ \ typeof(*(item)) *_item = (item); \ - if (!_item) \ - (head) = NULL; \ - else { \ - while (_item->name##_prev) \ - _item = _item->name##_prev; \ - (head) = _item; \ - } \ - } while (false) + while (_item && _item->name##_prev) \ + _item = _item->name##_prev; \ + _item; \ + }) /* Find the tail of the list */ -#define LIST_FIND_TAIL(name,item,tail) \ - do { \ +#define LIST_FIND_TAIL(name,item) \ + ({ \ typeof(*(item)) *_item = (item); \ - if (!_item) \ - (tail) = NULL; \ - else { \ - while (_item->name##_next) \ - _item = _item->name##_next; \ - (tail) = _item; \ - } \ - } while (false) + while (_item && _item->name##_next) \ + _item = _item->name##_next; \ + _item; \ + }) /* Insert an item after another one (a = where, b = what) */ #define LIST_INSERT_AFTER(name,head,a,b) \ - do { \ + ({ \ typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \ assert(_b); \ if (!_a) { \ @@ -103,11 +95,12 @@ _b->name##_prev = _a; \ _a->name##_next = _b; \ } \ - } while (false) + _b; \ + }) /* Insert an item before another one (a = where, b = what) */ #define LIST_INSERT_BEFORE(name,head,a,b) \ - do { \ + ({ \ typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \ assert(_b); \ if (!_a) { \ @@ -131,10 +124,14 @@ _b->name##_next = _a; \ _a->name##_prev = _b; \ } \ - } while (false) + _b; \ + }) -#define LIST_JUST_US(name,item) \ - (!(item)->name##_prev && !(item)->name##_next) +#define LIST_JUST_US(name, item) \ + ({ \ + typeof(*(item)) *_item = (item); \ + !(_item)->name##_prev && !(_item)->name##_next; \ + }) /* The type of the iterator 'i' is automatically determined by the type of 'head', and declared in the * loop. Hence, do not declare the same variable in the outer scope. Sometimes, we set 'head' through @@ -172,18 +169,19 @@ /* Join two lists tail to head: a->b, c->d to a->b->c->d and de-initialise second list */ #define LIST_JOIN(name,a,b) \ - do { \ + ({ \ assert(b); \ if (!(a)) \ (a) = (b); \ else { \ typeof(*(a)) *_head = (b), *_tail; \ - LIST_FIND_TAIL(name, (a), _tail); \ + _tail = LIST_FIND_TAIL(name, (a)); \ _tail->name##_next = _head; \ _head->name##_prev = _tail; \ } \ (b) = NULL; \ - } while (false) + a; \ + }) #define LIST_POP(name, a) \ ({ \ @@ -193,3 +191,7 @@ LIST_REMOVE(name, *_a, _p); \ _p; \ }) + +/* Now include "macro.h", because we want our definition of assert() which the macros above use. We include + * it down here instead of up top, since macro.h pulls in log.h which in turn needs our own definitions. */ +#include "macro.h" diff --git a/src/libnm-systemd-shared/src/basic/locale-util.c b/src/libnm-systemd-shared/src/basic/locale-util.c index ae567345..2f3701eb 100644 --- a/src/libnm-systemd-shared/src/basic/locale-util.c +++ b/src/libnm-systemd-shared/src/basic/locale-util.c @@ -12,7 +12,7 @@ #include <sys/mman.h> #include <sys/stat.h> -#include "def.h" +#include "constants.h" #include "dirent-util.h" #include "env-util.h" #include "fd-util.h" @@ -98,7 +98,7 @@ static int add_locales_from_archive(Set *locales) { const struct locarhead *h; const struct namehashent *e; const void *p = MAP_FAILED; - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; size_t sz = 0; struct stat st; int r; @@ -290,8 +290,9 @@ void init_gettext(void) { #endif /* NM_IGNORED */ bool is_locale_utf8(void) { - const char *set; static int cached_answer = -1; + const char *set; + int r; /* Note that we default to 'true' here, since today UTF8 is * pretty much supported everywhere. */ @@ -299,6 +300,13 @@ bool is_locale_utf8(void) { if (cached_answer >= 0) goto out; + r = getenv_bool_secure("SYSTEMD_UTF8"); + if (r >= 0) { + cached_answer = r; + goto out; + } else if (r != -ENXIO) + log_debug_errno(r, "Failed to parse $SYSTEMD_UTF8, ignoring: %m"); + if (!setlocale(LC_ALL, "")) { cached_answer = true; goto out; diff --git a/src/libnm-systemd-shared/src/basic/lock-util.h b/src/libnm-systemd-shared/src/basic/lock-util.h new file mode 100644 index 00000000..e7744476 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/lock-util.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <fcntl.h> + +typedef struct LockFile { + int dir_fd; + char *path; + int fd; + int operation; +} LockFile; + +int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret); +static inline int make_lock_file(const char *p, int operation, LockFile *ret) { + return make_lock_file_at(AT_FDCWD, p, operation, ret); +} +int make_lock_file_for(const char *p, int operation, LockFile *ret); +void release_lock_file(LockFile *f); + +#define LOCK_FILE_INIT { .dir_fd = -EBADF, .fd = -EBADF } + +/* POSIX locks with the same interface as flock(). */ +int posix_lock(int fd, int operation); +void posix_unlockpp(int **fd); + +#define CLEANUP_POSIX_UNLOCK(fd) \ + _cleanup_(posix_unlockpp) _unused_ int *CONCATENATE(_cleanup_posix_unlock_, UNIQ) = &(fd) + +/* Open File Description locks with the same interface as flock(). */ +int unposix_lock(int fd, int operation); +void unposix_unlockpp(int **fd); + +#define CLEANUP_UNPOSIX_UNLOCK(fd) \ + _cleanup_(unposix_unlockpp) _unused_ int *CONCATENATE(_cleanup_unposix_unlock_, UNIQ) = &(fd) + +typedef enum LockType { + LOCK_BSD, + LOCK_POSIX, + LOCK_UNPOSIX, +} LockType; + +int lock_generic(int fd, LockType type, int operation); diff --git a/src/libnm-systemd-shared/src/basic/log.h b/src/libnm-systemd-shared/src/basic/log.h index a6adacf6..eb7b51cb 100644 --- a/src/libnm-systemd-shared/src/basic/log.h +++ b/src/libnm-systemd-shared/src/basic/log.h @@ -7,8 +7,10 @@ #include <string.h> #include <syslog.h> +#include "list.h" #include "macro.h" #include "ratelimit.h" +#include "stdio-util.h" /* Some structures we reference but don't want to pull in headers for */ struct iovec; @@ -49,8 +51,10 @@ static inline void clear_log_syntax_callback(dummy_t *dummy) { const char *log_target_to_string(LogTarget target) _const_; LogTarget log_target_from_string(const char *s) _pure_; void log_set_target(LogTarget target); +void log_set_target_and_open(LogTarget target); int log_set_target_from_string(const char *e); LogTarget log_get_target(void) _pure_; +void log_settle_target(void); void log_set_max_level(int level); int log_set_max_level_from_string(const char *e); @@ -432,6 +436,7 @@ int log_emergency_level(void); #define log_oom() log_oom_internal(LOG_ERR, PROJECT_FILE, __LINE__, __func__) #define log_oom_debug() log_oom_internal(LOG_DEBUG, PROJECT_FILE, __LINE__, __func__) +#define log_oom_warning() log_oom_internal(LOG_WARNING, PROJECT_FILE, __LINE__, __func__) bool log_on_console(void) _pure_; @@ -516,15 +521,12 @@ typedef struct LogRateLimit { RateLimit ratelimit; } LogRateLimit; -#define log_ratelimit_internal(_level, _error, _format, _file, _line, _func, ...) \ +#define log_ratelimit_internal(_level, _error, _ratelimit, _format, _file, _line, _func, ...) \ ({ \ int _log_ratelimit_error = (_error); \ int _log_ratelimit_level = (_level); \ static LogRateLimit _log_ratelimit = { \ - .ratelimit = { \ - .interval = 1 * USEC_PER_SEC, \ - .burst = 1, \ - }, \ + .ratelimit = (_ratelimit), \ }; \ unsigned _num_dropped_errors = ratelimit_num_dropped(&_log_ratelimit.ratelimit); \ if (_log_ratelimit_error != _log_ratelimit.error || _log_ratelimit_level != _log_ratelimit.level) { \ @@ -532,18 +534,145 @@ typedef struct LogRateLimit { _log_ratelimit.error = _log_ratelimit_error; \ _log_ratelimit.level = _log_ratelimit_level; \ } \ - if (ratelimit_below(&_log_ratelimit.ratelimit)) \ + if (log_get_max_level() == LOG_DEBUG || ratelimit_below(&_log_ratelimit.ratelimit)) \ _log_ratelimit_error = _num_dropped_errors > 0 \ - ? log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format " (Dropped %u similar message(s))", __VA_ARGS__, _num_dropped_errors) \ - : log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format, __VA_ARGS__); \ + ? log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format " (Dropped %u similar message(s))", ##__VA_ARGS__, _num_dropped_errors) \ + : log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format, ##__VA_ARGS__); \ _log_ratelimit_error; \ }) -#define log_ratelimit_full_errno(level, error, format, ...) \ +#define log_ratelimit_full_errno(level, error, _ratelimit, format, ...) \ ({ \ int _level = (level), _e = (error); \ _e = (log_get_max_level() >= LOG_PRI(_level)) \ - ? log_ratelimit_internal(_level, _e, format, PROJECT_FILE, __LINE__, __func__, __VA_ARGS__) \ + ? log_ratelimit_internal(_level, _e, _ratelimit, format, PROJECT_FILE, __LINE__, __func__, ##__VA_ARGS__) \ : -ERRNO_VALUE(_e); \ _e < 0 ? _e : -ESTRPIPE; \ }) + +#define log_ratelimit_full(level, _ratelimit, format, ...) \ + log_ratelimit_full_errno(level, 0, _ratelimit, format, ##__VA_ARGS__) + +/* Normal logging */ +#define log_ratelimit_info(...) log_ratelimit_full(LOG_INFO, __VA_ARGS__) +#define log_ratelimit_notice(...) log_ratelimit_full(LOG_NOTICE, __VA_ARGS__) +#define log_ratelimit_warning(...) log_ratelimit_full(LOG_WARNING, __VA_ARGS__) +#define log_ratelimit_error(...) log_ratelimit_full(LOG_ERR, __VA_ARGS__) +#define log_ratelimit_emergency(...) log_ratelimit_full(log_emergency_level(), __VA_ARGS__) + +/* Logging triggered by an errno-like error */ +#define log_ratelimit_info_errno(error, ...) log_ratelimit_full_errno(LOG_INFO, error, __VA_ARGS__) +#define log_ratelimit_notice_errno(error, ...) log_ratelimit_full_errno(LOG_NOTICE, error, __VA_ARGS__) +#define log_ratelimit_warning_errno(error, ...) log_ratelimit_full_errno(LOG_WARNING, error, __VA_ARGS__) +#define log_ratelimit_error_errno(error, ...) log_ratelimit_full_errno(LOG_ERR, error, __VA_ARGS__) +#define log_ratelimit_emergency_errno(error, ...) log_ratelimit_full_errno(log_emergency_level(), error, __VA_ARGS__) + +const char *_log_set_prefix(const char *prefix, bool force); +static inline const char *_log_unset_prefixp(const char **p) { + assert(p); + _log_set_prefix(*p, true); + return NULL; +} + +#define LOG_SET_PREFIX(prefix) \ + _cleanup_(_log_unset_prefixp) _unused_ const char *CONCATENATE(_cleanup_log_unset_prefix_, UNIQ) = _log_set_prefix(prefix, false); + +/* + * The log context allows attaching extra metadata to log messages written to the journal via log.h. We keep + * track of a thread local log context onto which we can push extra metadata fields that should be logged. + * + * LOG_CONTEXT_PUSH() will add the provided field to the log context and will remove it again when the + * current block ends. LOG_CONTEXT_PUSH_STRV() will do the same but for all fields in the given strv. + * LOG_CONTEXT_PUSHF() is like LOG_CONTEXT_PUSH() but takes a format string and arguments. + * + * Using the macros is as simple as putting them anywhere inside a block to add a field to all following log + * messages logged from inside that block. + * + * void myfunction(...) { + * ... + * + * LOG_CONTEXT_PUSHF("MYMETADATA=%s", "abc"); + * + * // Every journal message logged will now have the MYMETADATA=abc + * // field included. + * } + * + * One special case to note is async code, where we use callbacks that are invoked to continue processing + * when some event occurs. For async code, there's usually an associated "userdata" struct containing all the + * information associated with the async operation. In this "userdata" struct, we can store a log context + * allocated with log_context_new() and freed with log_context_free(). We can then add and remove fields to + * the `fields` member of the log context object and all those fields will be logged along with each log + * message. + */ + +typedef struct LogContext LogContext; + +bool log_context_enabled(void); + +LogContext* log_context_new(const char *key, const char *value); +LogContext* log_context_new_strv(char **fields, bool owned); +LogContext* log_context_new_iov(struct iovec *input_iovec, size_t n_input_iovec, bool owned); + +/* Same as log_context_new(), but frees the given fields strv/iovec on failure. */ +LogContext* log_context_new_strv_consume(char **fields); +LogContext* log_context_new_iov_consume(struct iovec *input_iovec, size_t n_input_iovec); + +LogContext *log_context_ref(LogContext *c); +LogContext *log_context_unref(LogContext *c); + +DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_unref); + +/* Returns the number of attached log context objects. */ +size_t log_context_num_contexts(void); +/* Returns the number of fields in all attached log contexts. */ +size_t log_context_num_fields(void); + +#define LOG_CONTEXT_PUSH(...) \ + LOG_CONTEXT_PUSH_STRV(STRV_MAKE(__VA_ARGS__)) + +#define LOG_CONTEXT_PUSHF(...) \ + LOG_CONTEXT_PUSH(snprintf_ok((char[LINE_MAX]) {}, LINE_MAX, __VA_ARGS__)) + +#define _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, c) \ + _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new(key, value); + +#define LOG_CONTEXT_PUSH_KEY_VALUE(key, value) \ + _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, UNIQ_T(c, UNIQ)) + +#define _LOG_CONTEXT_PUSH_STRV(strv, c) \ + _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv(strv, /*owned=*/ false); + +#define LOG_CONTEXT_PUSH_STRV(strv) \ + _LOG_CONTEXT_PUSH_STRV(strv, UNIQ_T(c, UNIQ)) + +#define _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, c) \ + _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov(input_iovec, n_input_iovec, /*owned=*/ false); + +#define LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec) \ + _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ)) + +/* LOG_CONTEXT_CONSUME_STR()/LOG_CONTEXT_CONSUME_STRV()/LOG_CONTEXT_CONSUME_IOV() are identical to + * LOG_CONTEXT_PUSH_STR()/LOG_CONTEXT_PUSH_STRV()/LOG_CONTEXT_PUSH_IOV() except they take ownership of the + * given str/strv argument. + */ + +#define _LOG_CONTEXT_CONSUME_STR(s, c, strv) \ + _unused_ _cleanup_strv_free_ strv = strv_new(s); \ + if (!strv) \ + free(s); \ + _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(TAKE_PTR(strv)) + +#define LOG_CONTEXT_CONSUME_STR(s) \ + _LOG_CONTEXT_CONSUME_STR(s, UNIQ_T(c, UNIQ), UNIQ_T(sv, UNIQ)) + +#define _LOG_CONTEXT_CONSUME_STRV(strv, c) \ + _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(strv); + +#define LOG_CONTEXT_CONSUME_STRV(strv) \ + _LOG_CONTEXT_CONSUME_STRV(strv, UNIQ_T(c, UNIQ)) + +#define _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, c) \ + _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov_consume(input_iovec, n_input_iovec); + +#define LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec) \ + _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ)) diff --git a/src/libnm-systemd-shared/src/basic/macro.h b/src/libnm-systemd-shared/src/basic/macro.h index 7380da10..ce7350cb 100644 --- a/src/libnm-systemd-shared/src/basic/macro.h +++ b/src/libnm-systemd-shared/src/basic/macro.h @@ -9,32 +9,9 @@ #include <sys/sysmacros.h> #include <sys/types.h> +#include "constants.h" #include "macro-fundamental.h" -#if !defined(HAS_FEATURE_MEMORY_SANITIZER) -# if defined(__has_feature) -# if __has_feature(memory_sanitizer) -# define HAS_FEATURE_MEMORY_SANITIZER 1 -# endif -# endif -# if !defined(HAS_FEATURE_MEMORY_SANITIZER) -# define HAS_FEATURE_MEMORY_SANITIZER 0 -# endif -#endif - -#if !defined(HAS_FEATURE_ADDRESS_SANITIZER) -# ifdef __SANITIZE_ADDRESS__ -# define HAS_FEATURE_ADDRESS_SANITIZER 1 -# elif defined(__has_feature) -# if __has_feature(address_sanitizer) -# define HAS_FEATURE_ADDRESS_SANITIZER 1 -# endif -# endif -# if !defined(HAS_FEATURE_ADDRESS_SANITIZER) -# define HAS_FEATURE_ADDRESS_SANITIZER 0 -# endif -#endif - /* Note: on GCC "no_sanitize_address" is a function attribute only, on llvm it may also be applied to global * variables. We define a specific macro which knows this. Note that on GCC we don't need this decorator so much, since * our primary usecase for this attribute is registration structures placed in named ELF sections which shall not be @@ -88,10 +65,14 @@ _Pragma("GCC diagnostic push") #endif -#define DISABLE_WARNING_TYPE_LIMITS \ +#define DISABLE_WARNING_TYPE_LIMITS \ _Pragma("GCC diagnostic push"); \ _Pragma("GCC diagnostic ignored \"-Wtype-limits\"") +#define DISABLE_WARNING_ADDRESS \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Waddress\"") + #define REENABLE_WARNING \ _Pragma("GCC diagnostic pop") #else @@ -203,12 +184,12 @@ static inline int __coverity_check_and_return__(int condition) { #define assert_message_se(expr, message) \ do { \ if (_unlikely_(!(expr))) \ - log_assert_failed(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__); \ + log_assert_failed(message, PROJECT_FILE, __LINE__, __func__); \ } while (false) #define assert_log(expr, message) ((_likely_(expr)) \ ? (true) \ - : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__), false)) + : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __func__), false)) #endif /* __COVERITY__ */ @@ -223,7 +204,7 @@ static inline int __coverity_check_and_return__(int condition) { #endif #define assert_not_reached() \ - log_assert_failed_unreachable(PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__) + log_assert_failed_unreachable(PROJECT_FILE, __LINE__, __func__) #define assert_return(expr, r) \ do { \ @@ -276,6 +257,11 @@ static inline int __coverity_check_and_return__(int condition) { #define char_array_0(x) x[sizeof(x)-1] = 0; #define sizeof_field(struct_type, member) sizeof(((struct_type *) 0)->member) +#define endoffsetof_field(struct_type, member) (offsetof(struct_type, member) + sizeof_field(struct_type, member)) + +/* Maximum buffer size needed for formatting an unsigned integer type as hex, including space for '0x' + * prefix and trailing NUL suffix. */ +#define HEXADECIMAL_STR_MAX(type) (2 + sizeof(type) * 2 + 1) /* Returns the number of chars needed to format variables of the specified type as a decimal string. Adds in * extra space for a negative '-' prefix for signed types. Includes space for the trailing NUL. */ @@ -324,19 +310,14 @@ static inline int __coverity_check_and_return__(int condition) { p != (typeof(p)) POINTER_MAX; \ p = *(++_l)) -/* Define C11 thread_local attribute even on older gcc compiler - * version */ -#ifndef thread_local -/* - * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__ - * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769 - */ -#if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16)) -#define thread_local _Thread_local -#else -#define thread_local __thread -#endif -#endif +#define _FOREACH_ARRAY(i, array, num, m, end) \ + for (typeof(array[0]) *i = (array), *end = ({ \ + typeof(num) m = (num); \ + (i && m > 0) ? i + m : NULL; \ + }); end && i < end; i++) + +#define FOREACH_ARRAY(i, array, num) \ + _FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ)) #define DEFINE_TRIVIAL_DESTRUCTOR(name, type, func) \ static inline void name(type *p) { \ @@ -350,10 +331,14 @@ static inline int __coverity_check_and_return__(int condition) { *p = func(*p); \ } -/* When func() doesn't return the appropriate type, set variable to empty afterwards */ +/* When func() doesn't return the appropriate type, set variable to empty afterwards. + * The func() may be provided by a dynamically loaded shared library, hence add an assertion. */ #define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(type, func, empty) \ static inline void func##p(type *p) { \ if (*p != (empty)) { \ + DISABLE_WARNING_ADDRESS; \ + assert(func); \ + REENABLE_WARNING; \ func(*p); \ *p = (empty); \ } \ @@ -442,7 +427,7 @@ typedef struct { assert_cc(sizeof(dummy_t) == 0); -/* A little helper for subtracting 1 off a pointer in a safe UB-free way. This is intended to be used for for +/* A little helper for subtracting 1 off a pointer in a safe UB-free way. This is intended to be used for * loops that count down from a high pointer until some base. A naive loop would implement this like this: * * for (p = end-1; p >= base; p--) … @@ -458,4 +443,13 @@ assert_cc(sizeof(dummy_t) == 0); _q && _q > (base) ? &_q[-1] : NULL; \ }) +/* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly + * convertible. The iteration variable 'entry' must already be defined. */ +#define VA_ARGS_FOREACH(entry, ...) \ + _VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), ##__VA_ARGS__) +#define _VA_ARGS_FOREACH(entry, _entries_, _current_, ...) \ + for (typeof(entry) _entries_[] = { __VA_ARGS__ }, *_current_ = _entries_; \ + ((long)(_current_ - _entries_) < (long)ELEMENTSOF(_entries_)) && ({ entry = *_current_; true; }); \ + _current_++) + #include "log.h" diff --git a/src/libnm-systemd-shared/src/basic/memory-util.c b/src/libnm-systemd-shared/src/basic/memory-util.c index 2d41a26a..c1e0a742 100644 --- a/src/libnm-systemd-shared/src/basic/memory-util.c +++ b/src/libnm-systemd-shared/src/basic/memory-util.c @@ -5,6 +5,7 @@ #include <unistd.h> #include "memory-util.h" +#include "missing_threads.h" size_t page_size(void) { static thread_local size_t pgsz = 0; @@ -40,21 +41,3 @@ bool memeqbyte(uint8_t byte, const void *data, size_t length) { /* Now we know first 16 bytes match, memcmp() with self. */ return memcmp(data, p + 16, length) == 0; } - -#if !HAVE_EXPLICIT_BZERO -/* - * The pointer to memset() is volatile so that compiler must de-reference the pointer and can't assume that - * it points to any function in particular (such as memset(), which it then might further "optimize"). This - * approach is inspired by openssl's crypto/mem_clr.c. - */ -typedef void *(*memset_t)(void *,int,size_t); - -static volatile memset_t memset_func = memset; - -void* explicit_bzero_safe(void *p, size_t l) { - if (l > 0) - memset_func(p, '\0', l); - - return p; -} -#endif diff --git a/src/libnm-systemd-shared/src/basic/memory-util.h b/src/libnm-systemd-shared/src/basic/memory-util.h index 6e3280b9..d26a0918 100644 --- a/src/libnm-systemd-shared/src/basic/memory-util.h +++ b/src/libnm-systemd-shared/src/basic/memory-util.h @@ -9,6 +9,7 @@ #include "alloc-util.h" #include "macro.h" +#include "memory-util-fundamental.h" size_t page_size(void) _pure_; #define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) @@ -91,17 +92,6 @@ static inline void *mempmem_safe(const void *haystack, size_t haystacklen, const return (uint8_t*) p + needlelen; } -#if HAVE_EXPLICIT_BZERO -static inline void* explicit_bzero_safe(void *p, size_t l) { - if (l > 0) - explicit_bzero(p, l); - - return p; -} -#else -void *explicit_bzero_safe(void *p, size_t l); -#endif - static inline void* erase_and_free(void *p) { size_t l; @@ -121,3 +111,37 @@ static inline void erase_and_freep(void *p) { static inline void erase_char(char *p) { explicit_bzero_safe(p, sizeof(char)); } + +/* An automatic _cleanup_-like logic for destroy arrays (i.e. pointers + size) when leaving scope */ +typedef struct ArrayCleanup { + void **parray; + size_t *pn; + free_array_func_t pfunc; +} ArrayCleanup; + +static inline void array_cleanup(const ArrayCleanup *c) { + assert(c); + + assert(!c->parray == !c->pn); + + if (!c->parray) + return; + + if (*c->parray) { + assert(c->pfunc); + c->pfunc(*c->parray, *c->pn); + *c->parray = NULL; + } + + *c->pn = 0; +} + +#define CLEANUP_ARRAY(array, n, func) \ + _cleanup_(array_cleanup) _unused_ const ArrayCleanup CONCATENATE(_cleanup_array_, UNIQ) = { \ + .parray = (void**) &(array), \ + .pn = &(n), \ + .pfunc = (free_array_func_t) ({ \ + void (*_f)(typeof(array[0]) *a, size_t b) = func; \ + _f; \ + }), \ + } diff --git a/src/libnm-systemd-shared/src/basic/mempool.c b/src/libnm-systemd-shared/src/basic/mempool.c index 53a719e0..e467d706 100644 --- a/src/libnm-systemd-shared/src/basic/mempool.c +++ b/src/libnm-systemd-shared/src/basic/mempool.c @@ -5,6 +5,7 @@ #include <stdint.h> #include <stdlib.h> +#include "format-util.h" #include "macro.h" #include "memory-util.h" #include "mempool.h" @@ -15,21 +16,26 @@ struct pool { size_t n_used; }; +static void* pool_ptr(struct pool *p) { + return ((uint8_t*) ASSERT_PTR(p)) + ALIGN(sizeof(struct pool)); +} + void* mempool_alloc_tile(struct mempool *mp) { size_t i; /* When a tile is released we add it to the list and simply * place the next pointer at its offset 0. */ + assert(mp); assert(mp->tile_size >= sizeof(void*)); assert(mp->at_least > 0); if (mp->freelist) { - void *r; + void *t; - r = mp->freelist; - mp->freelist = * (void**) mp->freelist; - return r; + t = mp->freelist; + mp->freelist = *(void**) mp->freelist; + return t; } if (_unlikely_(!mp->first_pool) || @@ -55,7 +61,7 @@ void* mempool_alloc_tile(struct mempool *mp) { i = mp->first_pool->n_used++; - return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size; + return (uint8_t*) pool_ptr(mp->first_pool) + i*mp->tile_size; } void* mempool_alloc0_tile(struct mempool *mp) { @@ -67,19 +73,105 @@ void* mempool_alloc0_tile(struct mempool *mp) { return p; } -void mempool_free_tile(struct mempool *mp, void *p) { - * (void**) p = mp->freelist; +void* mempool_free_tile(struct mempool *mp, void *p) { + assert(mp); + + if (!p) + return NULL; + + *(void**) p = mp->freelist; mp->freelist = p; + + return NULL; +} + +static bool pool_contains(struct mempool *mp, struct pool *p, void *ptr) { + size_t off; + void *a; + + assert(mp); + assert(p); + + if (!ptr) + return false; + + a = pool_ptr(p); + if ((uint8_t*) ptr < (uint8_t*) a) + return false; + + off = (uint8_t*) ptr - (uint8_t*) a; + if (off >= mp->tile_size * p->n_tiles) + return false; + + assert(off % mp->tile_size == 0); + return true; +} + +static bool pool_is_unused(struct mempool *mp, struct pool *p) { + assert(mp); + assert(p); + + if (p->n_used == 0) + return true; + + /* Check if all tiles in this specific pool are in the freelist. */ + size_t n = 0; + void *i = mp->freelist; + while (i) { + if (pool_contains(mp, p, i)) + n++; + + i = *(void**) i; + } + + assert(n <= p->n_used); + + return n == p->n_used; +} + +static void pool_unlink(struct mempool *mp, struct pool *p) { + size_t m = 0; + + assert(mp); + assert(p); + + if (p->n_used == 0) + return; + + void **i = &mp->freelist; + while (*i) { + void *d = *i; + + if (pool_contains(mp, p, d)) { + *i = *(void**) d; + m++; + + if (m == p->n_used) + break; + } else + i = (void**) d; + } } -#if VALGRIND -void mempool_drop(struct mempool *mp) { - struct pool *p = mp->first_pool; - while (p) { - struct pool *n; - n = p->next; - free(p); - p = n; +void mempool_trim(struct mempool *mp) { + size_t trimmed = 0, left = 0; + + assert(mp); + + struct pool **p = &mp->first_pool; + while (*p) { + struct pool *d = *p; + + if (pool_is_unused(mp, d)) { + trimmed += d->n_tiles * mp->tile_size; + pool_unlink(mp, d); + *p = d->next; + free(d); + } else { + left += d->n_tiles * mp->tile_size; + p = &d->next; + } } + + log_debug("Trimmed %s from memory pool %p. (%s left)", FORMAT_BYTES(trimmed), mp, FORMAT_BYTES(left)); } -#endif diff --git a/src/libnm-systemd-shared/src/basic/mempool.h b/src/libnm-systemd-shared/src/basic/mempool.h index 539ccbdf..ba588af4 100644 --- a/src/libnm-systemd-shared/src/basic/mempool.h +++ b/src/libnm-systemd-shared/src/basic/mempool.h @@ -10,12 +10,12 @@ struct mempool { struct pool *first_pool; void *freelist; size_t tile_size; - unsigned at_least; + size_t at_least; }; void* mempool_alloc_tile(struct mempool *mp); void* mempool_alloc0_tile(struct mempool *mp); -void mempool_free_tile(struct mempool *mp, void *p); +void* mempool_free_tile(struct mempool *mp, void *p); #define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \ static struct mempool pool_name = { \ @@ -25,6 +25,4 @@ static struct mempool pool_name = { \ __attribute__((weak)) bool mempool_enabled(void); -#if VALGRIND -void mempool_drop(struct mempool *mp); -#endif +void mempool_trim(struct mempool *mp); diff --git a/src/libnm-systemd-shared/src/basic/missing_fcntl.h b/src/libnm-systemd-shared/src/basic/missing_fcntl.h index 00937d2a..24b2dc31 100644 --- a/src/libnm-systemd-shared/src/basic/missing_fcntl.h +++ b/src/libnm-systemd-shared/src/basic/missing_fcntl.h @@ -25,6 +25,14 @@ #define F_SEAL_WRITE 0x0008 /* prevent writes */ #endif +#ifndef F_SEAL_FUTURE_WRITE +#define F_SEAL_FUTURE_WRITE 0x0010 /* prevent future writes while mapped */ +#endif + +#ifndef F_SEAL_EXEC +#define F_SEAL_EXEC 0x0020 /* prevent chmod modifying exec bits */ +#endif + #ifndef F_OFD_GETLK #define F_OFD_GETLK 36 #define F_OFD_SETLK 37 @@ -58,3 +66,12 @@ #ifndef O_TMPFILE #define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) #endif + +/* So O_LARGEFILE is generally implied by glibc, and defined to zero hence, because we only build in LFS + * mode. However, when invoking fcntl(F_GETFL) the flag is ORed into the result anyway — glibc does not mask + * it away. Which sucks. Let's define the actual value here, so that we can mask it ourselves. */ +#if O_LARGEFILE != 0 +#define RAW_O_LARGEFILE O_LARGEFILE +#else +#define RAW_O_LARGEFILE 0100000 +#endif diff --git a/src/libnm-systemd-shared/src/basic/missing_syscall.h b/src/libnm-systemd-shared/src/basic/missing_syscall.h index 107f4b01..610a7cef 100644 --- a/src/libnm-systemd-shared/src/basic/missing_syscall.h +++ b/src/libnm-systemd-shared/src/basic/missing_syscall.h @@ -370,6 +370,20 @@ static inline int missing_rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t *info) /* ======================================================================= */ #if 0 /* NM_IGNORED */ +#if !HAVE_RT_TGSIGQUEUEINFO +static inline int missing_rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info) { +# if defined __NR_rt_tgsigqueueinfo && __NR_rt_tgsigqueueinfo >= 0 + return syscall(__NR_rt_tgsigqueueinfo, tgid, tid, sig, info); +# else +# error "__NR_rt_tgsigqueueinfo not defined" +# endif +} + +# define rt_tgsigqueueinfo missing_rt_tgsigqueueinfo +#endif + +/* ======================================================================= */ + #if !HAVE_EXECVEAT static inline int missing_execveat(int dirfd, const char *pathname, char *const argv[], char *const envp[], @@ -419,44 +433,6 @@ static inline int missing_close_range(int first_fd, int end_fd, unsigned flags) /* ======================================================================= */ -#if !HAVE_EPOLL_PWAIT2 - -/* Defined to be equivalent to the kernel's _NSIG_WORDS, i.e. the size of the array of longs that is - * encapsulated by sigset_t. */ -#define KERNEL_NSIG_WORDS (64 / (sizeof(long) * 8)) -#define KERNEL_NSIG_BYTES (KERNEL_NSIG_WORDS * sizeof(long)) - -struct epoll_event; - -static inline int missing_epoll_pwait2( - int fd, - struct epoll_event *events, - int maxevents, - const struct timespec *timeout, - const sigset_t *sigset) { - -# if defined(__NR_epoll_pwait2) && HAVE_LINUX_TIME_TYPES_H - if (timeout) { - /* Convert from userspace timespec to kernel timespec */ - struct __kernel_timespec ts = { - .tv_sec = timeout->tv_sec, - .tv_nsec = timeout->tv_nsec, - }; - - return syscall(__NR_epoll_pwait2, fd, events, maxevents, &ts, sigset, sigset ? KERNEL_NSIG_BYTES : 0); - } else - return syscall(__NR_epoll_pwait2, fd, events, maxevents, NULL, sigset, sigset ? KERNEL_NSIG_BYTES : 0); -# else - errno = ENOSYS; - return -1; -# endif -} - -# define epoll_pwait2 missing_epoll_pwait2 -#endif - -/* ======================================================================= */ - #if !HAVE_MOUNT_SETATTR #if !HAVE_STRUCT_MOUNT_ATTR @@ -600,6 +576,78 @@ static inline int missing_move_mount( /* ======================================================================= */ +#if !HAVE_FSOPEN + +#ifndef FSOPEN_CLOEXEC +#define FSOPEN_CLOEXEC 0x00000001 +#endif + +static inline int missing_fsopen(const char *fsname, unsigned flags) { +# if defined __NR_fsopen && __NR_fsopen >= 0 + return syscall(__NR_fsopen, fsname, flags); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define fsopen missing_fsopen +#endif + +/* ======================================================================= */ + +#if !HAVE_FSCONFIG + +#ifndef FSCONFIG_SET_FLAG +#define FSCONFIG_SET_FLAG 0 /* Set parameter, supplying no value */ +#endif + +#ifndef FSCONFIG_SET_STRING +#define FSCONFIG_SET_STRING 1 /* Set parameter, supplying a string value */ +#endif + +#ifndef FSCONFIG_SET_FD +#define FSCONFIG_SET_FD 5 /* Set parameter, supplying an object by fd */ +#endif + +#ifndef FSCONFIG_CMD_CREATE +#define FSCONFIG_CMD_CREATE 6 /* Invoke superblock creation */ +#endif + +static inline int missing_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux) { +# if defined __NR_fsconfig && __NR_fsconfig >= 0 + return syscall(__NR_fsconfig, fd, cmd, key, value, aux); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define fsconfig missing_fsconfig +#endif + +/* ======================================================================= */ + +#if !HAVE_FSMOUNT + +#ifndef FSMOUNT_CLOEXEC +#define FSMOUNT_CLOEXEC 0x00000001 +#endif + +static inline int missing_fsmount(int fd, unsigned flags, unsigned ms_flags) { +# if defined __NR_fsmount && __NR_fsmount >= 0 + return syscall(__NR_fsmount, fd, flags, ms_flags); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define fsmount missing_fsmount +#endif + +/* ======================================================================= */ + #if !HAVE_GETDENTS64 static inline ssize_t missing_getdents64(int fd, void *buffer, size_t length) { diff --git a/src/libnm-systemd-shared/src/basic/missing_threads.h b/src/libnm-systemd-shared/src/basic/missing_threads.h new file mode 100644 index 00000000..fb3b7224 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/missing_threads.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* If threads.h doesn't exist, then define our own thread_local to match C11's thread_local. */ +#if HAVE_THREADS_H +# include <threads.h> +#elif !(defined(thread_local)) +/* Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__ + * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769 */ +# if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16)) +# define thread_local _Thread_local +# else +# define thread_local __thread +# endif +#endif diff --git a/src/libnm-systemd-shared/src/basic/origin-id.h b/src/libnm-systemd-shared/src/basic/origin-id.h new file mode 100644 index 00000000..c55b0a36 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/origin-id.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <pthread.h> + +#include "random-util.h" + +/* This pattern needs to be repeated exactly in multiple modules, so macro it. + * To ensure an object is not passed into a different module (e.g.: when two shared objects statically + * linked to libsystemd get loaded in the same process, and the object created by one is passed to the + * other, see https://github.com/systemd/systemd/issues/27216), create a random static global random + * (mixed with PID, so that we can also check for reuse after fork) that is stored in the object and + * checked by public API on use. */ +#define _DEFINE_ORIGIN_ID_HELPERS(type, name, scope) \ +static uint64_t origin_id; \ + \ +static void origin_id_initialize(void) { \ + origin_id = random_u64(); \ +} \ + \ +static uint64_t origin_id_query(void) { \ + static pthread_once_t once = PTHREAD_ONCE_INIT; \ + assert_se(pthread_once(&once, origin_id_initialize) == 0); \ + return origin_id ^ getpid_cached(); \ +} \ + \ +scope bool name##_origin_changed(type *p) { \ + assert(p); \ + return p->origin_id != origin_id_query(); \ +} + +#define DEFINE_ORIGIN_ID_HELPERS(type, name) \ + _DEFINE_ORIGIN_ID_HELPERS(type, name,); + +#define DEFINE_PRIVATE_ORIGIN_ID_HELPERS(type, name) \ + _DEFINE_ORIGIN_ID_HELPERS(type, name, static); diff --git a/src/libnm-systemd-shared/src/basic/parse-util.c b/src/libnm-systemd-shared/src/basic/parse-util.c index 968f01c6..2b22039c 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.c +++ b/src/libnm-systemd-shared/src/basic/parse-util.c @@ -53,7 +53,6 @@ int parse_pid(const char *s, pid_t* ret_pid) { int r; assert(s); - assert(ret_pid); r = safe_atolu(s, &ul); if (r < 0) @@ -67,7 +66,8 @@ int parse_pid(const char *s, pid_t* ret_pid) { if (!pid_is_valid(pid)) return -ERANGE; - *ret_pid = pid; + if (ret_pid) + *ret_pid = pid; return 0; } @@ -261,6 +261,26 @@ int parse_size(const char *t, uint64_t base, uint64_t *size) { return 0; } +int parse_sector_size(const char *t, uint64_t *ret) { + int r; + + assert(t); + assert(ret); + + uint64_t ss; + + r = safe_atou64(t, &ss); + if (r < 0) + return log_error_errno(r, "Failed to parse sector size parameter %s", t); + if (ss < 512 || ss > 4096) /* Allow up to 4K due to dm-crypt support and 4K alignment by the homed LUKS backend */ + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Sector size not between 512 and 4096: %s", t); + if (!ISPOWEROF2(ss)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Sector size not power of 2: %s", t); + + *ret = ss; + return 0; +} + int parse_range(const char *t, unsigned *lower, unsigned *upper) { _cleanup_free_ char *word = NULL; unsigned l, u; @@ -319,6 +339,21 @@ int parse_errno(const char *t) { } #endif /* NM_IGNORED */ +int parse_fd(const char *t) { + int r, fd; + + assert(t); + + r = safe_atoi(t, &fd); + if (r < 0) + return r; + + if (fd < 0) + return -EBADF; + + return fd; +} + static const char *mangle_base(const char *s, unsigned *base) { const char *k; diff --git a/src/libnm-systemd-shared/src/basic/parse-util.h b/src/libnm-systemd-shared/src/basic/parse-util.h index 8d8d5232..c480407c 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.h +++ b/src/libnm-systemd-shared/src/basic/parse-util.h @@ -18,8 +18,10 @@ int parse_ifindex(const char *s); int parse_mtu(int family, const char *s, uint32_t *ret); int parse_size(const char *t, uint64_t base, uint64_t *size); +int parse_sector_size(const char *t, uint64_t *ret); int parse_range(const char *t, unsigned *lower, unsigned *upper); int parse_errno(const char *t); +int parse_fd(const char *t); #define SAFE_ATO_REFUSE_PLUS_MINUS (1U << 30) #define SAFE_ATO_REFUSE_LEADING_ZERO (1U << 29) diff --git a/src/libnm-systemd-shared/src/basic/path-util.c b/src/libnm-systemd-shared/src/basic/path-util.c index b45bb0b5..a2af9e0c 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.c +++ b/src/libnm-systemd-shared/src/basic/path-util.c @@ -10,7 +10,7 @@ #include <unistd.h> #include "alloc-util.h" -#include "chase-symlinks.h" +#include "chase.h" #include "extract-word.h" #include "fd-util.h" #include "fs-util.h" @@ -25,7 +25,7 @@ #if 0 /* NM_IGNORED */ int path_split_and_make_absolute(const char *p, char ***ret) { - char **l; + _cleanup_strv_free_ char **l = NULL; int r; assert(p); @@ -36,12 +36,10 @@ int path_split_and_make_absolute(const char *p, char ***ret) { return -ENOMEM; r = path_strv_make_absolute_cwd(l); - if (r < 0) { - strv_free(l); + if (r < 0) return r; - } - *ret = l; + *ret = TAKE_PTR(l); return r; } @@ -288,7 +286,7 @@ char **path_strv_resolve(char **l, const char *root) { } else t = *s; - r = chase_symlinks(t, root, 0, &u, NULL); + r = chase(t, root, 0, &u, NULL); if (r == -ENOENT) { if (root) { u = TAKE_PTR(orig); @@ -491,29 +489,37 @@ int path_compare(const char *a, const char *b) { } } -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; +bool path_equal_or_inode_same(const char *a, const char *b, int flags) { + return path_equal(a, b) || inode_same(a, b, flags) > 0; } -bool path_equal_filename(const char *a, const char *b) { - _cleanup_free_ char *a_basename = NULL, *b_basename = NULL; - int r; +int path_compare_filename(const char *a, const char *b) { + _cleanup_free_ char *fa = NULL, *fb = NULL; + int r, j, k; - assert(a); - assert(b); + /* Order NULL before non-NULL */ + r = CMP(!!a, !!b); + if (r != 0) + return r; - r = path_extract_filename(a, &a_basename); - if (r < 0) { - log_debug_errno(r, "Failed to parse basename of %s: %m", a); - return false; - } - r = path_extract_filename(b, &b_basename); - if (r < 0) { - log_debug_errno(r, "Failed to parse basename of %s: %m", b); - return false; - } + j = path_extract_filename(a, &fa); + k = path_extract_filename(b, &fb); + + /* When one of paths is "." or root, then order it earlier. */ + r = CMP(j != -EADDRNOTAVAIL, k != -EADDRNOTAVAIL); + if (r != 0) + return r; + + /* When one of paths is invalid (or we get OOM), order invalid path after valid one. */ + r = CMP(j < 0, k < 0); + if (r != 0) + return r; - return path_equal(a_basename, b_basename); + /* fallback to use strcmp() if both paths are invalid. */ + if (j < 0) + return strcmp(a, b); + + return strcmp(fa, fb); } char* path_extend_internal(char **x, ...) { @@ -523,17 +529,17 @@ char* path_extend_internal(char **x, ...) { va_list ap; bool slash; - /* Joins all listed strings until the sentinel and places a "/" between them unless the strings end/begin - * already with one so that it is unnecessary. Note that slashes which are already duplicate won't be - * removed. The string returned is hence always equal to or longer than the sum of the lengths of each - * individual string. + /* Joins all listed strings until the sentinel and places a "/" between them unless the strings + * end/begin already with one so that it is unnecessary. Note that slashes which are already + * duplicate won't be removed. The string returned is hence always equal to or longer than the sum of + * the lengths of the individual strings. * * The first argument may be an already allocated string that is extended via realloc() if * non-NULL. path_extend() and path_join() are macro wrappers around this function, making use of the * first parameter to distinguish the two operations. * - * Note: any listed empty string is simply skipped. This can be useful for concatenating strings of which some - * are optional. + * Note: any listed empty string is simply skipped. This can be useful for concatenating strings of + * which some are optional. * * Examples: * @@ -592,7 +598,7 @@ char* path_extend_internal(char **x, ...) { #if 0 /* NM_IGNORED */ static int check_x_access(const char *path, int *ret_fd) { - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; int r; /* We need to use O_PATH because there may be executables for which we have only exec @@ -620,22 +626,19 @@ static int check_x_access(const char *path, int *ret_fd) { } static int find_executable_impl(const char *name, const char *root, char **ret_filename, int *ret_fd) { - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; _cleanup_free_ char *path_name = NULL; int r; assert(name); - /* Function chase_symlinks() is invoked only when root is not NULL, as using it regardless of + /* Function chase() 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 */ + /* prefix root to name in case full paths are not specified */ + r = chase(name, root, CHASE_PREFIX_ROOT, &path_name, /* ret_fd= */ NULL); if (r < 0) return r; @@ -902,6 +905,8 @@ static const char *skip_slash_or_dot_backward(const char *path, const char *q) { continue; if (q > path && strneq(q - 1, "/.", 2)) continue; + if (q == path && *q == '.') + continue; break; } return q; @@ -926,6 +931,12 @@ int path_find_last_component(const char *path, bool accept_dot_dot, const char * * ret: "bbbbb/cc//././" * return value: 5 (== strlen("bbbbb")) * + * Input: path: "//.//aaa///bbbbb/cc//././" + * next: "///bbbbb/cc//././" + * Output: next: "//.//aaa///bbbbb/cc//././" (next == path) + * ret: "aaa///bbbbb/cc//././" + * return value: 3 (== strlen("aaa")) + * * Input: path: "/", ".", "", or NULL * Output: next: equivalent to path * ret: NULL @@ -1171,31 +1182,35 @@ bool path_is_normalized(const char *p) { } #if 0 /* NM_IGNORED */ -char *file_in_same_dir(const char *path, const char *filename) { - char *e, *ret; - size_t k; +int file_in_same_dir(const char *path, const char *filename, char **ret) { + _cleanup_free_ char *b = NULL; + int r; assert(path); assert(filename); + assert(ret); - /* This removes the last component of path and appends - * filename, unless the latter is absolute anyway or the - * former isn't */ + /* This removes the last component of path and appends filename, unless the latter is absolute anyway + * or the former isn't */ if (path_is_absolute(filename)) - return strdup(filename); - - e = strrchr(path, '/'); - if (!e) - return strdup(filename); + b = strdup(filename); + else { + _cleanup_free_ char *dn = NULL; - k = strlen(filename); - ret = new(char, (e + 1 - path) + k + 1); - if (!ret) - return NULL; + r = path_extract_directory(path, &dn); + if (r == -EDESTADDRREQ) /* no path prefix */ + b = strdup(filename); + else if (r < 0) + return r; + else + b = path_join(dn, filename); + } + if (!b) + return -ENOMEM; - memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1); - return ret; + *ret = TAKE_PTR(b); + return 0; } bool hidden_or_backup_file(const char *filename) { diff --git a/src/libnm-systemd-shared/src/basic/path-util.h b/src/libnm-systemd-shared/src/basic/path-util.h index 949fdc86..fee6e8ee 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.h +++ b/src/libnm-systemd-shared/src/basic/path-util.h @@ -68,15 +68,18 @@ char *path_startswith_full(const char *path, const char *prefix, bool accept_dot 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_; +int path_compare(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); +int path_compare_filename(const char *a, const char *b); +static inline bool path_equal_filename(const char *a, const char *b) { + return path_compare_filename(a, b) == 0; +} + +bool path_equal_or_inode_same(const char *a, const char *b, int flags); char* path_extend_internal(char **x, ...); #define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX) @@ -132,6 +135,7 @@ int fsck_exists_for_fstype(const char *fstype); /* Similar to path_join(), but only works for two components, and only the first one may be NULL and returns * an alloca() buffer, or possibly a const pointer into the path parameter. */ +/* DEPRECATED: use path_join() instead */ #define prefix_roota(root, path) \ ({ \ const char* _path = (path), *_root = (root), *_ret; \ @@ -171,7 +175,7 @@ static inline bool path_is_safe(const char *p) { } bool path_is_normalized(const char *p) _pure_; -char *file_in_same_dir(const char *path, const char *filename); +int file_in_same_dir(const char *path, const char *filename, char **ret); bool hidden_or_backup_file(const char *filename) _pure_; diff --git a/src/libnm-systemd-shared/src/basic/prioq.c b/src/libnm-systemd-shared/src/basic/prioq.c index 2f8bdf0f..0af84bd2 100644 --- a/src/libnm-systemd-shared/src/basic/prioq.c +++ b/src/libnm-systemd-shared/src/basic/prioq.c @@ -255,7 +255,7 @@ int prioq_remove(Prioq *q, void *data, unsigned *idx) { return 1; } -int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) { +void prioq_reshuffle(Prioq *q, void *data, unsigned *idx) { struct prioq_item *i; unsigned k; @@ -263,12 +263,11 @@ int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) { i = find_item(q, data, idx); if (!i) - return 0; + return; k = i - q->items; k = shuffle_down(q, k); shuffle_up(q, k); - return 1; } void *prioq_peek_by_index(Prioq *q, unsigned idx) { diff --git a/src/libnm-systemd-shared/src/basic/prioq.h b/src/libnm-systemd-shared/src/basic/prioq.h index 508db880..f66562f3 100644 --- a/src/libnm-systemd-shared/src/basic/prioq.h +++ b/src/libnm-systemd-shared/src/basic/prioq.h @@ -18,7 +18,7 @@ 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); +void prioq_reshuffle(Prioq *q, void *data, unsigned *idx); void *prioq_peek_by_index(Prioq *q, unsigned idx) _pure_; static inline void *prioq_peek(Prioq *q) { diff --git a/src/libnm-systemd-shared/src/basic/process-util.c b/src/libnm-systemd-shared/src/basic/process-util.c index 428323ef..8601e0da 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.c +++ b/src/libnm-systemd-shared/src/basic/process-util.c @@ -10,7 +10,6 @@ #include <stdbool.h> #include <stdio.h> #include <stdlib.h> -#include <sys/mman.h> #include <sys/mount.h> #include <sys/personality.h> #include <sys/prctl.h> @@ -26,19 +25,26 @@ #include "alloc-util.h" #include "architecture.h" +#include "argv-util.h" +#include "env-file.h" #include "env-util.h" #include "errno-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "hostname-util.h" #include "locale-util.h" #include "log.h" #include "macro.h" #include "memory-util.h" #include "missing_sched.h" #include "missing_syscall.h" +#include "missing_threads.h" +#include "mountpoint-util.h" #include "namespace-util.h" +#include "nulstr-util.h" +#include "parse-util.h" #include "path-util.h" #include "process-util.h" #include "raw-clone.h" @@ -222,18 +228,12 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags _cleanup_strv_free_ char **args = NULL; - args = strv_parse_nulstr(t, k); + /* Drop trailing NULs, otherwise strv_parse_nulstr() adds additional empty strings at the end. + * See also issue #21186. */ + args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true); if (!args) return -ENOMEM; - /* Drop trailing empty strings. See issue #21186. */ - STRV_FOREACH_BACKWARDS(p, args) { - if (!isempty(*p)) - break; - - *p = mfree(*p); - } - ans = quote_command_line(args, shflags); if (!ans) return -ENOMEM; @@ -259,149 +259,67 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags return 0; } -static int update_argv(const char name[], size_t l) { - static int can_do = -1; - - if (can_do == 0) - return 0; - can_do = false; /* We'll set it to true only if the whole process works */ - - /* Let's not bother with this if we don't have euid == 0. Strictly speaking we should check for the - * CAP_SYS_RESOURCE capability which is independent of the euid. In our own code the capability generally is - * present only for euid == 0, hence let's use this as quick bypass check, to avoid calling mmap() if - * PR_SET_MM_ARG_{START,END} fails with EPERM later on anyway. After all geteuid() is dead cheap to call, but - * mmap() is not. */ - if (geteuid() != 0) - return log_debug_errno(SYNTHETIC_ERRNO(EPERM), - "Skipping PR_SET_MM, as we don't have privileges."); - - static size_t mm_size = 0; - static char *mm = NULL; +int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) { + _cleanup_free_ char *t = NULL; + char **args; + size_t k; int r; - if (mm_size < l+1) { - size_t nn_size; - char *nn; - - nn_size = PAGE_ALIGN(l+1); - nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - if (nn == MAP_FAILED) - return log_debug_errno(errno, "mmap() failed: %m"); - - strncpy(nn, name, nn_size); - - /* Now, let's tell the kernel about this new memory */ - if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) { - if (ERRNO_IS_PRIVILEGE(errno)) - return log_debug_errno(errno, "PR_SET_MM_ARG_START failed: %m"); - - /* HACK: prctl() API is kind of dumb on this point. The existing end address may already be - * below the desired start address, in which case the kernel may have kicked this back due - * to a range-check failure (see linux/kernel/sys.c:validate_prctl_map() to see this in - * action). The proper solution would be to have a prctl() API that could set both start+end - * simultaneously, or at least let us query the existing address to anticipate this condition - * and respond accordingly. For now, we can only guess at the cause of this failure and try - * a workaround--which will briefly expand the arg space to something potentially huge before - * resizing it to what we want. */ - log_debug_errno(errno, "PR_SET_MM_ARG_START failed, attempting PR_SET_MM_ARG_END hack: %m"); - - if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) { - r = log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m"); - (void) munmap(nn, nn_size); - return r; - } - - if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) - return log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m"); - } else { - /* And update the end pointer to the new end, too. If this fails, we don't really know what - * to do, it's pretty unlikely that we can rollback, hence we'll just accept the failure, - * and continue. */ - if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) - log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m"); - } - - if (mm) - (void) munmap(mm, mm_size); + assert(pid >= 0); + assert((flags & ~PROCESS_CMDLINE_COMM_FALLBACK) == 0); + assert(ret); - mm = nn; - mm_size = nn_size; - } else { - strncpy(mm, name, mm_size); + r = get_process_cmdline_nulstr(pid, SIZE_MAX, flags, &t, &k); + if (r < 0) + return r; - /* Update the end pointer, continuing regardless of any failure. */ - if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) mm + l + 1, 0, 0) < 0) - log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m"); - } + args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true); + if (!args) + return -ENOMEM; - can_do = true; + *ret = args; return 0; } -int rename_process(const char name[]) { - bool truncated = false; - - /* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's - * internally used name of the process. For the first one a limit of 16 chars applies; to the second one in - * many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded; - * to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be - * truncated. - * - * Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */ - - if (isempty(name)) - return -EINVAL; /* let's not confuse users unnecessarily with an empty name */ - - if (!is_main_thread()) - return -EPERM; /* Let's not allow setting the process name from other threads than the main one, as we - * cache things without locking, and we make assumptions that PR_SET_NAME sets the - * process name that isn't correct on any other threads */ - - size_t l = strlen(name); - - /* First step, change the comm field. The main thread's comm is identical to the process comm. This means we - * can use PR_SET_NAME, which sets the thread name for the calling thread. */ - if (prctl(PR_SET_NAME, name) < 0) - log_debug_errno(errno, "PR_SET_NAME failed: %m"); - if (l >= TASK_COMM_LEN) /* Linux userspace process names can be 15 chars at max */ - truncated = true; +int container_get_leader(const char *machine, pid_t *pid) { + _cleanup_free_ char *s = NULL, *class = NULL; + const char *p; + pid_t leader; + int r; - /* Second step, change glibc's ID of the process name. */ - if (program_invocation_name) { - size_t k; + assert(machine); + assert(pid); - k = strlen(program_invocation_name); - strncpy(program_invocation_name, name, k); - if (l > k) - truncated = true; + if (streq(machine, ".host")) { + *pid = 1; + return 0; } - /* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but - * has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at - * the end. This is the best option for changing /proc/self/cmdline. */ - (void) update_argv(name, l); - - /* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if - * it still looks here */ - if (saved_argc > 0) { - if (saved_argv[0]) { - size_t k; - - k = strlen(saved_argv[0]); - strncpy(saved_argv[0], name, k); - if (l > k) - truncated = true; - } + if (!hostname_is_valid(machine, 0)) + return -EINVAL; - for (int i = 1; i < saved_argc; i++) { - if (!saved_argv[i]) - break; + p = strjoina("/run/systemd/machines/", machine); + r = parse_env_file(NULL, p, + "LEADER", &s, + "CLASS", &class); + if (r == -ENOENT) + return -EHOSTDOWN; + if (r < 0) + return r; + if (!s) + return -EIO; - memzero(saved_argv[i], strlen(saved_argv[i])); - } - } + if (!streq_ptr(class, "container")) + return -EIO; - return !truncated; + r = parse_pid(s, &leader); + if (r < 0) + return r; + if (leader <= 1) + return -EIO; + + *pid = leader; + return 0; } int is_kernel_thread(pid_t pid) { @@ -701,6 +619,8 @@ int get_process_umask(pid_t pid, mode_t *ret) { r = get_proc_field(p, "Umask", WHITESPACE, &m); if (r == -ENOENT) return -ESRCH; + if (r < 0) + return r; return parse_mode(m, ret); } @@ -870,6 +790,23 @@ void sigterm_wait(pid_t pid) { (void) wait_for_terminate(pid, NULL); } +void sigkill_nowait(pid_t pid) { + assert(pid > 1); + + (void) kill(pid, SIGKILL); +} + +void sigkill_nowaitp(pid_t *pid) { + PROTECT_ERRNO; + + if (!pid) + return; + if (*pid <= 1) + return; + + sigkill_nowait(*pid); +} + int kill_and_sigcont(pid_t pid, int sig) { int r; @@ -1015,7 +952,7 @@ int pid_from_same_root_fs(pid_t pid) { root = procfs_file_alloca(pid, "root"); - return files_same(root, "/proc/1/root", 0); + return inode_same(root, "/proc/1/root", 0); } #endif /* NM_IGNORED */ @@ -1224,6 +1161,7 @@ static void restore_sigsetp(sigset_t **ssp) { int safe_fork_full( const char *name, + const int stdio_fds[3], const int except_fds[], size_t n_except_fds, ForkFlags flags, @@ -1275,7 +1213,7 @@ int safe_fork_full( else pid = fork(); if (pid < 0) - return log_full_errno(prio, errno, "Failed to fork: %m"); + return log_full_errno(prio, errno, "Failed to fork off '%s': %m", strna(name)); if (pid > 0) { /* We are in the parent process */ @@ -1311,6 +1249,7 @@ int safe_fork_full( /* Close the logs if requested, before we log anything. And make sure we reopen it if needed. */ log_close(); log_set_open_when_needed(true); + log_settle_target(); } if (name) { @@ -1362,15 +1301,47 @@ int safe_fork_full( } if (FLAGS_SET(flags, FORK_NEW_MOUNTNS | FORK_MOUNTNS_SLAVE)) { - /* Optionally, make sure we never propagate mounts to the host. */ - if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) { log_full_errno(prio, errno, "Failed to remount root directory as MS_SLAVE: %m"); _exit(EXIT_FAILURE); } } + if (FLAGS_SET(flags, FORK_PRIVATE_TMP)) { + assert(FLAGS_SET(flags, FORK_NEW_MOUNTNS)); + + /* Optionally, overmount new tmpfs instance on /tmp/. */ + r = mount_nofollow("tmpfs", "/tmp", "tmpfs", + MS_NOSUID|MS_NODEV, + "mode=01777" TMPFS_LIMITS_RUN); + if (r < 0) { + log_full_errno(prio, r, "Failed to overmount /tmp/: %m"); + _exit(EXIT_FAILURE); + } + } + + if (flags & FORK_REARRANGE_STDIO) { + if (stdio_fds) { + r = rearrange_stdio(stdio_fds[0], stdio_fds[1], stdio_fds[2]); + if (r < 0) { + log_full_errno(prio, r, "Failed to rearrange stdio fds: %m"); + _exit(EXIT_FAILURE); + } + } else { + r = make_null_stdio(); + if (r < 0) { + log_full_errno(prio, r, "Failed to connect stdin/stdout to /dev/null: %m"); + _exit(EXIT_FAILURE); + } + } + } else if (flags & FORK_STDOUT_TO_STDERR) { + if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) { + log_full_errno(prio, errno, "Failed to connect stdout to stderr: %m"); + _exit(EXIT_FAILURE); + } + } + if (flags & FORK_CLOSE_ALL_FDS) { /* Close the logs here in case it got reopened above, as close_all_fds() would close them for us */ log_close(); @@ -1382,30 +1353,32 @@ int safe_fork_full( } } + if (flags & FORK_CLOEXEC_OFF) { + r = fd_cloexec_many(except_fds, n_except_fds, false); + if (r < 0) { + log_full_errno(prio, r, "Failed to turn off O_CLOEXEC on file descriptors: %m"); + _exit(EXIT_FAILURE); + } + } + /* When we were asked to reopen the logs, do so again now */ if (flags & FORK_REOPEN_LOG) { log_open(); log_set_open_when_needed(false); } - if (flags & FORK_NULL_STDIO) { - r = make_null_stdio(); + if (flags & FORK_RLIMIT_NOFILE_SAFE) { + r = rlimit_nofile_safe(); if (r < 0) { - log_full_errno(prio, r, "Failed to connect stdin/stdout to /dev/null: %m"); - _exit(EXIT_FAILURE); - } - - } else if (flags & FORK_STDOUT_TO_STDERR) { - if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) { - log_full_errno(prio, errno, "Failed to connect stdout to stderr: %m"); + log_full_errno(prio, r, "Failed to lower RLIMIT_NOFILE's soft limit to 1K: %m"); _exit(EXIT_FAILURE); } } - if (flags & FORK_RLIMIT_NOFILE_SAFE) { - r = rlimit_nofile_safe(); + if (!FLAGS_SET(flags, FORK_KEEP_NOTIFY_SOCKET)) { + r = RET_NERRNO(unsetenv("NOTIFY_SOCKET")); if (r < 0) { - log_full_errno(prio, r, "Failed to lower RLIMIT_NOFILE's soft limit to 1K: %m"); + log_full_errno(prio, r, "Failed to unset $NOTIFY_SOCKET: %m"); _exit(EXIT_FAILURE); } } @@ -1435,7 +1408,10 @@ 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, + NULL, + 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) { @@ -1450,7 +1426,10 @@ int namespace_fork( } /* We mask a few flags here that either make no sense for the grandchild, or that we don't have to do again */ - r = safe_fork_full(inner_name, except_fds, n_except_fds, flags & ~(FORK_WAIT|FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_NULL_STDIO), &pid); + r = safe_fork_full(inner_name, + NULL, + except_fds, n_except_fds, + flags & ~(FORK_WAIT|FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO), &pid); if (r < 0) _exit(EXIT_FAILURE); if (r == 0) { @@ -1503,6 +1482,15 @@ int pidfd_get_pid(int fd, pid_t *ret) { char *p; int r; + /* Converts a pidfd into a pid. Well known errors: + * + * -EBADF → fd invalid + * -ENOSYS → /proc/ not mounted + * -ENOTTY → fd valid, but not a pidfd + * -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one + * -ESRCH → fd valid, but process is already reaped + */ + if (fd < 0) return -EBADF; @@ -1510,25 +1498,39 @@ int pidfd_get_pid(int fd, pid_t *ret) { r = read_full_virtual_file(path, &fdinfo, NULL); if (r == -ENOENT) /* if fdinfo doesn't exist we assume the process does not exist */ - return -ESRCH; + return proc_mounted() > 0 ? -EBADF : -ENOSYS; if (r < 0) return r; - p = startswith(fdinfo, "Pid:"); - if (!p) { - p = strstr(fdinfo, "\nPid:"); - if (!p) - return -ENOTTY; /* not a pidfd? */ - - p += 5; - } + p = find_line_startswith(fdinfo, "Pid:"); + if (!p) + return -ENOTTY; /* not a pidfd? */ p += strspn(p, WHITESPACE); p[strcspn(p, WHITESPACE)] = 0; + if (streq(p, "0")) + return -EREMOTE; /* PID is in foreign PID namespace? */ + if (streq(p, "-1")) + return -ESRCH; /* refers to reaped process? */ + return parse_pid(p, ret); } +int pidfd_verify_pid(int pidfd, pid_t pid) { + pid_t current_pid; + int r; + + assert(pidfd >= 0); + assert(pid > 0); + + r = pidfd_get_pid(pidfd, ¤t_pid); + if (r < 0) + return r; + + return current_pid != pid ? -ESRCH : 0; +} + static int rlimit_to_nice(rlim_t limit) { if (limit <= 1) return PRIO_MAX-1; /* i.e. 19 */ @@ -1585,40 +1587,6 @@ int setpriority_closest(int priority) { return 0; } -bool invoked_as(char *argv[], const char *token) { - if (!argv || isempty(argv[0])) - return false; - - if (isempty(token)) - return false; - - return strstr(last_path_component(argv[0]), token); -} - -bool invoked_by_systemd(void) { - int r; - - /* If the process is directly executed by PID1 (e.g. ExecStart= or generator), systemd-importd, - * or systemd-homed, then $SYSTEMD_EXEC_PID= is set, and read the command line. */ - const char *e = getenv("SYSTEMD_EXEC_PID"); - if (!e) - return false; - - if (streq(e, "*")) - /* For testing. */ - return true; - - pid_t p; - r = parse_pid(e, &p); - if (r < 0) { - /* We know that systemd sets the variable correctly. Something else must have set it. */ - log_debug_errno(r, "Failed to parse \"SYSTEMD_EXEC_PID=%s\", ignoring: %m", e); - return false; - } - - return getpid_cached() == p; -} - _noreturn_ void freeze(void) { log_close(); @@ -1640,29 +1608,29 @@ _noreturn_ void freeze(void) { pause(); } -bool argv_looks_like_help(int argc, char **argv) { - char **l; +int get_process_threads(pid_t pid) { + _cleanup_free_ char *t = NULL; + const char *p; + int n, r; - /* Scans the command line for indications the user asks for help. This is supposed to be called by - * tools that do not implement getopt() style command line parsing because they are not primarily - * user-facing. Detects four ways of asking for help: - * - * 1. Passing zero arguments - * 2. Passing "help" as first argument - * 3. Passing --help as any argument - * 4. Passing -h as any argument - */ + if (pid < 0) + return -EINVAL; - if (argc <= 1) - return true; + p = procfs_file_alloca(pid, "status"); - if (streq_ptr(argv[1], "help")) - return true; + r = get_proc_field(p, "Threads", WHITESPACE, &t); + if (r == -ENOENT) + return proc_mounted() == 0 ? -ENOSYS : -ESRCH; + if (r < 0) + return r; - l = strv_skip(argv, 1); + r = safe_atoi(t, &n); + if (r < 0) + return r; + if (n < 0) + return -EINVAL; - return strv_contains(l, "--help") || - strv_contains(l, "-h"); + return n; } static const char *const sigchld_code_table[] = { diff --git a/src/libnm-systemd-shared/src/basic/process-util.h b/src/libnm-systemd-shared/src/basic/process-util.h index 962f12d1..5cf5c7c6 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.h +++ b/src/libnm-systemd-shared/src/basic/process-util.h @@ -40,6 +40,7 @@ typedef enum ProcessCmdlineFlags { int get_process_comm(pid_t pid, char **ret); int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret); +int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret); int get_process_exe(pid_t pid, char **ret); int get_process_uid(pid_t pid, uid_t *ret); int get_process_gid(pid_t pid, gid_t *ret); @@ -50,6 +51,8 @@ int get_process_environ(pid_t pid, char **ret); int get_process_ppid(pid_t pid, pid_t *ret); int get_process_umask(pid_t pid, mode_t *ret); +int container_get_leader(const char *machine, pid_t *pid); + int wait_for_terminate(pid_t pid, siginfo_t *status); typedef enum WaitFlags { @@ -66,10 +69,11 @@ int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout); void sigkill_wait(pid_t pid); void sigkill_waitp(pid_t *pid); void sigterm_wait(pid_t pid); +void sigkill_nowait(pid_t pid); +void sigkill_nowaitp(pid_t *pid); int kill_and_sigcont(pid_t pid, int sig); -int rename_process(const char name[]); int is_kernel_thread(pid_t pid); int getenv_for_pid(pid_t pid, const char *field, char **_value); @@ -142,22 +146,31 @@ typedef enum ForkFlags { FORK_CLOSE_ALL_FDS = 1 << 1, /* Close all open file descriptors in the child, except for 0,1,2 */ FORK_DEATHSIG = 1 << 2, /* Set PR_DEATHSIG in the child to SIGTERM */ FORK_DEATHSIG_SIGINT = 1 << 3, /* Set PR_DEATHSIG in the child to SIGINT */ - FORK_NULL_STDIO = 1 << 4, /* Connect 0,1,2 to /dev/null */ + FORK_REARRANGE_STDIO = 1 << 4, /* Connect 0,1,2 to specified fds or /dev/null */ FORK_REOPEN_LOG = 1 << 5, /* Reopen log connection */ FORK_LOG = 1 << 6, /* Log above LOG_DEBUG log level about failures */ FORK_WAIT = 1 << 7, /* Wait until child exited */ FORK_NEW_MOUNTNS = 1 << 8, /* Run child in its own mount namespace */ FORK_MOUNTNS_SLAVE = 1 << 9, /* Make child's mount namespace MS_SLAVE */ - FORK_RLIMIT_NOFILE_SAFE = 1 << 10, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */ - FORK_STDOUT_TO_STDERR = 1 << 11, /* Make stdout a copy of stderr */ - FORK_FLUSH_STDIO = 1 << 12, /* fflush() stdout (and stderr) before forking */ - FORK_NEW_USERNS = 1 << 13, /* Run child in its own user namespace */ + FORK_PRIVATE_TMP = 1 << 10, /* Mount new /tmp/ in the child (combine with FORK_NEW_MOUNTNS!) */ + FORK_RLIMIT_NOFILE_SAFE = 1 << 11, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */ + FORK_STDOUT_TO_STDERR = 1 << 12, /* Make stdout a copy of stderr */ + FORK_FLUSH_STDIO = 1 << 13, /* fflush() stdout (and stderr) before forking */ + FORK_NEW_USERNS = 1 << 14, /* Run child in its own user namespace */ + FORK_CLOEXEC_OFF = 1 << 15, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */ + FORK_KEEP_NOTIFY_SOCKET = 1 << 16, /* Unless this specified, $NOTIFY_SOCKET will be unset. */ } 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, + const int stdio_fds[3], + const 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); + return safe_fork_full(name, NULL, NULL, 0, flags, ret_pid); } int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid); @@ -177,23 +190,14 @@ int get_oom_score_adjust(int *ret); assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX); -/* Like TAKE_PTR() but for child PIDs, resetting them to 0 */ -#define TAKE_PID(pid) \ - ({ \ - pid_t *_ppid_ = &(pid); \ - pid_t _pid_ = *_ppid_; \ - *_ppid_ = 0; \ - _pid_; \ - }) +/* Like TAKE_PTR() but for pid_t, resetting them to 0 */ +#define TAKE_PID(pid) TAKE_GENERIC(pid, pid_t, 0) int pidfd_get_pid(int fd, pid_t *ret); +int pidfd_verify_pid(int pidfd, pid_t pid); int setpriority_closest(int priority); -bool invoked_as(char *argv[], const char *token); - -bool invoked_by_systemd(void); - _noreturn_ void freeze(void); -bool argv_looks_like_help(int argc, char **argv); +int get_process_threads(pid_t pid); diff --git a/src/libnm-systemd-shared/src/basic/random-util.c b/src/libnm-systemd-shared/src/basic/random-util.c index 9e1a746f..934d5e25 100644 --- a/src/libnm-systemd-shared/src/basic/random-util.c +++ b/src/libnm-systemd-shared/src/basic/random-util.c @@ -26,6 +26,7 @@ #include "io-util.h" #include "missing_random.h" #include "missing_syscall.h" +#include "missing_threads.h" #include "parse-util.h" #include "random-util.h" #include "sha256.h" @@ -77,7 +78,7 @@ static void fallback_random_bytes(void *p, size_t n) { void random_bytes(void *p, size_t n) { static bool have_getrandom = true, have_grndinsecure = true; - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; if (n == 0) return; @@ -119,7 +120,7 @@ void random_bytes(void *p, size_t n) { int crypto_random_bytes(void *p, size_t n) { static bool have_getrandom = true, seen_initialized = false; - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; if (n == 0) return 0; @@ -147,7 +148,7 @@ int crypto_random_bytes(void *p, size_t n) { } if (!seen_initialized) { - _cleanup_close_ int ready_fd = -1; + _cleanup_close_ int ready_fd = -EBADF; int r; ready_fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY); @@ -190,7 +191,7 @@ size_t random_pool_size(void) { } int random_write_entropy(int fd, const void *seed, size_t size, bool credit) { - _cleanup_close_ int opened_fd = -1; + _cleanup_close_ int opened_fd = -EBADF; int r; assert(seed || size == 0); diff --git a/src/libnm-systemd-shared/src/basic/random-util.h b/src/libnm-systemd-shared/src/basic/random-util.h index 2d998072..b1a4d109 100644 --- a/src/libnm-systemd-shared/src/basic/random-util.h +++ b/src/libnm-systemd-shared/src/basic/random-util.h @@ -23,6 +23,7 @@ static inline uint32_t random_u32(void) { /* Some limits on the pool sizes when we deal with the kernel random pool */ #define RANDOM_POOL_SIZE_MIN 32U #define RANDOM_POOL_SIZE_MAX (10U*1024U*1024U) +#define RANDOM_EFI_SEED_SIZE 32U size_t random_pool_size(void); diff --git a/src/libnm-systemd-shared/src/basic/ratelimit.c b/src/libnm-systemd-shared/src/basic/ratelimit.c index 3ccec119..a28c8122 100644 --- a/src/libnm-systemd-shared/src/basic/ratelimit.c +++ b/src/libnm-systemd-shared/src/basic/ratelimit.c @@ -22,26 +22,41 @@ bool ratelimit_below(RateLimit *r) { if (r->begin <= 0 || usec_sub_unsigned(ts, r->begin) > r->interval) { - r->begin = ts; - - /* Reset counter */ - r->num = 0; - goto good; + r->begin = ts; /* Start a new time window */ + r->num = 1; /* Reset counter */ + return true; } - if (r->num < r->burst) - goto good; - - r->num++; - return false; + if (_unlikely_(r->num == UINT_MAX)) + return false; -good: r->num++; - return true; + return r->num <= r->burst; } unsigned ratelimit_num_dropped(RateLimit *r) { assert(r); - return r->num > r->burst ? r->num - r->burst : 0; + if (r->num == UINT_MAX) /* overflow, return as special case */ + return UINT_MAX; + + return LESS_BY(r->num, r->burst); +} + +usec_t ratelimit_end(const RateLimit *rl) { + assert(rl); + + if (rl->begin == 0) + return 0; + + return usec_add(rl->begin, rl->interval); +} + +usec_t ratelimit_left(const RateLimit *rl) { + assert(rl); + + if (rl->begin == 0) + return 0; + + return usec_sub_unsigned(ratelimit_end(rl), now(CLOCK_MONOTONIC)); } diff --git a/src/libnm-systemd-shared/src/basic/ratelimit.h b/src/libnm-systemd-shared/src/basic/ratelimit.h index 22361898..bb7160a8 100644 --- a/src/libnm-systemd-shared/src/basic/ratelimit.h +++ b/src/libnm-systemd-shared/src/basic/ratelimit.h @@ -23,3 +23,6 @@ static inline bool ratelimit_configured(RateLimit *rl) { bool ratelimit_below(RateLimit *r); unsigned ratelimit_num_dropped(RateLimit *r); + +usec_t ratelimit_end(const RateLimit *rl); +usec_t ratelimit_left(const RateLimit *rl); diff --git a/src/libnm-systemd-shared/src/basic/signal-util.c b/src/libnm-systemd-shared/src/basic/signal-util.c index 6d8c9920..270d397d 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.c +++ b/src/libnm-systemd-shared/src/basic/signal-util.c @@ -7,6 +7,8 @@ #include "errno-util.h" #include "macro.h" +#include "missing_syscall.h" +#include "missing_threads.h" #include "parse-util.h" #include "signal-util.h" #include "stdio-util.h" @@ -285,4 +287,21 @@ int pop_pending_signal_internal(int sig, ...) { return r; /* Returns the signal popped */ } + +void propagate_signal(int sig, siginfo_t *siginfo) { + pid_t p; + + /* To be called from a signal handler. Will raise the same signal again, in our process + in our threads. + * + * Note that we use raw_getpid() instead of getpid_cached(). We might have forked with raw_clone() + * earlier (see PID 1), and hence let's go to the raw syscall here. In particular as this is not + * performance sensitive code. + * + * Note that we use kill() rather than raise() as fallback, for similar reasons. */ + + p = raw_getpid(); + + if (rt_tgsigqueueinfo(p, gettid(), sig, siginfo) < 0) + assert_se(kill(p, sig) >= 0); +} #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 36372c19..ad2ba841 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.h +++ b/src/libnm-systemd-shared/src/basic/signal-util.h @@ -65,3 +65,5 @@ int signal_is_blocked(int sig); int pop_pending_signal_internal(int sig, ...); #define pop_pending_signal(...) pop_pending_signal_internal(__VA_ARGS__, -1) + +void propagate_signal(int sig, siginfo_t *siginfo); diff --git a/src/libnm-systemd-shared/src/basic/socket-util.c b/src/libnm-systemd-shared/src/basic/socket-util.c index c155e352..9b411e07 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.c +++ b/src/libnm-systemd-shared/src/basic/socket-util.c @@ -228,7 +228,7 @@ bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) { return false; if (a->sockaddr.un.sun_path[0]) { - if (!path_equal_or_files_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, 0)) + if (!path_equal_or_inode_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, 0)) return false; } else { if (a->size != b->size) @@ -1054,9 +1054,9 @@ ssize_t receive_one_fd_iov( } if (found) - *ret_fd = *(int*) CMSG_DATA(found); + *ret_fd = *CMSG_TYPED_DATA(found, int); else - *ret_fd = -1; + *ret_fd = -EBADF; return k; } @@ -1181,6 +1181,24 @@ struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t leng return NULL; } +void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len) { + struct cmsghdr *cmsg; + + assert(mh); + assert(buf); + assert(buf_len > 0); + + /* This is similar to cmsg_find_data(), but copy the found data to buf. This should be typically used + * when reading possibly unaligned data such as timestamp, as time_t is 64bit and size_t is 32bit on + * RISCV32. See issue #27241. */ + + cmsg = cmsg_find(mh, level, type, CMSG_LEN(buf_len)); + if (!cmsg) + return NULL; + + return memcpy_safe(buf, CMSG_DATA(cmsg), buf_len); +} + #if 0 /* NM_IGNORED */ int socket_ioctl_fd(void) { int fd; @@ -1320,7 +1338,7 @@ ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags) { } #if 0 /* NM_IGNORED */ -int socket_get_family(int fd, int *ret) { +int socket_get_family(int fd) { int af; socklen_t sl = sizeof(af); @@ -1334,12 +1352,11 @@ int socket_get_family(int fd, int *ret) { } int socket_set_recvpktinfo(int fd, int af, bool b) { - int r; if (af == AF_UNSPEC) { - r = socket_get_family(fd, &af); - if (r < 0) - return r; + af = socket_get_family(fd); + if (af < 0) + return af; } switch (af) { @@ -1363,12 +1380,11 @@ int socket_set_recvpktinfo(int fd, int af, bool b) { int socket_set_unicast_if(int fd, int af, int ifi) { be32_t ifindex_be = htobe32(ifi); - int r; if (af == AF_UNSPEC) { - r = socket_get_family(fd, &af); - if (r < 0) - return r; + af = socket_get_family(fd); + if (af < 0) + return af; } switch (af) { @@ -1385,12 +1401,10 @@ int socket_set_unicast_if(int fd, int af, int ifi) { } int socket_set_option(int fd, int af, int opt_ipv4, int opt_ipv6, int val) { - int r; - if (af == AF_UNSPEC) { - r = socket_get_family(fd, &af); - if (r < 0) - return r; + af = socket_get_family(fd); + if (af < 0) + return af; } switch (af) { @@ -1410,9 +1424,9 @@ int socket_get_mtu(int fd, int af, size_t *ret) { int mtu, r; if (af == AF_UNSPEC) { - r = socket_get_family(fd, &af); - if (r < 0) - return r; + af = socket_get_family(fd); + if (af < 0) + return af; } switch (af) { @@ -1439,50 +1453,136 @@ int socket_get_mtu(int fd, int af, size_t *ret) { } #endif /* NM_IGNORED */ -int connect_unix_path(int fd, int dir_fd, const char *path) { - _cleanup_close_ int inode_fd = -1; +static int connect_unix_path_simple(int fd, const char *path) { union sockaddr_union sa = { .un.sun_family = AF_UNIX, }; - size_t path_len; - socklen_t salen; + size_t l; assert(fd >= 0); - assert(dir_fd == AT_FDCWD || dir_fd >= 0); assert(path); + l = strlen(path); + assert(l > 0); + assert(l < sizeof(sa.un.sun_path)); + + memcpy(sa.un.sun_path, path, l + 1); + return RET_NERRNO(connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + l + 1)); +} + +static int connect_unix_inode(int fd, int inode_fd) { + assert(fd >= 0); + assert(inode_fd >= 0); + + return connect_unix_path_simple(fd, FORMAT_PROC_FD_PATH(inode_fd)); +} + +int connect_unix_path(int fd, int dir_fd, const char *path) { + _cleanup_close_ int inode_fd = -EBADF; + + assert(fd >= 0); + assert(dir_fd == AT_FDCWD || dir_fd >= 0); + /* Connects to the specified AF_UNIX socket in the file system. Works around the 108 byte size limit * in sockaddr_un, by going via O_PATH if needed. This hence works for any kind of path. */ - path_len = strlen(path); + if (!path) + return connect_unix_inode(fd, dir_fd); /* If no path is specified, then dir_fd refers to the socket inode to connect to. */ /* Refuse zero length path early, to make sure AF_UNIX stack won't mistake this for an abstract * namespace path, since first char is NUL */ - if (path_len <= 0) + if (isempty(path)) return -EINVAL; - if (dir_fd == AT_FDCWD && path_len < sizeof(sa.un.sun_path)) { - memcpy(sa.un.sun_path, path, path_len + 1); - salen = offsetof(struct sockaddr_un, sun_path) + path_len + 1; - } else { - const char *proc; - size_t proc_len; + /* Shortcut for the simple case */ + if (dir_fd == AT_FDCWD && strlen(path) < sizeof_field(struct sockaddr_un, sun_path)) + return connect_unix_path_simple(fd, path); - /* If dir_fd is specified, then we need to go the indirect O_PATH route, because connectat() - * does not exist. If the path is too long, we also need to take the indirect route, since we - * can't fit this into a sockaddr_un directly. */ + /* If dir_fd is specified, then we need to go the indirect O_PATH route, because connectat() does not + * exist. If the path is too long, we also need to take the indirect route, since we can't fit this + * into a sockaddr_un directly. */ - inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); - if (inode_fd < 0) - return -errno; + inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); + if (inode_fd < 0) + return -errno; + + return connect_unix_inode(fd, inode_fd); +} + +int socket_address_parse_unix(SocketAddress *ret_address, const char *s) { + struct sockaddr_un un; + int r; + + assert(ret_address); + assert(s); + + if (!IN_SET(*s, '/', '@')) + return -EPROTO; + + r = sockaddr_un_set_path(&un, s); + if (r < 0) + return r; + + *ret_address = (SocketAddress) { + .sockaddr.un = un, + .size = r, + }; - proc = FORMAT_PROC_FD_PATH(inode_fd); - proc_len = strlen(proc); + return 0; +} + +#if 0 /* NM_IGNORED */ +int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { + /* AF_VSOCK socket in vsock:cid:port notation */ + _cleanup_free_ char *n = NULL; + char *e, *cid_start; + unsigned port, cid; + int type, r; + + assert(ret_address); + assert(s); + + if ((cid_start = startswith(s, "vsock:"))) + type = 0; + else if ((cid_start = startswith(s, "vsock-dgram:"))) + type = SOCK_DGRAM; + else if ((cid_start = startswith(s, "vsock-seqpacket:"))) + type = SOCK_SEQPACKET; + else if ((cid_start = startswith(s, "vsock-stream:"))) + type = SOCK_STREAM; + else + return -EPROTO; - assert(proc_len < sizeof(sa.un.sun_path)); - memcpy(sa.un.sun_path, proc, proc_len + 1); - salen = offsetof(struct sockaddr_un, sun_path) + proc_len + 1; + e = strchr(cid_start, ':'); + if (!e) + return -EINVAL; + + r = safe_atou(e+1, &port); + if (r < 0) + return r; + + n = strndup(cid_start, e - cid_start); + if (!n) + return -ENOMEM; + + if (isempty(n)) + cid = VMADDR_CID_ANY; + else { + r = safe_atou(n, &cid); + if (r < 0) + return r; } - return RET_NERRNO(connect(fd, &sa.sa, salen)); + *ret_address = (SocketAddress) { + .sockaddr.vm = { + .svm_cid = cid, + .svm_family = AF_VSOCK, + .svm_port = port, + }, + .type = type, + .size = sizeof(struct sockaddr_vm), + }; + + return 0; } +#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/socket-util.h b/src/libnm-systemd-shared/src/basic/socket-util.h index cf9a0b35..26f9636f 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.h +++ b/src/libnm-systemd-shared/src/basic/socket-util.h @@ -177,15 +177,29 @@ int flush_accept(int fd); #define CMSG_FOREACH(cmsg, mh) \ for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg))) +/* Returns the cmsghdr's data pointer, but safely cast to the specified type. Does two alignment checks: one + * at compile time, that the requested type has a smaller or same alignment as 'struct cmsghdr', and one + * during runtime, that the actual pointer matches the alignment too. This is supposed to catch cases such as + * 'struct timeval' is embedded into 'struct cmsghdr' on architectures where the alignment of the former is 8 + * bytes (because of a 64bit time_t), but of the latter is 4 bytes (because size_t is 32bit), such as + * riscv32. */ +#define CMSG_TYPED_DATA(cmsg, type) \ + ({ \ + struct cmsghdr *_cmsg = (cmsg); \ + assert_cc(alignof(type) <= alignof(struct cmsghdr)); \ + _cmsg ? CAST_ALIGN_PTR(type, CMSG_DATA(_cmsg)) : (type*) NULL; \ + }) + struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length); +void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len); /* Type-safe, dereferencing version of cmsg_find() */ -#define CMSG_FIND_DATA(mh, level, type, ctype) \ - ({ \ - struct cmsghdr *_found; \ - _found = cmsg_find(mh, level, type, CMSG_LEN(sizeof(ctype))); \ - (ctype*) (_found ? CMSG_DATA(_found) : NULL); \ - }) +#define CMSG_FIND_DATA(mh, level, type, ctype) \ + CMSG_TYPED_DATA(cmsg_find(mh, level, type, CMSG_LEN(sizeof(ctype))), ctype) + +/* Type-safe version of cmsg_find_and_copy_data() */ +#define CMSG_FIND_AND_COPY_DATA(mh, level, type, ctype) \ + (ctype*) cmsg_find_and_copy_data(mh, level, type, &(ctype){}, sizeof(ctype)) /* Resolves to a type that can carry cmsghdr structures. Make sure things are properly aligned, i.e. the type * itself is placed properly in memory and the size is also aligned to what's appropriate for "cmsghdr" @@ -306,7 +320,7 @@ struct timespec_large { ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags); -int socket_get_family(int fd, int *ret); +int socket_get_family(int fd); int socket_set_recvpktinfo(int fd, int af, bool b); int socket_set_unicast_if(int fd, int af, int ifi); @@ -338,3 +352,16 @@ int socket_get_mtu(int fd, int af, size_t *ret); #define UCRED_INVALID { .pid = 0, .uid = UID_INVALID, .gid = GID_INVALID } int connect_unix_path(int fd, int dir_fd, const char *path); + +/* Parses AF_UNIX and AF_VSOCK addresses. AF_INET[6] require some netlink calls, so it cannot be in + * src/basic/ and is done from 'socket_local_address from src/shared/. Return -EPROTO in case of + * protocol mismatch. */ +int socket_address_parse_unix(SocketAddress *ret_address, const char *s); +int socket_address_parse_vsock(SocketAddress *ret_address, const char *s); + +/* libc's SOMAXCONN is defined to 128 or 4096 (at least on glibc). But actually, the value can be much + * larger. In our codebase we want to set it to the max usually, since noawadays socket memory is properly + * tracked by memcg, and hence we don't need to enforce extra limits here. Moreover, the kernel caps it to + * /proc/sys/net/core/somaxconn anyway, thus by setting this to unbounded we just make that sysctl file + * authoritative. */ +#define SOMAXCONN_DELUXE INT_MAX diff --git a/src/libnm-systemd-shared/src/basic/stat-util.c b/src/libnm-systemd-shared/src/basic/stat-util.c index 5a142263..a81ee468 100644 --- a/src/libnm-systemd-shared/src/basic/stat-util.c +++ b/src/libnm-systemd-shared/src/basic/stat-util.c @@ -10,13 +10,14 @@ #include <unistd.h> #include "alloc-util.h" -#include "chase-symlinks.h" +#include "chase.h" #include "dirent-util.h" #include "errno-util.h" #include "fd-util.h" #include "fileio.h" #include "filesystems.h" #include "fs-util.h" +#include "hash-funcs.h" #include "macro.h" #include "missing_fs.h" #include "missing_magic.h" @@ -69,7 +70,7 @@ int is_device_node(const char *path) { } int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) { - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; struct dirent *buf; size_t m; @@ -149,56 +150,58 @@ int null_or_empty_path_with_root(const char *fn, const char *root) { if (path_equal_ptr(path_startswith(fn, root ?: "/"), "dev/null")) return true; - r = chase_symlinks_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st, NULL); + r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st); if (r < 0) return r; return null_or_empty(&st); } -int null_or_empty_fd(int fd) { - struct stat st; +static int fd_is_read_only_fs(int fd) { + struct statvfs st; assert(fd >= 0); - if (fstat(fd, &st) < 0) + if (fstatvfs(fd, &st) < 0) return -errno; - return null_or_empty(&st); + if (st.f_flag & ST_RDONLY) + return true; + + /* On NFS, fstatvfs() might not reflect whether we can actually write to the remote share. Let's try + * again with access(W_OK) which is more reliable, at least sometimes. */ + if (access_fd(fd, W_OK) == -EROFS) + return true; + + return false; } int path_is_read_only_fs(const char *path) { - struct statvfs st; + _cleanup_close_ int fd = -EBADF; assert(path); - if (statvfs(path, &st) < 0) + fd = open(path, O_CLOEXEC | O_PATH); + if (fd < 0) return -errno; - if (st.f_flag & ST_RDONLY) - return true; - - /* On NFS, statvfs() might not reflect whether we can actually - * write to the remote share. Let's try again with - * access(W_OK) which is more reliable, at least sometimes. */ - if (access(path, W_OK) < 0 && errno == EROFS) - return true; - - return false; + return fd_is_read_only_fs(fd); } #endif /* NM_IGNORED */ -int files_same(const char *filea, const char *fileb, int flags) { +int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags) { struct stat a, b; + assert(fda >= 0 || fda == AT_FDCWD); assert(filea); + assert(fdb >= 0 || fdb == AT_FDCWD); assert(fileb); - if (fstatat(AT_FDCWD, filea, &a, flags) < 0) - return -errno; + if (fstatat(fda, filea, &a, flags) < 0) + return log_debug_errno(errno, "Cannot stat %s: %m", filea); - if (fstatat(AT_FDCWD, fileb, &b, flags) < 0) - return -errno; + if (fstatat(fdb, fileb, &b, flags) < 0) + return log_debug_errno(errno, "Cannot stat %s: %m", fileb); return stat_inode_same(&a, &b); } @@ -210,22 +213,13 @@ bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) { return F_TYPE_EQUAL(s->f_type, magic_value); } -#if 0 /* NM_IGNORED */ -int fd_is_fs_type(int fd, statfs_f_type_t magic_value) { - struct statfs s; - - if (fstatfs(fd, &s) < 0) - return -errno; - - return is_fs_type(&s, magic_value); -} -#endif /* NM_IGNORED */ - -int path_is_fs_type(const char *path, statfs_f_type_t magic_value) { +int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value) { struct statfs s; + int r; - if (statfs(path, &s) < 0) - return -errno; + r = xstatfsat(dir_fd, path, &s); + if (r < 0) + return r; return is_fs_type(&s, magic_value); } @@ -306,6 +300,18 @@ int fd_verify_regular(int fd) { } #if 0 /* NM_IGNORED */ +int verify_regular_at(int dir_fd, const char *path, bool follow) { + struct stat st; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); + + if (fstatat(dir_fd, path, &st, (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0) + return -errno; + + return stat_verify_regular(&st); +} + int stat_verify_directory(const struct stat *st) { assert(st); @@ -455,3 +461,58 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s return 0; } #endif /* NM_IGNORED */ + +int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { + _cleanup_close_ int fd = -EBADF; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(ret); + + fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0); + if (fd < 0) + return fd; + + return RET_NERRNO(fstatfs(fd, ret)); +} + +#if 0 /* NM_IGNORED */ +void inode_hash_func(const struct stat *q, struct siphash *state) { + siphash24_compress(&q->st_dev, sizeof(q->st_dev), state); + siphash24_compress(&q->st_ino, sizeof(q->st_ino), state); +} + +int inode_compare_func(const struct stat *a, const struct stat *b) { + int r; + + r = CMP(a->st_dev, b->st_dev); + if (r != 0) + return r; + + return CMP(a->st_ino, b->st_ino); +} + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free); + +const char* inode_type_to_string(mode_t m) { + + /* Returns a short string for the inode type. We use the same name as the underlying macros for each + * inode type. */ + + switch (m & S_IFMT) { + case S_IFREG: + return "reg"; + case S_IFDIR: + return "dir"; + case S_IFCHR: + return "chr"; + case S_IFBLK: + return "blk"; + case S_IFIFO: + return "fifo"; + case S_IFSOCK: + return "sock"; + } + + return NULL; +} +#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/stat-util.h b/src/libnm-systemd-shared/src/basic/stat-util.h index f9519d8c..ae0aaf8f 100644 --- a/src/libnm-systemd-shared/src/basic/stat-util.h +++ b/src/libnm-systemd-shared/src/basic/stat-util.h @@ -11,6 +11,7 @@ #include "macro.h" #include "missing_stat.h" +#include "siphash24.h" int is_symlink(const char *path); int is_dir_full(int atfd, const char *fname, bool follow); @@ -29,7 +30,6 @@ static inline int dir_is_empty(const char *path, bool ignore_hidden_or_backup) { bool null_or_empty(struct stat *st) _pure_; int null_or_empty_path_with_root(const char *fn, const char *root); -int null_or_empty_fd(int fd); static inline int null_or_empty_path(const char *fn) { return null_or_empty_path_with_root(fn, NULL); @@ -37,15 +37,24 @@ static inline int null_or_empty_path(const char *fn) { int path_is_read_only_fs(const char *path); -int files_same(const char *filea, const char *fileb, int flags); +int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags); + +static inline int inode_same(const char *filea, const char *fileb, int flags) { + return inode_same_at(AT_FDCWD, filea, AT_FDCWD, fileb, flags); +} /* The .f_type field of struct statfs is really weird defined on * different archs. Let's give its type a name. */ typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t; bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) _pure_; -int fd_is_fs_type(int fd, statfs_f_type_t magic_value); -int path_is_fs_type(const char *path, statfs_f_type_t magic_value); +int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value); +static inline int fd_is_fs_type(int fd, statfs_f_type_t magic_value) { + return is_fs_type_at(fd, NULL, magic_value); +} +static inline int path_is_fs_type(const char *path, statfs_f_type_t magic_value) { + return is_fs_type_at(AT_FDCWD, path, magic_value); +} bool is_temporary_fs(const struct statfs *s) _pure_; bool is_network_fs(const struct statfs *s) _pure_; @@ -64,6 +73,7 @@ int path_is_network_fs(const char *path); int stat_verify_regular(const struct stat *st); int fd_verify_regular(int fd); +int verify_regular_at(int dir_fd, const char *path, bool follow); int stat_verify_directory(const struct stat *st); int fd_verify_directory(int fd); @@ -78,6 +88,8 @@ bool statx_mount_same(const struct new_statx *a, const struct new_statx *b); int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct statx *sx); +int xstatfsat(int dir_fd, const char *path, struct statfs *ret); + #if HAS_FEATURE_MEMORY_SANITIZER # warning "Explicitly initializing struct statx, to work around msan limitation. Please remove as soon as msan has been updated to not require this." # define STRUCT_STATX_DEFINE(var) \ @@ -96,3 +108,9 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s struct new_statx nsx; \ } var #endif + +void inode_hash_func(const struct stat *q, struct siphash *state); +int inode_compare_func(const struct stat *a, const struct stat *b); +extern const struct hash_ops inode_hash_ops; + +const char* inode_type_to_string(mode_t m); diff --git a/src/libnm-systemd-shared/src/basic/stdio-util.h b/src/libnm-systemd-shared/src/basic/stdio-util.h index 07cbfe9d..d0b25fdf 100644 --- a/src/libnm-systemd-shared/src/basic/stdio-util.h +++ b/src/libnm-systemd-shared/src/basic/stdio-util.h @@ -9,15 +9,20 @@ #include <sys/types.h> #include "macro.h" -#include "memory-util.h" -#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; \ - }) +_printf_(3, 4) +static inline char *snprintf_ok(char *buf, size_t len, const char *format, ...) { + va_list ap; + int r; + + va_start(ap, format); + DISABLE_WARNING_FORMAT_NONLITERAL; + r = vsnprintf(buf, len, format, ap); + REENABLE_WARNING; + va_end(ap); + + return r >= 0 && (size_t) r < len ? buf : NULL; +} #define xsprintf(buf, fmt, ...) \ assert_message_se(snprintf_ok(buf, ELEMENTSOF(buf), fmt, ##__VA_ARGS__), "xsprintf: " #buf "[] must be big enough") @@ -28,7 +33,7 @@ do { \ size_t _i, _k; \ /* See https://github.com/google/sanitizers/issues/992 */ \ if (HAS_FEATURE_MEMORY_SANITIZER) \ - zero(_argtypes); \ + memset(_argtypes, 0, sizeof(_argtypes)); \ _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \ assert(_k < ELEMENTSOF(_argtypes)); \ for (_i = 0; _i < _k; _i++) { \ diff --git a/src/libnm-systemd-shared/src/basic/string-table.h b/src/libnm-systemd-shared/src/basic/string-table.h index e3a26a62..3be70dfa 100644 --- a/src/libnm-systemd-shared/src/basic/string-table.h +++ b/src/libnm-systemd-shared/src/basic/string-table.h @@ -95,6 +95,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k #define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \ _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,) \ _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,) +#define DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(name,type,max) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,) #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \ _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static) diff --git a/src/libnm-systemd-shared/src/basic/string-util.c b/src/libnm-systemd-shared/src/basic/string-util.c index 9b2ee879..1afa49bb 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.c +++ b/src/libnm-systemd-shared/src/basic/string-util.c @@ -11,16 +11,17 @@ #include "alloc-util.h" #include "escape.h" #include "extract-word.h" +#include "fd-util.h" #include "fileio.h" #include "gunicode.h" #include "locale-util.h" #include "macro.h" #include "memory-util.h" +#include "memstream-util.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" #include "utf8.h" -#include "util.h" char* first_word(const char *s, const char *word) { size_t sl, wl; @@ -611,8 +612,8 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { STATE_CSI, STATE_CSO, } state = STATE_OTHER; - char *obuf = NULL; - size_t osz = 0, isz, shift[2] = {}, n_carriage_returns = 0; + _cleanup_(memstream_done) MemStream m = {}; + size_t isz, shift[2] = {}, n_carriage_returns = 0; FILE *f; assert(ibuf); @@ -636,7 +637,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { /* Note we turn off internal locking on f for performance reasons. It's safe to do so since we * created f here and it doesn't leave our scope. */ - f = open_memstream_unlocked(&obuf, &osz); + f = memstream_init(&m); if (!f) return NULL; @@ -721,17 +722,12 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { } } - if (fflush_and_check(f) < 0) { - fclose(f); - return mfree(obuf); - } - fclose(f); + char *obuf; + if (memstream_finalize(&m, &obuf, _isz) < 0) + return NULL; free_and_replace(*ibuf, obuf); - if (_isz) - *_isz = osz; - if (highlight) { highlight[0] += shift[0]; highlight[1] += shift[1]; @@ -739,6 +735,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { return *ibuf; } +#endif /* NM_IGNORED */ char *strextend_with_separator_internal(char **x, const char *separator, ...) { size_t f, l, l_separator; @@ -809,7 +806,6 @@ char *strextend_with_separator_internal(char **x, const char *separator, ...) { return p; } -#endif /* NM_IGNORED */ int strextendf_with_separator(char **x, const char *separator, const char *format, ...) { size_t m, a, l_separator; @@ -964,8 +960,7 @@ int free_and_strdup(char **p, const char *s) { } else t = NULL; - free(*p); - *p = t; + free_and_replace(*p, t); return 1; } @@ -1197,6 +1192,49 @@ char *string_replace_char(char *str, char old_char, char new_char) { return str; } +int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) { + char *b; + + assert(s || n == 0); + assert(mode >= 0); + assert(mode < _MAKE_CSTRING_MODE_MAX); + + /* Converts a sized character buffer into a NUL-terminated NUL string, refusing if there are embedded + * NUL bytes. Whether to expect a trailing NUL byte can be specified via 'mode' */ + + if (n == 0) { + if (mode == MAKE_CSTRING_REQUIRE_TRAILING_NUL) + return -EINVAL; + + if (!ret) + return 0; + + b = new0(char, 1); + } else { + const char *nul; + + nul = memchr(s, 0, n); + if (nul) { + if (nul < s + n - 1 || /* embedded NUL? */ + mode == MAKE_CSTRING_REFUSE_TRAILING_NUL) + return -EINVAL; + + n--; + } else if (mode == MAKE_CSTRING_REQUIRE_TRAILING_NUL) + return -EINVAL; + + if (!ret) + return 0; + + b = memdup_suffix0(s, n); + } + if (!b) + return -ENOMEM; + + *ret = b; + return 0; +} + size_t strspn_from_end(const char *str, const char *accept) { size_t n = 0; @@ -1211,4 +1249,55 @@ size_t strspn_from_end(const char *str, const char *accept) { return n; } + +char *strdupspn(const char *a, const char *accept) { + if (isempty(a) || isempty(accept)) + return strdup(""); + + return strndup(a, strspn(a, accept)); +} + +char *strdupcspn(const char *a, const char *reject) { + if (isempty(a)) + return strdup(""); + if (isempty(reject)) + return strdup(a); + + return strndup(a, strcspn(a, reject)); +} + +char *find_line_startswith(const char *haystack, const char *needle) { + char *p; + + assert(haystack); + assert(needle); + + /* Finds the first line in 'haystack' that starts with the specified string. Returns a pointer to the + * first character after it */ + + p = strstr(haystack, needle); + if (!p) + return NULL; + + if (p > haystack) + while (p[-1] != '\n') { + p = strstr(p + 1, needle); + if (!p) + return NULL; + } + + return p + strlen(needle); +} #endif /* NM_IGNORED */ + +char *startswith_strv(const char *string, char **strv) { + char *found = NULL; + + STRV_FOREACH(i, strv) { + found = startswith(string, *i); + if (found) + break; + } + + return found; +} diff --git a/src/libnm-systemd-shared/src/basic/string-util.h b/src/libnm-systemd-shared/src/basic/string-util.h index 46681ced..4430910e 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.h +++ b/src/libnm-systemd-shared/src/basic/string-util.h @@ -29,6 +29,18 @@ static inline char* strstr_ptr(const char *haystack, const char *needle) { return strstr(haystack, needle); } +static inline char *strstrafter(const char *haystack, const char *needle) { + char *p; + + /* Returns NULL if not found, or pointer to first character after needle if found */ + + p = strstr_ptr(haystack, needle); + if (!p) + return NULL; + + return p + strlen(needle); +} + static inline const char* strnull(const char *s) { return s ?: "(null)"; } @@ -53,9 +65,13 @@ static inline const char* enable_disable(bool b) { return b ? "enable" : "disable"; } -static inline const char *empty_to_null(const char *p) { - return isempty(p) ? NULL : p; -} +/* This macro's return pointer will have the "const" qualifier set or unset the same way as the input + * pointer. */ +#define empty_to_null(p) \ + ({ \ + const char *_p = (p); \ + (typeof(p)) (isempty(_p) ? NULL : _p); \ + }) static inline const char *empty_to_na(const char *p) { return isempty(p) ? "n/a" : p; @@ -74,6 +90,11 @@ static inline bool empty_or_dash(const char *str) { static inline const char *empty_or_dash_to_null(const char *p) { return empty_or_dash(p) ? NULL : p; } +#define empty_or_dash_to_null(p) \ + ({ \ + const char *_p = (p); \ + (typeof(p)) (empty_or_dash(_p) ? NULL : _p); \ + }) char *first_word(const char *s, const char *word) _pure_; @@ -230,4 +251,24 @@ bool streq_skip_trailing_chars(const char *s1, const char *s2, const char *ok); char *string_replace_char(char *str, char old_char, char new_char); +typedef enum MakeCStringMode { + MAKE_CSTRING_REFUSE_TRAILING_NUL, + MAKE_CSTRING_ALLOW_TRAILING_NUL, + MAKE_CSTRING_REQUIRE_TRAILING_NUL, + _MAKE_CSTRING_MODE_MAX, + _MAKE_CSTRING_MODE_INVALID = -1, +} MakeCStringMode; + +int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret); + size_t strspn_from_end(const char *str, const char *accept); + +char *strdupspn(const char *a, const char *accept); +char *strdupcspn(const char *a, const char *reject); + +char *find_line_startswith(const char *haystack, const char *needle); + +char *startswith_strv(const char *string, char **strv); + +#define STARTSWITH_SET(p, ...) \ + startswith_strv(p, STRV_MAKE(__VA_ARGS__)) diff --git a/src/libnm-systemd-shared/src/basic/strv.c b/src/libnm-systemd-shared/src/basic/strv.c index 48ca8575..9ad53307 100644 --- a/src/libnm-systemd-shared/src/basic/strv.c +++ b/src/libnm-systemd-shared/src/basic/strv.c @@ -9,6 +9,7 @@ #include <stdlib.h> #include "alloc-util.h" +#include "env-util.h" #include "escape.h" #include "extract-word.h" #include "fileio.h" @@ -65,6 +66,16 @@ char* strv_find_startswith(char * const *l, const char *name) { return NULL; } +char* strv_find_first_field(char * const *needles, char * const *haystack) { + STRV_FOREACH(k, needles) { + char *value = strv_env_pairs_get((char **)haystack, *k); + if (value) + return value; + } + + return NULL; +} + char** strv_free(char **l) { STRV_FOREACH(k, l) free(*k); @@ -79,20 +90,26 @@ char** strv_free_erase(char **l) { return mfree(l); } -char** strv_copy(char * const *l) { +char** strv_copy_n(char * const *l, size_t m) { _cleanup_strv_free_ char **result = NULL; char **k; - result = new(char*, strv_length(l) + 1); + result = new(char*, MIN(strv_length(l), m) + 1); if (!result) return NULL; k = result; STRV_FOREACH(i, l) { + if (m == 0) + break; + *k = strdup(*i); if (!*k) return NULL; k++; + + if (m != SIZE_MAX) + m--; } *k = NULL; @@ -629,123 +646,6 @@ char** strv_remove(char **l, const char *s) { return l; } -char** strv_parse_nulstr(const char *s, size_t l) { - /* l is the length of the input data, which will be split at NULs into - * elements of the resulting strv. Hence, the number of items in the resulting strv - * will be equal to one plus the number of NUL bytes in the l bytes starting at s, - * unless s[l-1] is NUL, in which case the final empty string is not stored in - * the resulting strv, and length is equal to the number of NUL bytes. - * - * Note that contrary to a normal nulstr which cannot contain empty strings, because - * the input data is terminated by any two consequent NUL bytes, this parser accepts - * empty strings in s. - */ - - size_t c = 0, i = 0; - char **v; - - assert(s || l <= 0); - - if (l <= 0) - return new0(char*, 1); - - for (const char *p = s; p < s + l; p++) - if (*p == 0) - c++; - - if (s[l-1] != 0) - c++; - - v = new0(char*, c+1); - if (!v) - return NULL; - - for (const char *p = s; p < s + l; ) { - const char *e; - - e = memchr(p, 0, s + l - p); - - v[i] = strndup(p, e ? e - p : s + l - p); - if (!v[i]) { - strv_free(v); - return NULL; - } - - i++; - - if (!e) - break; - - p = e + 1; - } - - assert(i == c); - - return v; -} - -#if 0 /* NM_IGNORED */ -char** strv_split_nulstr(const char *s) { - const char *i; - char **r = NULL; - - NULSTR_FOREACH(i, s) - if (strv_extend(&r, i) < 0) { - strv_free(r); - return NULL; - } - - if (!r) - return strv_new(NULL); - - return r; -} -#endif /* NM_IGNORED */ - -int strv_make_nulstr(char * const *l, char **ret, size_t *ret_size) { - /* A valid nulstr with two NULs at the end will be created, but - * q will be the length without the two trailing NULs. Thus the output - * string is a valid nulstr and can be iterated over using NULSTR_FOREACH, - * and can also be parsed by strv_parse_nulstr as long as the length - * is provided separately. - */ - - _cleanup_free_ char *m = NULL; - size_t n = 0; - - assert(ret); - assert(ret_size); - - STRV_FOREACH(i, l) { - size_t z; - - z = strlen(*i); - - if (!GREEDY_REALLOC(m, n + z + 2)) - return -ENOMEM; - - memcpy(m + n, *i, z + 1); - n += z + 1; - } - - if (!m) { - m = new0(char, 1); - if (!m) - return -ENOMEM; - n = 1; - } else - /* make sure there is a second extra NUL at the end of resulting nulstr */ - m[n] = '\0'; - - assert(n > 0); - *ret = m; - *ret_size = n - 1; - - m = NULL; - - return 0; -} - bool strv_overlap(char * const *a, char * const *b) { STRV_FOREACH(i, a) if (strv_contains(b, *i)) @@ -787,9 +687,9 @@ int strv_compare(char * const *a, char * const *b) { return 0; } -void strv_print(char * const *l) { +void strv_print_full(char * const *l, const char *prefix) { STRV_FOREACH(s, l) - puts(*s); + printf("%s%s\n", strempty(prefix), *s); } int strv_extendf(char ***l, const char *format, ...) { @@ -832,8 +732,7 @@ char** strv_shell_escape(char **l, const char *bad) { if (!v) return NULL; - free(*s); - *s = v; + free_and_replace(*s, v); } return l; @@ -916,6 +815,22 @@ rollback: return -ENOMEM; } +int strv_extend_assignment(char ***l, const char *lhs, const char *rhs) { + char *j; + + assert(l); + assert(lhs); + + if (!rhs) /* value is optional, in which case we suppress the field */ + return 0; + + j = strjoin(lhs, "=", rhs); + if (!j) + return -ENOMEM; + + return strv_consume(l, j); +} + int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) { bool b = false; int r; diff --git a/src/libnm-systemd-shared/src/basic/strv.h b/src/libnm-systemd-shared/src/basic/strv.h index d6f5ac6b..544d46a3 100644 --- a/src/libnm-systemd-shared/src/basic/strv.h +++ b/src/libnm-systemd-shared/src/basic/strv.h @@ -17,6 +17,9 @@ char* strv_find(char * const *l, const char *name) _pure_; char* strv_find_case(char * const *l, const char *name) _pure_; char* strv_find_prefix(char * const *l, const char *name) _pure_; char* strv_find_startswith(char * const *l, const char *name) _pure_; +/* Given two vectors, the first a list of keys and the second a list of key-value pairs, returns the value + * of the first key from the first vector that is found in the second vector. */ +char* strv_find_first_field(char * const *needles, char * const *haystack) _pure_; #define strv_contains(l, s) (!!strv_find((l), (s))) #define strv_contains_case(l, s) (!!strv_find_case((l), (s))) @@ -29,7 +32,10 @@ char** strv_free_erase(char **l); DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase); #define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep) -char** strv_copy(char * const *l); +char** strv_copy_n(char * const *l, size_t n); +static inline char** strv_copy(char * const *l) { + return strv_copy_n(l, SIZE_MAX); +} size_t strv_length(char * const *l) _pure_; int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates); @@ -45,7 +51,7 @@ static inline int strv_extend(char ***l, const char *value) { return strv_extend_with_size(l, NULL, value); } -int strv_extendf(char ***l, const char *format, ...) _printf_(2,0); +int strv_extendf(char ***l, const char *format, ...) _printf_(2,3); int strv_extend_front(char ***l, const char *value); int strv_push_with_size(char ***l, size_t *n, char *value); @@ -84,7 +90,7 @@ char** strv_new_ap(const char *x, va_list ap); #define STRV_IGNORE ((const char *) POINTER_MAX) static inline const char* STRV_IFNOTNULL(const char *x) { - return x ? x : STRV_IGNORE; + return x ?: STRV_IGNORE; } static inline bool strv_isempty(char * const *l) { @@ -124,20 +130,6 @@ static inline char *strv_join(char * const *l, const char *separator) { return strv_join_full(l, separator, NULL, false); } -char** strv_parse_nulstr(const char *s, size_t l); -char** strv_split_nulstr(const char *s); -int strv_make_nulstr(char * const *l, char **p, size_t *n); - -static inline int strv_from_nulstr(char ***a, const char *nulstr) { - char **t; - - t = strv_split_nulstr(nulstr); - if (!t) - return -ENOMEM; - *a = t; - return 0; -} - bool strv_overlap(char * const *a, char * const *b) _pure_; #define _STRV_FOREACH_BACKWARDS(s, l, h, i) \ @@ -160,7 +152,10 @@ bool strv_overlap(char * const *a, char * const *b) _pure_; _STRV_FOREACH_PAIR(x, y, l, UNIQ_T(i, UNIQ)) char** strv_sort(char **l); -void strv_print(char * const *l); +void strv_print_full(char * const *l, const char *prefix); +static inline void strv_print(char * const *l) { + strv_print_full(l, NULL); +} #define strv_from_stdarg_alloca(first) \ ({ \ @@ -205,18 +200,6 @@ void strv_print(char * const *l); _x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \ }) -#define STARTSWITH_SET(p, ...) \ - ({ \ - const char *_p = (p); \ - char *_found = NULL; \ - STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) { \ - _found = startswith(_p, *_i); \ - if (_found) \ - break; \ - } \ - _found; \ - }) - #define ENDSWITH_SET(p, ...) \ ({ \ const char *_p = (p); \ @@ -255,6 +238,8 @@ char** strv_skip(char **l, size_t n); int strv_extend_n(char ***l, const char *value, size_t n); +int strv_extend_assignment(char ***l, const char *lhs, const char *rhs); + int fputstrv(FILE *f, char * const *l, const char *separator, bool *space); #define strv_free_and_replace(a, b) \ diff --git a/src/libnm-systemd-shared/src/basic/time-util.c b/src/libnm-systemd-shared/src/basic/time-util.c index cf556a73..092912b2 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.c +++ b/src/libnm-systemd-shared/src/basic/time-util.c @@ -19,6 +19,7 @@ #include "io-util.h" #include "log.h" #include "macro.h" +#include "missing_threads.h" #include "missing_timerfd.h" #include "parse-util.h" #include "path-util.h" @@ -173,6 +174,8 @@ dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) { dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) { usec_t nowm; + assert(ts); + if (u == USEC_INFINITY) { ts->realtime = ts->monotonic = USEC_INFINITY; return ts; @@ -185,6 +188,7 @@ dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) { } usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) { + assert(ts); switch (clock) { @@ -230,7 +234,7 @@ nsec_t timespec_load_nsec(const struct timespec *ts) { return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec; } -struct timespec *timespec_store(struct timespec *ts, usec_t u) { +struct timespec *timespec_store(struct timespec *ts, usec_t u) { assert(ts); if (u == USEC_INFINITY || @@ -246,7 +250,7 @@ struct timespec *timespec_store(struct timespec *ts, usec_t u) { return ts; } -struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n) { +struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n) { assert(ts); if (n == NSEC_INFINITY || @@ -311,54 +315,48 @@ char *format_timestamp_style( }; struct tm tm; + bool utc, us; time_t sec; size_t n; - bool utc = false, us = false; - int r; assert(buf); + assert(style >= 0); + assert(style < _TIMESTAMP_STYLE_MAX); - switch (style) { - case TIMESTAMP_PRETTY: - case TIMESTAMP_UNIX: - break; - case TIMESTAMP_US: - us = true; - break; - case TIMESTAMP_UTC: - utc = true; - break; - case TIMESTAMP_US_UTC: - us = true; - utc = true; - break; - default: - return NULL; - } - - if (l < (size_t) (3 + /* week day */ - 1 + 10 + /* space and date */ - 1 + 8 + /* space and time */ - (us ? 1 + 6 : 0) + /* "." and microsecond part */ - 1 + 1 + /* space and shortest possible zone */ - 1)) - return NULL; /* Not enough space even for the shortest form. */ if (!timestamp_is_set(t)) return NULL; /* Timestamp is unset */ if (style == TIMESTAMP_UNIX) { - r = snprintf(buf, l, "@" USEC_FMT, t / USEC_PER_SEC); /* round down µs → s */ - if (r < 0 || (size_t) r >= l) - return NULL; /* Doesn't fit */ + if (l < (size_t) (1 + 1 + 1)) + return NULL; /* not enough space for even the shortest of forms */ - return buf; + return snprintf_ok(buf, l, "@" USEC_FMT, t / USEC_PER_SEC); /* round down µs → s */ } + utc = IN_SET(style, TIMESTAMP_UTC, TIMESTAMP_US_UTC, TIMESTAMP_DATE); + us = IN_SET(style, TIMESTAMP_US, TIMESTAMP_US_UTC); + + if (l < (size_t) (3 + /* week day */ + 1 + 10 + /* space and date */ + style == TIMESTAMP_DATE ? 0 : + (1 + 8 + /* space and time */ + (us ? 1 + 6 : 0) + /* "." and microsecond part */ + 1 + (utc ? 3 : 1)) + /* space and shortest possible zone */ + 1)) + return NULL; /* Not enough space even for the shortest form. */ + /* Let's not format times with years > 9999 */ if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) { - assert(l >= STRLEN("--- XXXX-XX-XX XX:XX:XX") + 1); - strcpy(buf, "--- XXXX-XX-XX XX:XX:XX"); - return buf; + static const char* const xxx[_TIMESTAMP_STYLE_MAX] = { + [TIMESTAMP_PRETTY] = "--- XXXX-XX-XX XX:XX:XX", + [TIMESTAMP_US] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX", + [TIMESTAMP_UTC] = "--- XXXX-XX-XX XX:XX:XX UTC", + [TIMESTAMP_US_UTC] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC", + [TIMESTAMP_DATE] = "--- XXXX-XX-XX", + }; + + assert(l >= strlen(xxx[style]) + 1); + return strcpy(buf, xxx[style]); } sec = (time_t) (t / USEC_PER_SEC); /* Round down */ @@ -370,6 +368,14 @@ char *format_timestamp_style( assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays)); memcpy(buf, weekdays[tm.tm_wday], 4); + if (style == TIMESTAMP_DATE) { + /* Special format string if only date should be shown. */ + if (strftime(buf + 3, l - 3, " %Y-%m-%d", &tm) <= 0) + return NULL; /* Doesn't fit */ + + return buf; + } + /* Add the main components */ if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0) return NULL; /* Doesn't fit */ @@ -419,27 +425,29 @@ char *format_timestamp_style( return buf; } -char *format_timestamp_relative(char *buf, size_t l, usec_t t) { +char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, clockid_t clock, bool implicit_left) { const char *s; usec_t n, d; + assert(buf); + if (!timestamp_is_set(t)) return NULL; - n = now(CLOCK_REALTIME); + n = now(clock); if (n > t) { d = n - t; - s = "ago"; + s = " ago"; } else { d = t - n; - s = "left"; + s = implicit_left ? "" : " left"; } 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", + (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s", years, years == 1 ? "year" : "years", months, @@ -449,7 +457,7 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) { 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", + (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s", months, months == 1 ? "month" : "months", days, @@ -459,39 +467,39 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) { 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", + (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); + (void) snprintf(buf, l, USEC_FMT " days%s", d / USEC_PER_DAY,s); else if (d >= 25*USEC_PER_HOUR) - (void) snprintf(buf, l, "1 day " USEC_FMT "h %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) - (void) snprintf(buf, l, USEC_FMT "h %s", + (void) snprintf(buf, l, USEC_FMT "h%s", d / USEC_PER_HOUR, s); else if (d >= USEC_PER_HOUR) - (void) snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %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) - (void) snprintf(buf, l, USEC_FMT "min %s", + (void) snprintf(buf, l, USEC_FMT "min%s", d / USEC_PER_MINUTE, s); else if (d >= USEC_PER_MINUTE) - (void) snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %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) - (void) snprintf(buf, l, USEC_FMT "s %s", + (void) snprintf(buf, l, USEC_FMT "s%s", d / USEC_PER_SEC, s); else if (d >= USEC_PER_MSEC) - (void) snprintf(buf, l, USEC_FMT "ms %s", + (void) snprintf(buf, l, USEC_FMT "ms%s", d / USEC_PER_MSEC, s); else if (d > 0) - (void) snprintf(buf, l, USEC_FMT"us %s", + (void) snprintf(buf, l, USEC_FMT"us%s", d, s); else (void) snprintf(buf, l, "now"); @@ -501,7 +509,7 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) { } #endif /* NM_IGNORED */ -char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { +char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { static const struct { const char *suffix; usec_t usec; @@ -608,7 +616,14 @@ char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { } #if 0 /* NM_IGNORED */ -static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) { +static int parse_timestamp_impl( + const char *t, + size_t tz_offset, + bool utc, + int isdst, + long gmtoff, + usec_t *ret) { + static const struct { const char *name; const int nr; @@ -629,12 +644,14 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) { { "Sat", 6 }, }; - const char *k, *utc = NULL, *tzn = NULL; + _cleanup_free_ char *t_alloc = NULL; + usec_t usec, plus = 0, minus = 0; + bool with_tz = false; + int r, weekday = -1; + unsigned fractional = 0; + const char *k; struct tm tm, copy; - time_t x; - usec_t x_usec, plus = 0, minus = 0, ret; - int r, weekday = -1, dst = -1; - size_t i; + time_t sec; /* Allowed syntaxes: * @@ -650,103 +667,96 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) { * +5min * -5days * @2147483647 (seconds since epoch) + * + * Note, on DST change, 00:00:00 may not exist and in that case the time part may be shifted. + * E.g. "Sun 2023-03-13 America/Havana" is parsed as "Sun 2023-03-13 01:00:00 CDT". */ assert(t); + if (tz_offset != SIZE_MAX) { + /* If the input string contains timezone, then cut it here. */ + + if (tz_offset <= 1) /* timezone must be after a space. */ + return -EINVAL; + + t_alloc = strndup(t, tz_offset - 1); + if (!t_alloc) + return -ENOMEM; + + t = t_alloc; + with_tz = true; + } + + if (utc) { + /* glibc accepts gmtoff more than 24 hours, but we refuse it. */ + if ((usec_t) labs(gmtoff) * USEC_PER_SEC > USEC_PER_DAY) + return -EINVAL; + } else { + if (gmtoff != 0) + return -EINVAL; + } + if (t[0] == '@' && !with_tz) - return parse_sec(t + 1, usec); + return parse_sec(t + 1, ret); - ret = now(CLOCK_REALTIME); + usec = now(CLOCK_REALTIME); if (!with_tz) { if (streq(t, "now")) goto finish; - else if (t[0] == '+') { + if (t[0] == '+') { r = parse_sec(t+1, &plus); if (r < 0) return r; goto finish; + } - } else if (t[0] == '-') { + if (t[0] == '-') { r = parse_sec(t+1, &minus); if (r < 0) return r; goto finish; + } + + if ((k = endswith(t, " ago"))) { + _cleanup_free_ char *buf = NULL; - } else if ((k = endswith(t, " ago"))) { - t = strndupa_safe(t, k - t); + buf = strndup(t, k - t); + if (!buf) + return -ENOMEM; - r = parse_sec(t, &minus); + r = parse_sec(buf, &minus); if (r < 0) return r; goto finish; + } + + if ((k = endswith(t, " left"))) { + _cleanup_free_ char *buf = NULL; - } else if ((k = endswith(t, " left"))) { - t = strndupa_safe(t, k - t); + buf = strndup(t, k - t); + if (!buf) + return -ENOMEM; - r = parse_sec(t, &plus); + r = parse_sec(buf, &plus); if (r < 0) return r; goto finish; } - - /* See if the timestamp is suffixed with UTC */ - utc = endswith_no_case(t, " UTC"); - if (utc) - t = strndupa_safe(t, utc - t); - else { - const char *e = NULL; - int j; - - tzset(); - - /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note - * that we only support the local timezones here, nothing else. Not because we - * wouldn't want to, but simply because there are no nice APIs available to cover - * this. By accepting the local time zone strings, we make sure that all timestamps - * written by format_timestamp() can be parsed correctly, even though we don't - * support arbitrary timezone specifications. */ - - for (j = 0; j <= 1; j++) { - - if (isempty(tzname[j])) - continue; - - e = endswith_no_case(t, tzname[j]); - if (!e) - continue; - if (e == t) - continue; - if (e[-1] != ' ') - continue; - - break; - } - - if (IN_SET(j, 0, 1)) { - /* Found one of the two timezones specified. */ - t = strndupa_safe(t, e - t - 1); - dst = j; - tzn = tzname[j]; - } - } } - x = (time_t) (ret / USEC_PER_SEC); - x_usec = 0; + sec = (time_t) (usec / USEC_PER_SEC); - if (!localtime_or_gmtime_r(&x, &tm, utc)) + if (!localtime_or_gmtime_r(&sec, &tm, utc)) return -EINVAL; - tm.tm_isdst = dst; - if (!with_tz && tzn) - tm.tm_zone = tzn; + tm.tm_isdst = isdst; if (streq(t, "today")) { tm.tm_sec = tm.tm_min = tm.tm_hour = 0; @@ -763,18 +773,13 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) { goto from_tm; } - for (i = 0; i < ELEMENTSOF(day_nr); i++) { - size_t skip; - - if (!startswith_no_case(t, day_nr[i].name)) - continue; - - skip = strlen(day_nr[i].name); - if (t[skip] != ' ') + for (size_t i = 0; i < ELEMENTSOF(day_nr); i++) { + k = startswith_no_case(t, day_nr[i].name); + if (!k || *k != ' ') continue; weekday = day_nr[i].nr; - t += skip + 1; + t = k + 1; break; } @@ -853,65 +858,130 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) { return -EINVAL; parse_usec: - { - unsigned add; - - k++; - r = parse_fractional_part_u(&k, 6, &add); - if (r < 0) - return -EINVAL; - - if (*k) - return -EINVAL; - - x_usec = add; - } + k++; + r = parse_fractional_part_u(&k, 6, &fractional); + if (r < 0) + return -EINVAL; + if (*k != '\0') + return -EINVAL; from_tm: + assert(plus == 0); + assert(minus == 0); + if (weekday >= 0 && tm.tm_wday != weekday) return -EINVAL; - x = mktime_or_timegm(&tm, utc); - if (x < 0) - return -EINVAL; + if (gmtoff < 0) { + plus = -gmtoff * USEC_PER_SEC; + + /* If gmtoff is negative, the string may be too old to be parsed as UTC. + * E.g. 1969-12-31 23:00:00 -06 == 1970-01-01 05:00:00 UTC + * We assumed that gmtoff is in the range of -24:00…+24:00, hence the only date we need to + * handle here is 1969-12-31. So, let's shift the date with one day, then subtract the shift + * later. */ + if (tm.tm_year == 69 && tm.tm_mon == 11 && tm.tm_mday == 31) { + /* Thu 1970-01-01-00:00:00 */ + tm.tm_year = 70; + tm.tm_mon = 0; + tm.tm_mday = 1; + tm.tm_wday = 4; + tm.tm_yday = 0; + minus = USEC_PER_DAY; + } + } else + minus = gmtoff * USEC_PER_SEC; - ret = (usec_t) x * USEC_PER_SEC + x_usec; - if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX) + sec = mktime_or_timegm(&tm, utc); + if (sec < 0) return -EINVAL; + usec = usec_add(sec * USEC_PER_SEC, fractional); + finish: - if (ret + plus < ret) /* overflow? */ - return -EINVAL; - ret += plus; - if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX) + usec = usec_add(usec, plus); + + if (usec < minus) return -EINVAL; - if (ret >= minus) - ret -= minus; - else + usec = usec_sub_unsigned(usec, minus); + + if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX) return -EINVAL; - if (usec) - *usec = ret; + if (ret) + *ret = usec; return 0; } +static int parse_timestamp_maybe_with_tz(const char *t, size_t tz_offset, bool valid_tz, usec_t *ret) { + assert(t); + + tzset(); + + for (int j = 0; j <= 1; j++) { + if (isempty(tzname[j])) + continue; + + if (!streq(t + tz_offset, tzname[j])) + continue; + + /* The specified timezone matches tzname[] of the local timezone. */ + return parse_timestamp_impl(t, tz_offset, /* utc = */ false, /* isdst = */ j, /* gmtoff = */ 0, ret); + } + + /* If we know that the last word is a valid timezone (e.g. Asia/Tokyo), then simply drop the timezone + * and parse the remaining string as a local time. If we know that the last word is not a timezone, + * then assume that it is a part of the time and try to parse the whole string as a local time. */ + return parse_timestamp_impl(t, valid_tz ? tz_offset : SIZE_MAX, + /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret); +} + typedef struct ParseTimestampResult { usec_t usec; int return_value; } ParseTimestampResult; -int parse_timestamp(const char *t, usec_t *usec) { - char *last_space, *tz = NULL; +int parse_timestamp(const char *t, usec_t *ret) { ParseTimestampResult *shared, tmp; + const char *k, *tz, *current_tz; + size_t tz_offset; + struct tm tm; int r; - last_space = strrchr(t, ' '); - if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG)) - tz = last_space + 1; + assert(t); + + tz = strrchr(t, ' '); + if (!tz) + return parse_timestamp_impl(t, /* tz_offset = */ SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret); + + tz++; + tz_offset = tz - t; + + /* Shortcut, parse the string as UTC. */ + if (streq(tz, "UTC")) + return parse_timestamp_impl(t, tz_offset, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret); - if (!tz || endswith_no_case(t, " UTC")) - return parse_timestamp_impl(t, usec, false); + /* If the timezone is compatible with RFC-822/ISO 8601 (e.g. +06, or -03:00) then parse the string as + * UTC and shift the result. Note, this must be earlier than the timezone check with tzname[], as + * tzname[] may be in the same format. */ + k = strptime(tz, "%z", &tm); + if (k && *k == '\0') + return parse_timestamp_impl(t, tz_offset, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret); + + /* If the last word is not a timezone file (e.g. Asia/Tokyo), then let's check if it matches + * tzname[] of the local timezone, e.g. JST or CEST. */ + if (!timezone_is_valid(tz, LOG_DEBUG)) + return parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ false, ret); + + /* Shortcut. If the current $TZ is equivalent to the specified timezone, it is not necessary to fork + * the process. */ + current_tz = getenv("TZ"); + if (current_tz && *current_tz == ':' && streq(current_tz + 1, tz)) + return parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ true, ret); + + /* Otherwise, to avoid polluting the current environment variables, let's fork the process and set + * the specified timezone in the child process. */ shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); if (shared == MAP_FAILED) @@ -923,8 +993,7 @@ int parse_timestamp(const char *t, usec_t *usec) { return r; } if (r == 0) { - bool with_tz = true; - char *colon_tz; + const char *colon_tz; /* tzset(3) says $TZ should be prefixed with ":" if we reference timezone files */ colon_tz = strjoina(":", tz); @@ -934,17 +1003,7 @@ int parse_timestamp(const char *t, usec_t *usec) { _exit(EXIT_FAILURE); } - tzset(); - - /* If there is a timezone that matches the tzname fields, leave the parsing to the implementation. - * Otherwise just cut it off. */ - with_tz = !STR_IN_SET(tz, tzname[0], tzname[1]); - - /* Cut off the timezone if we don't need it. */ - if (with_tz) - t = strndupa_safe(t, last_space - t); - - shared->return_value = parse_timestamp_impl(t, &shared->usec, with_tz); + shared->return_value = parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ true, &shared->usec); _exit(EXIT_SUCCESS); } @@ -953,13 +1012,13 @@ int parse_timestamp(const char *t, usec_t *usec) { if (munmap(shared, sizeof *shared) != 0) return negative_errno(); - if (tmp.return_value == 0 && usec) - *usec = tmp.usec; + if (tmp.return_value == 0 && ret) + *ret = tmp.usec; return tmp.return_value; } -static const char* extract_multiplier(const char *p, usec_t *multiplier) { +static const char* extract_multiplier(const char *p, usec_t *ret) { static const struct { const char *suffix; usec_t usec; @@ -995,12 +1054,15 @@ static const char* extract_multiplier(const char *p, usec_t *multiplier) { { "µs", 1ULL }, }; + assert(p); + assert(ret); + for (size_t i = 0; i < ELEMENTSOF(table); i++) { char *e; e = startswith(p, table[i].suffix); if (e) { - *multiplier = table[i].usec; + *ret = table[i].usec; return e; } } @@ -1008,9 +1070,9 @@ static const char* extract_multiplier(const char *p, usec_t *multiplier) { return p; } -int parse_time(const char *t, usec_t *usec, usec_t default_unit) { +int parse_time(const char *t, usec_t *ret, usec_t default_unit) { const char *p, *s; - usec_t r = 0; + usec_t usec = 0; bool something = false; assert(t); @@ -1025,8 +1087,8 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { if (*s != 0) return -EINVAL; - if (usec) - *usec = USEC_INFINITY; + if (ret) + *ret = USEC_INFINITY; return 0; } @@ -1073,10 +1135,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { return -ERANGE; k = (usec_t) l * multiplier; - if (k >= USEC_INFINITY - r) + if (k >= USEC_INFINITY - usec) return -ERANGE; - r += k; + usec += k; something = true; @@ -1086,10 +1148,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) { k = (usec_t) (*b - '0') * m; - if (k >= USEC_INFINITY - r) + if (k >= USEC_INFINITY - usec) return -ERANGE; - r += k; + usec += k; } /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */ @@ -1098,13 +1160,13 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { } } - if (usec) - *usec = r; + if (ret) + *ret = usec; return 0; } -int parse_sec(const char *t, usec_t *usec) { - return parse_time(t, usec, USEC_PER_SEC); +int parse_sec(const char *t, usec_t *ret) { + return parse_time(t, ret, USEC_PER_SEC); } int parse_sec_fix_0(const char *t, usec_t *ret) { @@ -1123,6 +1185,9 @@ int parse_sec_fix_0(const char *t, usec_t *ret) { } int parse_sec_def_infinity(const char *t, usec_t *ret) { + assert(t); + assert(ret); + t += strspn(t, WHITESPACE); if (isempty(t)) { *ret = USEC_INFINITY; @@ -1131,7 +1196,7 @@ int parse_sec_def_infinity(const char *t, usec_t *ret) { return parse_sec(t, ret); } -static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) { +static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) { static const struct { const char *suffix; nsec_t nsec; @@ -1171,12 +1236,15 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) { }; size_t i; + assert(p); + assert(ret); + for (i = 0; i < ELEMENTSOF(table); i++) { char *e; e = startswith(p, table[i].suffix); if (e) { - *multiplier = table[i].nsec; + *ret = table[i].nsec; return e; } } @@ -1184,13 +1252,13 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) { return p; } -int parse_nsec(const char *t, nsec_t *nsec) { +int parse_nsec(const char *t, nsec_t *ret) { const char *p, *s; - nsec_t r = 0; + nsec_t nsec = 0; bool something = false; assert(t); - assert(nsec); + assert(ret); p = t; @@ -1201,7 +1269,7 @@ int parse_nsec(const char *t, nsec_t *nsec) { if (*s != 0) return -EINVAL; - *nsec = NSEC_INFINITY; + *ret = NSEC_INFINITY; return 0; } @@ -1248,10 +1316,10 @@ int parse_nsec(const char *t, nsec_t *nsec) { return -ERANGE; k = (nsec_t) l * multiplier; - if (k >= NSEC_INFINITY - r) + if (k >= NSEC_INFINITY - nsec) return -ERANGE; - r += k; + nsec += k; something = true; @@ -1261,10 +1329,10 @@ int parse_nsec(const char *t, nsec_t *nsec) { for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) { k = (nsec_t) (*b - '0') * m; - if (k >= NSEC_INFINITY - r) + if (k >= NSEC_INFINITY - nsec) return -ERANGE; - r += k; + nsec += k; } /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */ @@ -1273,7 +1341,7 @@ int parse_nsec(const char *t, nsec_t *nsec) { } } - *nsec = r; + *ret = nsec; return 0; } @@ -1324,6 +1392,8 @@ static int get_timezones_from_tzdata_zi(char ***ret) { _cleanup_strv_free_ char **zones = NULL; int r; + assert(ret); + f = fopen("/usr/share/zoneinfo/tzdata.zi", "re"); if (!f) return -errno; @@ -1401,7 +1471,7 @@ int get_timezones(char ***ret) { int verify_timezone(const char *name, int log_level) { bool slash = false; const char *p, *t; - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; char buf[4]; int r; @@ -1483,6 +1553,8 @@ int get_timezone(char **ret) { char *z; int r; + assert(ret); + r = readlink_malloc("/etc/localtime", &t); if (r == -ENOENT) { /* If the symlink does not exist, assume "UTC", like glibc does */ @@ -1512,10 +1584,15 @@ int get_timezone(char **ret) { } time_t mktime_or_timegm(struct tm *tm, bool utc) { + assert(tm); + return utc ? timegm(tm) : mktime(tm); } struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) { + assert(t); + assert(tm); + return utc ? gmtime_r(t, tm) : localtime_r(t, tm); } @@ -1575,7 +1652,7 @@ int time_change_fd(void) { .it_value.tv_sec = TIME_T_MAX, }; - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX)); @@ -1615,14 +1692,14 @@ int time_change_fd(void) { static const char* const timestamp_style_table[_TIMESTAMP_STYLE_MAX] = { [TIMESTAMP_PRETTY] = "pretty", - [TIMESTAMP_US] = "us", - [TIMESTAMP_UTC] = "utc", + [TIMESTAMP_US] = "us", + [TIMESTAMP_UTC] = "utc", [TIMESTAMP_US_UTC] = "us+utc", - [TIMESTAMP_UNIX] = "unix", + [TIMESTAMP_UNIX] = "unix", }; /* Use the macro for enum → string to allow for aliases */ -_DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle,); +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle); /* For the string → enum mapping we use the generic implementation, but also support two aliases */ TimestampStyle timestamp_style_from_string(const char *s) { diff --git a/src/libnm-systemd-shared/src/basic/time-util.h b/src/libnm-systemd-shared/src/basic/time-util.h index c98f95a5..b49137d5 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.h +++ b/src/libnm-systemd-shared/src/basic/time-util.h @@ -35,6 +35,7 @@ typedef enum TimestampStyle { TIMESTAMP_UTC, TIMESTAMP_US_UTC, TIMESTAMP_UNIX, + TIMESTAMP_DATE, _TIMESTAMP_STYLE_MAX, _TIMESTAMP_STYLE_INVALID = -EINVAL, } TimestampStyle; @@ -65,7 +66,6 @@ typedef enum TimestampStyle { /* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this * to 6. Let's rely on that. */ #define FORMAT_TIMESTAMP_MAX (3U+1U+10U+1U+8U+1U+6U+1U+6U+1U) -#define FORMAT_TIMESTAMP_WIDTH 28U /* when outputting, assume this width */ #define FORMAT_TIMESTAMP_RELATIVE_MAX 256U #define FORMAT_TIMESPAN_MAX 64U @@ -123,10 +123,19 @@ struct timeval* timeval_store(struct timeval *tv, usec_t u); #define TIMEVAL_STORE(u) timeval_store(&(struct timeval) {}, (u)) 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_timestamp_relative_full(char *buf, size_t l, usec_t t, clockid_t clock, bool implicit_left) _warn_unused_result_; char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) _warn_unused_result_; _warn_unused_result_ +static inline char* format_timestamp_relative(char *buf, size_t l, usec_t t) { + return format_timestamp_relative_full(buf, l, t, CLOCK_REALTIME, /* implicit_left = */ false); +} +_warn_unused_result_ +static inline char* format_timestamp_relative_monotonic(char *buf, size_t l, usec_t t) { + return format_timestamp_relative_full(buf, l, t, CLOCK_MONOTONIC, /* implicit_left = */ false); +} + +_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); } @@ -137,19 +146,21 @@ static inline char* format_timestamp(char *buf, size_t l, usec_t t) { #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_TIMESTAMP_RELATIVE_MONOTONIC(t) \ + format_timestamp_relative_monotonic((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_timestamp(const char *t, usec_t *ret); -int parse_sec(const char *t, usec_t *usec); -int parse_sec_fix_0(const char *t, usec_t *usec); -int parse_sec_def_infinity(const char *t, usec_t *usec); -int parse_time(const char *t, usec_t *usec, usec_t default_unit); -int parse_nsec(const char *t, nsec_t *nsec); +int parse_sec(const char *t, usec_t *ret); +int parse_sec_fix_0(const char *t, usec_t *ret); +int parse_sec_def_infinity(const char *t, usec_t *ret); +int parse_time(const char *t, usec_t *ret, usec_t default_unit); +int parse_nsec(const char *t, nsec_t *ret); -int get_timezones(char ***l); +int get_timezones(char ***ret); 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; @@ -159,7 +170,7 @@ bool clock_supported(clockid_t clock); usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to); -int get_timezone(char **timezone); +int get_timezone(char **ret); time_t mktime_or_timegm(struct tm *tm, bool utc); struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc); @@ -200,13 +211,17 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) { return usec_sub_unsigned(timestamp, (usec_t) delta); } +/* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit + * year territory. However, since we want to stay away from this in all timezones we take one day off. */ +#define USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT ((usec_t) 253402214399000000) /* Thu 9999-12-30 23:59:59 UTC */ +/* With a 32bit time_t we can't go beyond 2038... + * We parse timestamp with RFC-822/ISO 8601 (e.g. +06, or -03:00) as UTC, hence the upper bound must be off + * by USEC_PER_DAY. See parse_timestamp() for more details. */ +#define USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT (((usec_t) INT32_MAX) * USEC_PER_SEC - USEC_PER_DAY) #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) +# define USEC_TIMESTAMP_FORMATTABLE_MAX USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT #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_TIMESTAMP_FORMATTABLE_MAX_32BIT #else # error "Yuck, time_t is neither 4 nor 8 bytes wide?" #endif diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.c b/src/libnm-systemd-shared/src/basic/tmpfile-util.c index 2a27f86b..a66ee82d 100644 --- a/src/libnm-systemd-shared/src/basic/tmpfile-util.c +++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.c @@ -16,34 +16,22 @@ #include "path-util.h" #include "process-util.h" #include "random-util.h" +#include "stat-util.h" #include "stdio-util.h" #include "string-util.h" +#include "sync-util.h" #include "tmpfile-util.h" #include "umask-util.h" -int fopen_temporary(const char *path, FILE **ret_f, char **ret_temp_path) { +static int fopen_temporary_internal(int dir_fd, const char *path, FILE **ret_file) { _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *t = NULL; - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; int r; - if (path) { - r = tempfn_xxxxxx(path, NULL, &t); - if (r < 0) - return r; - } else { - const char *d; - - r = tmp_dir(&d); - if (r < 0) - return r; - - t = path_join(d, "XXXXXX"); - if (!t) - return -ENOMEM; - } + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); - fd = mkostemp_safe(t); + fd = openat(dir_fd, path, O_CLOEXEC|O_NOCTTY|O_RDWR|O_CREAT|O_EXCL, 0600); if (fd < 0) return -errno; @@ -52,15 +40,59 @@ int fopen_temporary(const char *path, FILE **ret_f, char **ret_temp_path) { r = take_fdopen_unlocked(&fd, "w", &f); if (r < 0) { - (void) unlink(t); + (void) unlinkat(dir_fd, path, 0); + return r; + } + + if (ret_file) + *ret_file = TAKE_PTR(f); + + return 0; +} + +int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) { + _cleanup_free_ char *t = NULL; + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); + + r = tempfn_random(path, NULL, &t); + if (r < 0) return r; + + r = fopen_temporary_internal(dir_fd, t, ret_file); + if (r < 0) + return r; + + if (ret_path) + *ret_path = TAKE_PTR(t); + + return 0; +} + +int fopen_temporary_child_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) { + _cleanup_free_ char *t = NULL; + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + + if (!path) { + r = tmp_dir(&path); + if (r < 0) + return r; } - if (ret_f) - *ret_f = TAKE_PTR(f); + r = tempfn_random_child(path, NULL, &t); + if (r < 0) + return r; - if (ret_temp_path) - *ret_temp_path = TAKE_PTR(t); + r = fopen_temporary_internal(dir_fd, t, ret_file); + if (r < 0) + return r; + + if (ret_path) + *ret_path = TAKE_PTR(t); return 0; } @@ -74,7 +106,7 @@ int mkostemp_safe(char *pattern) { #if 0 /* NM_IGNORED */ int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) { - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; FILE *f; fd = mkostemp_safe(pattern); @@ -171,7 +203,6 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { return tempfn_build(p, extra, "XXXXXX", /* child = */ false, ret); } -#if 0 /* NM_IGNORED */ int tempfn_random(const char *p, const char *extra, char **ret) { _cleanup_free_ char *s = NULL; @@ -216,6 +247,7 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) { return tempfn_build(p, extra, s, /* child = */ true, ret); } +#if 0 /* NM_IGNORED */ int open_tmpfile_unlinkable(const char *directory, int flags) { char *p; int fd, r; @@ -246,7 +278,7 @@ int open_tmpfile_unlinkable(const char *directory, int flags) { return fd; } -int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { +int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path) { _cleanup_free_ char *tmp = NULL; int r, fd; @@ -260,7 +292,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { * which case "ret_path" will be returned as NULL. If not possible the temporary path name used is returned in * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */ - fd = open_parent(target, O_TMPFILE|flags, 0640); + fd = open_parent_at(dir_fd, target, O_TMPFILE|flags, 0640); if (fd >= 0) { *ret_path = NULL; return fd; @@ -272,7 +304,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { if (r < 0) return r; - fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640); + fd = openat(dir_fd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640); if (fd < 0) return -errno; @@ -284,7 +316,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file) { _cleanup_free_ char *path = NULL; _cleanup_fclose_ FILE *f = NULL; - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -EBADF; assert(target); assert(ret_file); @@ -303,24 +335,84 @@ int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE return 0; } -int link_tmpfile(int fd, const char *path, const char *target) { +static int link_fd(int fd, int newdirfd, const char *newpath) { + int r; + + assert(fd >= 0); + assert(newdirfd >= 0 || newdirfd == AT_FDCWD); + assert(newpath); + + /* Try symlinking via /proc/fd/ first. */ + r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW)); + if (r != -ENOENT) + return r; + + /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a + * more recent kernel, but does not require /proc/ mounted) */ + if (proc_mounted() != 0) + return r; + + return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH)); +} + +int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, LinkTmpfileFlags flags) { + _cleanup_free_ char *tmp = NULL; + int r; + assert(fd >= 0); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(target); - /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd - * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported - * on the directory, and renameat2() is used instead. - * - * Note that in both cases we will not replace existing files. This is because linkat() does not support this - * operation currently (renameat2() does), and there is no nice way to emulate this. */ + /* Moves a temporary file created with open_tmpfile() above into its final place. If "path" is NULL + * an fd created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE + * is not supported on the directory, and renameat2() is used instead. */ + + if (FLAGS_SET(flags, LINK_TMPFILE_SYNC) && fsync(fd) < 0) + return -errno; + + if (path) { + if (FLAGS_SET(flags, LINK_TMPFILE_REPLACE)) + r = RET_NERRNO(renameat(dir_fd, path, dir_fd, target)); + else + r = rename_noreplace(dir_fd, path, dir_fd, target); + if (r < 0) + return r; + } else { + + r = link_fd(fd, dir_fd, target); + if (r != -EEXIST || !FLAGS_SET(flags, LINK_TMPFILE_REPLACE)) + return r; + + /* So the target already exists and we were asked to replace it. That sucks a bit, since the kernel's + * linkat() logic does not allow that. We work-around this by linking the file to a random name + * first, and then renaming that to the final name. This reintroduces the race O_TMPFILE kinda is + * trying to fix, but at least the vulnerability window (i.e. where the file is linked into the file + * system under a temporary name) is very short. */ + + r = tempfn_random(target, NULL, &tmp); + if (r < 0) + return r; + + if (link_fd(fd, dir_fd, tmp) < 0) + return -EEXIST; /* propagate original error */ + + r = RET_NERRNO(renameat(dir_fd, tmp, dir_fd, target)); + if (r < 0) { + (void) unlinkat(dir_fd, tmp, 0); + return r; + } + } - if (path) - return rename_noreplace(AT_FDCWD, path, AT_FDCWD, target); + if (FLAGS_SET(flags, LINK_TMPFILE_SYNC)) { + r = fsync_full(fd); + if (r < 0) + return r; + } - return RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), AT_FDCWD, target, AT_SYMLINK_FOLLOW)); + return 0; } -int flink_tmpfile(FILE *f, const char *path, const char *target) { +int flink_tmpfile(FILE *f, const char *path, const char *target, LinkTmpfileFlags flags) { int fd, r; assert(f); @@ -330,11 +422,11 @@ int flink_tmpfile(FILE *f, const char *path, const char *target) { if (fd < 0) /* Not all FILE* objects encapsulate fds */ return -EBADF; - r = fflush_sync_and_check(f); + r = fflush_and_check(f); if (r < 0) return r; - return link_tmpfile(fd, path, target); + return link_tmpfile(fd, path, target, flags); } int mkdtemp_malloc(const char *template, char **ret) { @@ -363,4 +455,24 @@ int mkdtemp_malloc(const char *template, char **ret) { *ret = TAKE_PTR(p); return 0; } + +int mkdtemp_open(const char *template, int flags, char **ret) { + _cleanup_free_ char *p = NULL; + int fd, r; + + r = mkdtemp_malloc(template, &p); + if (r < 0) + return r; + + fd = RET_NERRNO(open(p, O_DIRECTORY|O_CLOEXEC|flags)); + if (fd < 0) { + (void) rmdir(p); + return fd; + } + + if (ret) + *ret = TAKE_PTR(p); + + return fd; +} #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.h b/src/libnm-systemd-shared/src/basic/tmpfile-util.h index 610cbaf8..50904eca 100644 --- a/src/libnm-systemd-shared/src/basic/tmpfile-util.h +++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.h @@ -1,9 +1,20 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include <fcntl.h> +#include <stdbool.h> #include <stdio.h> -int fopen_temporary(const char *path, FILE **_f, char **_temp_path); +int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path); +static inline int fopen_temporary(const char *path, FILE **ret_file, char **ret_path) { + return fopen_temporary_at(AT_FDCWD, path, ret_file, ret_path); +} + +int fopen_temporary_child_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path); +static inline int fopen_temporary_child(const char *path, FILE **ret_file, char **ret_path) { + return fopen_temporary_child_at(AT_FDCWD, path, ret_file, ret_path); +} + int mkostemp_safe(char *pattern); int fmkostemp_safe(char *pattern, const char *mode, FILE**_f); @@ -12,10 +23,23 @@ int tempfn_random(const char *p, const char *extra, char **ret); int tempfn_random_child(const char *p, const char *extra, char **ret); int open_tmpfile_unlinkable(const char *directory, int flags); -int open_tmpfile_linkable(const char *target, int flags, char **ret_path); +int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path); +static inline int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { + return open_tmpfile_linkable_at(AT_FDCWD, target, flags, ret_path); +} int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file); -int link_tmpfile(int fd, const char *path, const char *target); -int flink_tmpfile(FILE *f, const char *path, const char *target); + +typedef enum LinkTmpfileFlags { + LINK_TMPFILE_REPLACE = 1 << 0, + LINK_TMPFILE_SYNC = 1 << 1, +} LinkTmpfileFlags; + +int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, LinkTmpfileFlags flags); +static inline int link_tmpfile(int fd, const char *path, const char *target, LinkTmpfileFlags flags) { + return link_tmpfile_at(fd, AT_FDCWD, path, target, flags); +} +int flink_tmpfile(FILE *f, const char *path, const char *target, LinkTmpfileFlags flags); int mkdtemp_malloc(const char *template, char **ret); +int mkdtemp_open(const char *template, int flags, char **ret); diff --git a/src/libnm-systemd-shared/src/basic/umask-util.h b/src/libnm-systemd-shared/src/basic/umask-util.h index 90d18f70..6f0e1cc2 100644 --- a/src/libnm-systemd-shared/src/basic/umask-util.h +++ b/src/libnm-systemd-shared/src/basic/umask-util.h @@ -15,12 +15,12 @@ static inline void umaskp(mode_t *u) { /* We make use of the fact here that the umask() concept is using only the lower 9 bits of mode_t, although * mode_t has space for the file type in the bits further up. We simply OR in the file type mask S_IFMT to - * distinguish the first and the second iteration of the RUN_WITH_UMASK() loop, so that we can run the first - * one, and exit on the second. */ + * distinguish the first and the second iteration of the WITH_UMASK() loop, so that we can run the first one, + * and exit on the second. */ assert_cc((S_IFMT & 0777) == 0); -#define RUN_WITH_UMASK(mask) \ +#define WITH_UMASK(mask) \ for (_cleanup_umask_ mode_t _saved_umask_ = umask(mask) | S_IFMT; \ FLAGS_SET(_saved_umask_, S_IFMT); \ _saved_umask_ &= 0777) diff --git a/src/libnm-systemd-shared/src/basic/user-util.h b/src/libnm-systemd-shared/src/basic/user-util.h index a08683bc..8b829a9a 100644 --- a/src/libnm-systemd-shared/src/basic/user-util.h +++ b/src/libnm-systemd-shared/src/basic/user-util.h @@ -80,7 +80,8 @@ int take_etc_passwd_lock(const char *root); #define UID_MAPPED_ROOT ((uid_t) (INT32_MAX-1)) #define GID_MAPPED_ROOT ((gid_t) (INT32_MAX-1)) -#define ETC_PASSWD_LOCK_PATH "/etc/.pwd.lock" +#define ETC_PASSWD_LOCK_FILENAME ".pwd.lock" +#define ETC_PASSWD_LOCK_PATH "/etc/" ETC_PASSWD_LOCK_FILENAME /* The following macros add 1 when converting things, since UID 0 is a valid UID, while the pointer * NULL is special */ @@ -102,7 +103,7 @@ typedef enum ValidUserFlags { bool valid_user_group_name(const char *u, ValidUserFlags flags); bool valid_gecos(const char *d); -char *mangle_gecos(const char *d); +char* mangle_gecos(const char *d); bool valid_home(const char *p); static inline bool valid_shell(const char *p) { @@ -130,11 +131,12 @@ int putsgent_sane(const struct sgrp *sg, FILE *stream); #endif bool is_nologin_shell(const char *shell); +const char* default_root_shell_at(int rfd); const char* default_root_shell(const char *root); int is_this_me(const char *username); -const char *get_home_root(void); +const char* get_home_root(void); static inline bool hashed_password_is_locked_or_invalid(const char *password) { return password && password[0] != '$'; @@ -148,3 +150,8 @@ static inline bool hashed_password_is_locked_or_invalid(const char *password) { /* A password indicating "hey, no password required for login" */ #define PASSWORD_NONE "" + +/* Used by sysusers to indicate that the password should be filled in by firstboot. + * Also see https://github.com/systemd/systemd/pull/24680#pullrequestreview-1439464325. + */ +#define PASSWORD_UNPROVISIONED "!unprovisioned" diff --git a/src/libnm-systemd-shared/src/basic/util.c b/src/libnm-systemd-shared/src/basic/util.c deleted file mode 100644 index 3425d0f0..00000000 --- a/src/libnm-systemd-shared/src/basic/util.c +++ /dev/null @@ -1,178 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "nm-sd-adapt-shared.h" - -#include <errno.h> -#include <fcntl.h> -#include <sys/mman.h> - -#include "alloc-util.h" -#include "build.h" -#include "env-file.h" -#include "env-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "hostname-util.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "util.h" -#include "virt.h" - -#if 0 /* NM_IGNORED */ -int saved_argc = 0; -char **saved_argv = NULL; -static int saved_in_initrd = -1; - -bool kexec_loaded(void) { - _cleanup_free_ char *s = NULL; - - if (read_one_line_file("/sys/kernel/kexec_loaded", &s) < 0) - return false; - - return s[0] == '1'; -} - -int prot_from_flags(int flags) { - - switch (flags & O_ACCMODE) { - - case O_RDONLY: - return PROT_READ; - - case O_WRONLY: - return PROT_WRITE; - - case O_RDWR: - return PROT_READ|PROT_WRITE; - - default: - return -EINVAL; - } -} - -bool in_initrd(void) { - int r; - const char *e; - bool lenient = false; - - if (saved_in_initrd >= 0) - return saved_in_initrd; - - /* We have two checks here: - * - * 1. the flag file /etc/initrd-release must exist - * 2. the root file system must be a memory file system - * - * The second check is extra paranoia, since misdetecting an - * initrd can have bad consequences due the initrd - * emptying when transititioning to the main systemd. - * - * If env var $SYSTEMD_IN_INITRD is not set or set to "auto", - * both checks are used. If it's set to "lenient", only check - * 1 is used. If set to a boolean value, then the boolean - * value is returned. - */ - - e = secure_getenv("SYSTEMD_IN_INITRD"); - if (e) { - if (streq(e, "lenient")) - lenient = true; - else if (!streq(e, "auto")) { - r = parse_boolean(e); - if (r >= 0) { - saved_in_initrd = r > 0; - return saved_in_initrd; - } - log_debug_errno(r, "Failed to parse $SYSTEMD_IN_INITRD, ignoring: %m"); - } - } - - if (!lenient) { - r = path_is_temporary_fs("/"); - if (r < 0) - log_debug_errno(r, "Couldn't determine if / is a temporary file system: %m"); - - saved_in_initrd = r > 0; - } - - r = access("/etc/initrd-release", F_OK); - if (r >= 0) { - if (saved_in_initrd == 0) - log_debug("/etc/initrd-release exists, but it's not an initrd."); - else - saved_in_initrd = 1; - } else { - if (errno != ENOENT) - log_debug_errno(errno, "Failed to test if /etc/initrd-release exists: %m"); - saved_in_initrd = 0; - } - - return saved_in_initrd; -} - -void in_initrd_force(bool value) { - saved_in_initrd = value; -} - -int container_get_leader(const char *machine, pid_t *pid) { - _cleanup_free_ char *s = NULL, *class = NULL; - const char *p; - pid_t leader; - int r; - - assert(machine); - assert(pid); - - if (streq(machine, ".host")) { - *pid = 1; - return 0; - } - - if (!hostname_is_valid(machine, 0)) - return -EINVAL; - - p = strjoina("/run/systemd/machines/", machine); - r = parse_env_file(NULL, p, - "LEADER", &s, - "CLASS", &class); - if (r == -ENOENT) - return -EHOSTDOWN; - if (r < 0) - return r; - if (!s) - return -EIO; - - if (!streq_ptr(class, "container")) - return -EIO; - - r = parse_pid(s, &leader); - if (r < 0) - return r; - if (leader <= 1) - return -EIO; - - *pid = leader; - return 0; -} - -int version(void) { - printf("systemd " STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")\n%s\n", - systemd_features); - return 0; -} - -/* Turn off core dumps but only if we're running outside of a container. */ -void disable_coredumps(void) { - int r; - - if (detect_container() > 0) - return; - - r = write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", WRITE_STRING_FILE_DISABLE_BUFFER); - if (r < 0) - log_debug_errno(r, "Failed to turn off coredumps, ignoring: %m"); -} -#endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/util.h b/src/libnm-systemd-shared/src/fundamental/logarithm.h index 368e3e4f..5f742951 100644 --- a/src/libnm-systemd-shared/src/basic/util.h +++ b/src/libnm-systemd-shared/src/fundamental/logarithm.h @@ -3,29 +3,6 @@ #include <stdint.h> -#include "macro.h" - -extern int saved_argc; -extern char **saved_argv; - -static inline void save_argc_argv(int argc, char **argv) { - - /* Protect against CVE-2021-4034 style attacks */ - assert_se(argc > 0); - assert_se(argv); - assert_se(argv[0]); - - saved_argc = argc; - saved_argv = argv; -} - -bool kexec_loaded(void); - -int prot_from_flags(int flags) _const_; - -bool in_initrd(void); -void in_initrd_force(bool value); - /* Note: log2(0) == log2(1) == 0 here and below. */ #define CONST_LOG2ULL(x) ((x) > 1 ? (unsigned) __builtin_clzll(x) ^ 63U : 0) @@ -55,6 +32,14 @@ static inline unsigned u32ctz(uint32_t n) { #endif } +#define popcount(n) \ + _Generic((n), \ + unsigned char: __builtin_popcount(n), \ + unsigned short: __builtin_popcount(n), \ + unsigned: __builtin_popcount(n), \ + unsigned long: __builtin_popcountl(n), \ + unsigned long long: __builtin_popcountll(n)) + #define CONST_LOG2U(x) ((x) > 1 ? __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1 : 0) #define NONCONST_LOG2U(x) ({ \ unsigned _x = (x); \ @@ -80,9 +65,3 @@ static inline unsigned log2u_round_up(unsigned x) { return log2u(x - 1) + 1; } - -int container_get_leader(const char *machine, pid_t *pid); - -int version(void); - -void disable_coredumps(void); diff --git a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h index 0c69d98a..89b83e7d 100644 --- a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h @@ -1,17 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#ifndef SD_BOOT +#if !SD_BOOT # include <assert.h> #endif #include <limits.h> +#include <stdalign.h> #include <stdbool.h> #include <stddef.h> #include <stdint.h> #define _align_(x) __attribute__((__aligned__(x))) -#define _alignas_(x) __attribute__((__aligned__(__alignof__(x)))) +#define _alignas_(x) __attribute__((__aligned__(alignof(x)))) #define _alignptr_ __attribute__((__aligned__(sizeof(void *)))) #define _cleanup_(x) __attribute__((__cleanup__(x))) #define _const_ __attribute__((__const__)) @@ -20,6 +21,7 @@ #define _hidden_ __attribute__((__visibility__("hidden"))) #define _likely_(x) (__builtin_expect(!!(x), 1)) #define _malloc_ __attribute__((__malloc__)) +#define _noinline_ __attribute__((noinline)) #define _noreturn_ _Noreturn #define _packed_ __attribute__((__packed__)) #define _printf_(a, b) __attribute__((__format__(printf, a, b))) @@ -36,6 +38,13 @@ #define _weak_ __attribute__((__weak__)) #define _weakref_(x) __attribute__((__weakref__(#x))) +/* !NM_IGNORED */ +#if __GNUC__ <= 4 +#undef _returns_nonnull_ +#define _returns_nonnull_ +#endif +/* !NM_IGNORED */ + #ifdef __clang__ # define _alloc_(...) #else @@ -66,18 +75,18 @@ #define XCONCATENATE(x, y) x ## y #define CONCATENATE(x, y) XCONCATENATE(x, y) -#ifdef SD_BOOT +#if SD_BOOT _noreturn_ void efi_assert(const char *expr, const char *file, unsigned line, const char *function); #ifdef NDEBUG #define assert(expr) #define assert_not_reached() __builtin_unreachable() #else - #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__) + #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); }) + #define assert_not_reached() efi_assert("Code should not be reached", __FILE__, __LINE__, __func__) #endif #define static_assert _Static_assert - #define assert_se(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); }) + #define assert_se(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); }) #endif /* This passes the argument through after (if asserts are enabled) checking that it is not null. */ @@ -106,7 +115,6 @@ #define assert_cc(expr) static_assert(expr, #expr) - #define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq)) #define UNIQ __COUNTER__ @@ -259,47 +267,54 @@ (UNIQ_T(X, xq) / UNIQ_T(Y, yq) + !!(UNIQ_T(X, xq) % UNIQ_T(Y, yq))); \ }) -#define CASE_F(X) case X: -#define CASE_F_1(CASE, X) CASE_F(X) -#define CASE_F_2(CASE, X, ...) CASE(X) CASE_F_1(CASE, __VA_ARGS__) -#define CASE_F_3(CASE, X, ...) CASE(X) CASE_F_2(CASE, __VA_ARGS__) -#define CASE_F_4(CASE, X, ...) CASE(X) CASE_F_3(CASE, __VA_ARGS__) -#define CASE_F_5(CASE, X, ...) CASE(X) CASE_F_4(CASE, __VA_ARGS__) -#define CASE_F_6(CASE, X, ...) CASE(X) CASE_F_5(CASE, __VA_ARGS__) -#define CASE_F_7(CASE, X, ...) CASE(X) CASE_F_6(CASE, __VA_ARGS__) -#define CASE_F_8(CASE, X, ...) CASE(X) CASE_F_7(CASE, __VA_ARGS__) -#define CASE_F_9(CASE, X, ...) CASE(X) CASE_F_8(CASE, __VA_ARGS__) -#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__) -#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__) -#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__) -#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__) -#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__) -#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__) -#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__) -#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__) -#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__) -#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__) -#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__) +/* Rounds up x to the next multiple of y. Resolves to typeof(x) -1 in case of overflow */ +#define __ROUND_UP(q, x, y) \ + ({ \ + const typeof(y) UNIQ_T(A, q) = (y); \ + const typeof(x) UNIQ_T(B, q) = DIV_ROUND_UP((x), UNIQ_T(A, q)); \ + typeof(x) UNIQ_T(C, q); \ + __builtin_mul_overflow(UNIQ_T(B, q), UNIQ_T(A, q), &UNIQ_T(C, q)) ? (typeof(x)) -1 : UNIQ_T(C, q); \ + }) +#define ROUND_UP(x, y) __ROUND_UP(UNIQ, (x), (y)) + +#define CASE_F_1(X) case X: +#define CASE_F_2(X, ...) case X: CASE_F_1( __VA_ARGS__) +#define CASE_F_3(X, ...) case X: CASE_F_2( __VA_ARGS__) +#define CASE_F_4(X, ...) case X: CASE_F_3( __VA_ARGS__) +#define CASE_F_5(X, ...) case X: CASE_F_4( __VA_ARGS__) +#define CASE_F_6(X, ...) case X: CASE_F_5( __VA_ARGS__) +#define CASE_F_7(X, ...) case X: CASE_F_6( __VA_ARGS__) +#define CASE_F_8(X, ...) case X: CASE_F_7( __VA_ARGS__) +#define CASE_F_9(X, ...) case X: CASE_F_8( __VA_ARGS__) +#define CASE_F_10(X, ...) case X: CASE_F_9( __VA_ARGS__) +#define CASE_F_11(X, ...) case X: CASE_F_10( __VA_ARGS__) +#define CASE_F_12(X, ...) case X: CASE_F_11( __VA_ARGS__) +#define CASE_F_13(X, ...) case X: CASE_F_12( __VA_ARGS__) +#define CASE_F_14(X, ...) case X: CASE_F_13( __VA_ARGS__) +#define CASE_F_15(X, ...) case X: CASE_F_14( __VA_ARGS__) +#define CASE_F_16(X, ...) case X: CASE_F_15( __VA_ARGS__) +#define CASE_F_17(X, ...) case X: CASE_F_16( __VA_ARGS__) +#define CASE_F_18(X, ...) case X: CASE_F_17( __VA_ARGS__) +#define CASE_F_19(X, ...) case X: CASE_F_18( __VA_ARGS__) +#define CASE_F_20(X, ...) case X: CASE_F_19( __VA_ARGS__) #define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME #define FOR_EACH_MAKE_CASE(...) \ GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \ CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \ - (CASE_F,__VA_ARGS__) + (__VA_ARGS__) -#define IN_SET(x, ...) \ +#define IN_SET(x, first, ...) \ ({ \ bool _found = false; \ - /* If the build breaks in the line below, you need to extend the case macros. (We use "long double" as \ - * type for the array, in the hope that checkers such as ubsan don't complain that the initializers for \ - * the array are not representable by the base type. Ideally we'd use typeof(x) as base type, but that \ - * doesn't work, as we want to use this on bitfields and gcc refuses typeof() on bitfields.) */ \ - static const long double __assert_in_set[] _unused_ = { __VA_ARGS__ }; \ + /* If the build breaks in the line below, you need to extend the case macros. We use typeof(+x) \ + * here to widen the type of x if it is a bit-field as this would otherwise be illegal. */ \ + static const typeof(+x) __assert_in_set[] _unused_ = { first, __VA_ARGS__ }; \ assert_cc(ELEMENTSOF(__assert_in_set) <= 20); \ switch (x) { \ - FOR_EACH_MAKE_CASE(__VA_ARGS__) \ + FOR_EACH_MAKE_CASE(first, __VA_ARGS__) \ _found = true; \ - break; \ + break; \ default: \ break; \ } \ @@ -308,13 +323,18 @@ /* Takes inspiration from Rust's Option::take() method: reads and returns a pointer, but at the same time * resets it to NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */ -#define TAKE_PTR(ptr) \ - ({ \ - typeof(ptr) *_pptr_ = &(ptr); \ - typeof(ptr) _ptr_ = *_pptr_; \ - *_pptr_ = NULL; \ - _ptr_; \ +#define TAKE_GENERIC(var, type, nullvalue) \ + ({ \ + type *_pvar_ = &(var); \ + type _var_ = *_pvar_; \ + type _nullvalue_ = nullvalue; \ + *_pvar_ = _nullvalue_; \ + _var_; \ }) +#define TAKE_PTR_TYPE(ptr, type) TAKE_GENERIC(ptr, type, NULL) +#define TAKE_PTR(ptr) TAKE_PTR_TYPE(ptr, typeof(ptr)) +#define TAKE_STRUCT_TYPE(s, type) TAKE_GENERIC(s, type, {}) +#define TAKE_STRUCT(s) TAKE_STRUCT_TYPE(s, typeof(s)) /* * STRLEN - return the length of a string literal, minus the trailing NUL byte. @@ -338,13 +358,19 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) { return ((l + ali - 1) & ~(ali - 1)); } +#define ALIGN2(l) ALIGN_TO(l, 2) #define ALIGN4(l) ALIGN_TO(l, 4) #define ALIGN8(l) ALIGN_TO(l, 8) -#ifndef SD_BOOT -/* libefi also provides ALIGN, and we do not use them in sd-boot explicitly. */ +#define ALIGN2_PTR(p) ((void*) ALIGN2((uintptr_t) p)) +#define ALIGN4_PTR(p) ((void*) ALIGN4((uintptr_t) p)) +#define ALIGN8_PTR(p) ((void*) ALIGN8((uintptr_t) p)) #define ALIGN(l) ALIGN_TO(l, sizeof(void*)) #define ALIGN_PTR(p) ((void*) ALIGN((uintptr_t) (p))) -#endif + +/* Checks if the specified pointer is aligned as appropriate for the specific type */ +#define IS_ALIGNED16(p) (((uintptr_t) p) % alignof(uint16_t) == 0) +#define IS_ALIGNED32(p) (((uintptr_t) p) % alignof(uint32_t) == 0) +#define IS_ALIGNED64(p) (((uintptr_t) p) % alignof(uint64_t) == 0) /* Same as ALIGN_TO but callable in constant contexts. */ #define CONST_ALIGN_TO(l, ali) \ @@ -356,9 +382,31 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) { ((l) + (ali) - 1) & ~((ali) - 1), \ VOID_0) +/* Similar to ((t *) (void *) (p)) to cast a pointer. The macro asserts that the pointer has a suitable + * alignment for type "t". This exists for places where otherwise "-Wcast-align=strict" would issue a + * warning or if you want to assert that the cast gives a pointer of suitable alignment. */ +#define CAST_ALIGN_PTR(t, p) \ + ({ \ + const void *_p = (p); \ + assert(((uintptr_t) _p) % alignof(t) == 0); \ + (t *) _p; \ + }) + #define UPDATE_FLAG(orig, flag, b) \ ((b) ? ((orig) | (flag)) : ((orig) & ~(flag))) #define SET_FLAG(v, flag, b) \ (v) = UPDATE_FLAG(v, flag, b) #define FLAGS_SET(v, flags) \ ((~(v) & (flags)) == 0) + +/* Declare a flexible array usable in a union. + * This is essentially a work-around for a pointless constraint in C99 + * and might go away in some future version of the standard. + * + * See https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=3080ea5553cc909b000d1f1d964a9041962f2c5b + */ +#define DECLARE_FLEX_ARRAY(type, name) \ + struct { \ + dummy_t __empty__ ## name; \ + type name[]; \ + } diff --git a/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h new file mode 100644 index 00000000..78e2dbec --- /dev/null +++ b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stddef.h> + +#if SD_BOOT +# include "efi-string.h" +#else +# include <string.h> +#endif + +#include "macro-fundamental.h" + +#if !SD_BOOT && HAVE_EXPLICIT_BZERO +static inline void *explicit_bzero_safe(void *p, size_t l) { + if (p && l > 0) + explicit_bzero(p, l); + + return p; +} +#else +static inline void *explicit_bzero_safe(void *p, size_t l) { + if (p && l > 0) { + memset(p, 0, l); + __asm__ __volatile__("" : : "r"(p) : "memory"); + } + return p; +} +#endif + +struct VarEraser { + /* NB: This is a pointer to memory to erase in case of CLEANUP_ERASE(). Pointer to pointer to memory + * to erase in case of CLEANUP_ERASE_PTR() */ + void *p; + size_t size; +}; + +static inline void erase_var(struct VarEraser *e) { + explicit_bzero_safe(e->p, e->size); +} + +/* Mark var to be erased when leaving scope. */ +#define CLEANUP_ERASE(var) \ + _cleanup_(erase_var) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { \ + .p = &(var), \ + .size = sizeof(var), \ + } + +static inline void erase_varp(struct VarEraser *e) { + + /* Very similar to erase_var(), but assumes `p` is a pointer to a pointer whose memory shall be destructed. */ + if (!e->p) + return; + + explicit_bzero_safe(*(void**) e->p, e->size); +} + +/* Mark pointer so that memory pointed to is erased when leaving scope. Note: this takes a pointer to the + * specified pointer, instead of just a copy of it. This is to allow callers to invalidate the pointer after + * use, if they like, disabling our automatic erasure (for example because they succeeded with whatever they + * wanted to do and now intend to return the allocated buffer to their caller without it being erased). */ +#define CLEANUP_ERASE_PTR(ptr, sz) \ + _cleanup_(erase_varp) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { \ + .p = (ptr), \ + .size = (sz), \ + } diff --git a/src/libnm-systemd-shared/src/fundamental/sha256.c b/src/libnm-systemd-shared/src/fundamental/sha256.c index b4c36ab3..a4c6d627 100644 --- a/src/libnm-systemd-shared/src/fundamental/sha256.c +++ b/src/libnm-systemd-shared/src/fundamental/sha256.c @@ -24,7 +24,7 @@ <https://www.gnu.org/licenses/>. */ #include <stdbool.h> -#ifdef SD_BOOT +#if SD_BOOT # include "efi-string.h" #else # include <string.h> @@ -32,6 +32,7 @@ #include "macro-fundamental.h" #include "sha256.h" +#include "unaligned-fundamental.h" #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ # define SWAP(n) \ @@ -50,14 +51,6 @@ # define SWAP64(n) (n) #endif -/* The condition below is from glibc's string/string-inline.c. - * See definition of _STRING_INLINE_unaligned. */ -#if !defined(__mc68020__) && !defined(__s390__) && !defined(__i386__) -# define UNALIGNED_P(p) (((uintptr_t) p) % __alignof__(uint32_t) != 0) -#else -# define UNALIGNED_P(p) false -#endif - /* This array contains the bytes used to pad the buffer to the next 64-byte boundary. (FIPS 180-2:5.1.1) */ static const uint8_t fillbuf[64] = { @@ -130,11 +123,7 @@ uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_ /* Put result from CTX in first 32 bytes following RESBUF. */ for (size_t i = 0; i < 8; ++i) - if (UNALIGNED_P(resbuf)) - memcpy(resbuf + i * sizeof(uint32_t), (uint32_t[]) { SWAP(ctx->H[i]) }, sizeof(uint32_t)); - else - ((uint32_t *) (void *) resbuf)[i] = SWAP(ctx->H[i]); - + unaligned_write_ne32(resbuf + i * sizeof(uint32_t), SWAP(ctx->H[i])); return resbuf; } @@ -167,18 +156,17 @@ void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx /* Process available complete blocks. */ if (len >= 64) { - if (UNALIGNED_P(buffer)) + if (IS_ALIGNED32(buffer)) { + sha256_process_block(buffer, len & ~63, ctx); + buffer = (const char *) buffer + (len & ~63); + len &= 63; + } else while (len > 64) { memcpy(ctx->buffer, buffer, 64); sha256_process_block(ctx->buffer, 64, ctx); buffer = (const char *) buffer + 64; len -= 64; } - else { - sha256_process_block(buffer, len & ~63, ctx); - buffer = (const char *) buffer + (len & ~63); - len &= 63; - } } /* Move remaining bytes into internal buffer. */ diff --git a/src/libnm-systemd-shared/src/fundamental/sha256.h b/src/libnm-systemd-shared/src/fundamental/sha256.h index 31790c2e..dbb08e35 100644 --- a/src/libnm-systemd-shared/src/fundamental/sha256.h +++ b/src/libnm-systemd-shared/src/fundamental/sha256.h @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "stdint.h" +#include <stddef.h> +#include <stdint.h> #define SHA256_DIGEST_SIZE 32 @@ -28,6 +29,11 @@ void sha256_init_ctx(struct sha256_ctx *ctx); uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_DIGEST_SIZE]); void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx); +static inline void sha256_process_bytes_and_size(const void *buffer, size_t len, struct sha256_ctx *ctx) { + sha256_process_bytes(&len, sizeof(len), ctx); + sha256_process_bytes(buffer, len, ctx); +} + uint8_t* sha256_direct(const void *buffer, size_t sz, uint8_t result[static SHA256_DIGEST_SIZE]); #define SHA256_DIRECT(buffer, sz) sha256_direct(buffer, sz, (uint8_t[SHA256_DIGEST_SIZE]) {}) 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 33f2d860..3a3e7f59 100644 --- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c +++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c @@ -2,7 +2,7 @@ #include "nm-sd-adapt-shared.h" -#ifndef SD_BOOT +#if !SD_BOOT # include <ctype.h> #endif @@ -22,7 +22,6 @@ sd_char *startswith(const sd_char *s, const sd_char *prefix) { return (sd_char*) s + l; } -#ifndef SD_BOOT sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) { size_t l; @@ -35,7 +34,6 @@ sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) { return (sd_char*) s + l; } -#endif sd_char* endswith(const sd_char *s, const sd_char *postfix) { size_t sl, pl; diff --git a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h index ecf32e51..9019542b 100644 --- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h @@ -1,9 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#ifdef SD_BOOT -# include <efi.h> -# include <efilib.h> +#if SD_BOOT +# include "efi.h" # include "efi-string.h" #else # include <string.h> @@ -11,7 +10,7 @@ #include "macro-fundamental.h" -#ifdef SD_BOOT +#if SD_BOOT # define strlen strlen16 # define strcmp strcmp16 # define strncmp strncmp16 @@ -59,9 +58,7 @@ static inline size_t strlen_ptr(const sd_char *s) { } sd_char *startswith(const sd_char *s, const sd_char *prefix) _pure_; -#ifndef SD_BOOT sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) _pure_; -#endif sd_char *endswith(const sd_char *s, const sd_char *postfix) _pure_; sd_char *endswith_no_case(const sd_char *s, const sd_char *postfix) _pure_; @@ -110,6 +107,10 @@ static inline bool ascii_isdigit(sd_char a) { return a >= '0' && a <= '9'; } +static inline bool ascii_ishex(sd_char a) { + return ascii_isdigit(a) || (a >= 'a' && a <= 'f') || (a >= 'A' && a <= 'F'); +} + static inline bool ascii_isalpha(sd_char a) { /* A pure ASCII, locale independent version of isalpha() */ return (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z'); diff --git a/src/libnm-systemd-shared/src/shared/dns-domain.c b/src/libnm-systemd-shared/src/shared/dns-domain.c index 7bba3b18..43f43197 100644 --- a/src/libnm-systemd-shared/src/shared/dns-domain.c +++ b/src/libnm-systemd-shared/src/shared/dns-domain.c @@ -302,7 +302,6 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) { int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { _cleanup_free_ uint32_t *input = NULL; size_t input_size, l; - const char *p; bool contains_8bit = false; char buffer[DNS_LABEL_MAX+1]; int r; @@ -319,7 +318,7 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded if (encoded_size <= 0) return -EINVAL; - for (p = encoded; p < encoded + encoded_size; p++) + for (const char *p = encoded; p < encoded + encoded_size; p++) if ((uint8_t) *p > 127) contains_8bit = true; @@ -534,7 +533,18 @@ int dns_name_compare_func(const char *a, const char *b) { } } -DEFINE_HASH_OPS(dns_name_hash_ops, char, dns_name_hash_func, dns_name_compare_func); +DEFINE_HASH_OPS( + dns_name_hash_ops, + char, + dns_name_hash_func, + dns_name_compare_func); + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + dns_name_hash_ops_free, + char, + dns_name_hash_func, + dns_name_compare_func, + free); int dns_name_equal(const char *x, const char *y) { int r, q; @@ -752,9 +762,8 @@ int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_ad return r; if (r > 0) { uint8_t a[4]; - unsigned i; - for (i = 0; i < ELEMENTSOF(a); i++) { + for (size_t i = 0; i < ELEMENTSOF(a); i++) { char label[DNS_LABEL_MAX+1]; r = dns_label_unescape(&p, label, sizeof label, 0); @@ -788,9 +797,8 @@ int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_ad return r; if (r > 0) { struct in6_addr a; - unsigned i; - for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) { + for (size_t i = 0; i < ELEMENTSOF(a.s6_addr); i++) { char label[DNS_LABEL_MAX+1]; int x, y; @@ -832,7 +840,6 @@ int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_ad #endif /* NM_IGNORED */ bool dns_name_is_root(const char *name) { - assert(name); /* There are exactly two ways to encode the root domain name: @@ -904,8 +911,6 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo #if 0 /* NM_IGNORED */ static bool srv_type_label_is_valid(const char *label, size_t n) { - size_t k; - assert(label); if (n < 2) /* Label needs to be at least 2 chars long */ @@ -919,12 +924,11 @@ static bool srv_type_label_is_valid(const char *label, size_t n) { return false; /* Third and further chars must be alphanumeric or a hyphen */ - for (k = 2; k < n; k++) { + for (size_t k = 2; k < n; k++) if (!ascii_isalpha(label[k]) && !ascii_isdigit(label[k]) && label[k] != '-') return false; - } return true; } @@ -1120,14 +1124,12 @@ finish: } static int dns_name_build_suffix_table(const char *name, const char *table[]) { - const char *p; + const char *p = ASSERT_PTR(name); unsigned n = 0; int r; - assert(name); assert(table); - p = name; for (;;) { if (n > DNS_N_LABELS_MAX) return -EINVAL; diff --git a/src/libnm-systemd-shared/src/shared/dns-domain.h b/src/libnm-systemd-shared/src/shared/dns-domain.h index 828e924d..a9449410 100644 --- a/src/libnm-systemd-shared/src/shared/dns-domain.h +++ b/src/libnm-systemd-shared/src/shared/dns-domain.h @@ -62,13 +62,10 @@ static inline int dns_name_is_valid_ldh(const char *s) { return 1; } -static inline bool dns_name_is_empty(const char *s) { - return isempty(s) || streq(s, "."); -} - void dns_name_hash_func(const char *s, struct siphash *state); int dns_name_compare_func(const char *a, const char *b); extern const struct hash_ops dns_name_hash_ops; +extern const struct hash_ops dns_name_hash_ops_free; int dns_name_between(const char *a, const char *b, const char *c); int dns_name_equal(const char *x, const char *y); |