diff options
author | Michael Biebl <biebl@debian.org> | 2018-01-28 22:49:17 +0100 |
---|---|---|
committer | Michael Biebl <biebl@debian.org> | 2018-01-28 22:49:17 +0100 |
commit | 1d42b86df9052528a8f56b2f52d8bc2faf87b2da (patch) | |
tree | 0d80f37a1ad6f02067261ee3e7ee62e1869fcd56 /src/basic | |
parent | 52ad194e0b816b8273dd8d0fea3e6d467f6ca34e (diff) | |
download | systemd-1d42b86df9052528a8f56b2f52d8bc2faf87b2da.tar.gz |
New upstream version 237
Diffstat (limited to 'src/basic')
79 files changed, 1987 insertions, 775 deletions
diff --git a/src/basic/af-list.h b/src/basic/af-list.h index 65656022cc..1684bc6a0f 100644 --- a/src/basic/af-list.h +++ b/src/basic/af-list.h @@ -20,6 +20,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sys/socket.h> + #include "string-util.h" const char *af_to_name(int id); diff --git a/src/basic/async.c b/src/basic/async.c index d368b92522..b6c6d6a80b 100644 --- a/src/basic/async.c +++ b/src/basic/async.c @@ -27,49 +27,82 @@ #include "fd-util.h" #include "log.h" #include "macro.h" +#include "process-util.h" +#include "signal-util.h" #include "util.h" int asynchronous_job(void* (*func)(void *p), void *arg) { + sigset_t ss, saved_ss; pthread_attr_t a; pthread_t t; - int r; + int r, k; - /* It kinda sucks that we have to resort to threads to - * implement an asynchronous sync(), but well, such is - * life. - * - * Note that issuing this command right before exiting a - * process will cause the process to wait for the sync() to - * complete. This function hence is nicely asynchronous really - * only in long running processes. */ + /* It kinda sucks that we have to resort to threads to implement an asynchronous close(), but well, such is + * life. */ r = pthread_attr_init(&a); if (r > 0) return -r; r = pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED); - if (r > 0) + if (r > 0) { + r = -r; + goto finish; + } + + if (sigfillset(&ss) < 0) { + r = -errno; + goto finish; + } + + /* Block all signals before forking off the thread, so that the new thread is started with all signals + * blocked. This way the existence of the new thread won't affect signal handling in other threads. */ + + r = pthread_sigmask(SIG_BLOCK, &ss, &saved_ss); + if (r > 0) { + r = -r; goto finish; + } r = pthread_create(&t, &a, func, arg); + k = pthread_sigmask(SIG_SETMASK, &saved_ss, NULL); + + if (r > 0) + r = -r; + else if (k > 0) + r = -k; + else + r = 0; + finish: pthread_attr_destroy(&a); - return -r; + return r; } -static void *sync_thread(void *p) { - sync(); - return NULL; -} +int asynchronous_sync(pid_t *ret_pid) { + int r; -int asynchronous_sync(void) { - log_debug("Spawning new thread for sync"); + /* This forks off an invocation of fork() as a child process, in order to initiate synchronization to + * disk. Note that we implement this as helper process rather than thread as we don't want the sync() to hang our + * original process ever, and a thread would do that as the process can't exit with threads hanging in blocking + * syscalls. */ + + r = safe_fork("(sd-sync)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS, ret_pid); + if (r < 0) + return r; + if (r == 0) { + /* Child process */ + (void) sync(); + _exit(EXIT_SUCCESS); + } - return asynchronous_job(sync_thread, NULL); + return 0; } static void *close_thread(void *p) { + (void) pthread_setname_np(pthread_self(), "close"); + assert_se(close_nointr(PTR_TO_FD(p)) != -EBADF); return NULL; } diff --git a/src/basic/async.h b/src/basic/async.h index 7eac54d8b2..01c975bb30 100644 --- a/src/basic/async.h +++ b/src/basic/async.h @@ -22,5 +22,5 @@ int asynchronous_job(void* (*func)(void *p), void *arg); -int asynchronous_sync(void); +int asynchronous_sync(pid_t *ret_pid); int asynchronous_close(int fd); diff --git a/src/basic/blockdev-util.c b/src/basic/blockdev-util.c new file mode 100644 index 0000000000..3a8f8d1c27 --- /dev/null +++ b/src/basic/blockdev-util.c @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/stat.h> +#include <sys/statfs.h> + +#include "alloc-util.h" +#include "blockdev-util.h" +#include "btrfs-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "missing.h" +#include "stat-util.h" + +int block_get_whole_disk(dev_t d, dev_t *ret) { + char p[SYS_BLOCK_PATH_MAX("/partition")]; + _cleanup_free_ char *s = NULL; + unsigned n, m; + int r; + + assert(ret); + + /* If it has a queue this is good enough for us */ + xsprintf_sys_block_path(p, "/queue", d); + if (access(p, F_OK) >= 0) { + *ret = d; + return 0; + } + + /* If it is a partition find the originating device */ + xsprintf_sys_block_path(p, "/partition", d); + if (access(p, F_OK) < 0) + return -ENOENT; + + /* Get parent dev_t */ + xsprintf_sys_block_path(p, "/../dev", d); + r = read_one_line_file(p, &s); + if (r < 0) + return r; + + r = sscanf(s, "%u:%u", &m, &n); + if (r != 2) + return -EINVAL; + + /* Only return this if it is really good enough for us. */ + xsprintf_sys_block_path(p, "/queue", makedev(m, n)); + if (access(p, F_OK) < 0) + return -ENOENT; + + *ret = makedev(m, n); + return 0; +} + +int get_block_device(const char *path, dev_t *dev) { + struct stat st; + struct statfs sfs; + + assert(path); + assert(dev); + + /* Get's the block device directly backing a file system. If + * the block device is encrypted, returns the device mapper + * block device. */ + + if (lstat(path, &st)) + return -errno; + + if (major(st.st_dev) != 0) { + *dev = st.st_dev; + return 1; + } + + if (statfs(path, &sfs) < 0) + return -errno; + + if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) + return btrfs_get_block_device(path, dev); + + return 0; +} + +int get_block_device_harder(const char *path, dev_t *dev) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *t = NULL; + char p[SYS_BLOCK_PATH_MAX("/slaves")]; + struct dirent *de, *found = NULL; + const char *q; + unsigned maj, min; + dev_t dt; + int r; + + assert(path); + assert(dev); + + /* Gets the backing block device for a file system, and + * handles LUKS encrypted file systems, looking for its + * immediate parent, if there is one. */ + + r = get_block_device(path, &dt); + if (r <= 0) + return r; + + xsprintf_sys_block_path(p, "/slaves", dt); + d = opendir(p); + if (!d) { + if (errno == ENOENT) + goto fallback; + + return -errno; + } + + FOREACH_DIRENT_ALL(de, d, return -errno) { + + if (dot_or_dot_dot(de->d_name)) + continue; + + if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN)) + continue; + + if (found) { + _cleanup_free_ char *u = NULL, *v = NULL, *a = NULL, *b = NULL; + + /* We found a device backed by multiple other devices. We don't really support automatic + * discovery on such setups, with the exception of dm-verity partitions. In this case there are + * two backing devices: the data partition and the hash partition. We are fine with such + * setups, however, only if both partitions are on the same physical device. Hence, let's + * verify this. */ + + u = strjoin(p, "/", de->d_name, "/../dev"); + if (!u) + return -ENOMEM; + + v = strjoin(p, "/", found->d_name, "/../dev"); + if (!v) + return -ENOMEM; + + r = read_one_line_file(u, &a); + if (r < 0) { + log_debug_errno(r, "Failed to read %s: %m", u); + goto fallback; + } + + r = read_one_line_file(v, &b); + if (r < 0) { + log_debug_errno(r, "Failed to read %s: %m", v); + goto fallback; + } + + /* Check if the parent device is the same. If not, then the two backing devices are on + * different physical devices, and we don't support that. */ + if (!streq(a, b)) + goto fallback; + } + + found = de; + } + + if (!found) + goto fallback; + + q = strjoina(p, "/", found->d_name, "/dev"); + + r = read_one_line_file(q, &t); + if (r == -ENOENT) + goto fallback; + if (r < 0) + return r; + + if (sscanf(t, "%u:%u", &maj, &min) != 2) + return -EINVAL; + + if (maj == 0) + goto fallback; + + *dev = makedev(maj, min); + return 1; + +fallback: + *dev = dt; + return 1; +} diff --git a/src/basic/blockdev-util.h b/src/basic/blockdev-util.h new file mode 100644 index 0000000000..642b7ce522 --- /dev/null +++ b/src/basic/blockdev-util.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include "macro.h" +#include "stdio-util.h" +#include "string-util.h" + +#define SYS_BLOCK_PATH_MAX(suffix) \ + (STRLEN("/sys/dev/block/") + DECIMAL_STR_MAX(dev_t) + 1 + DECIMAL_STR_MAX(dev_t) + strlen_ptr(suffix)) +#define xsprintf_sys_block_path(buf, suffix, devno) \ + xsprintf(buf, "/sys/dev/block/%u:%u%s", major(devno), minor(devno), strempty(suffix)) + +int block_get_whole_disk(dev_t d, dev_t *ret); + +int get_block_device(const char *path, dev_t *dev); + +int get_block_device_harder(const char *path, dev_t *dev); diff --git a/src/basic/btrfs-ctree.h b/src/basic/btrfs-ctree.h index 66bdf9736e..c5a4244e37 100644 --- a/src/basic/btrfs-ctree.h +++ b/src/basic/btrfs-ctree.h @@ -1,6 +1,7 @@ #pragma once #include "macro.h" +#include "missing.h" #include "sparse-endian.h" /* Stolen from btrfs' ctree.h */ diff --git a/src/basic/btrfs-util.c b/src/basic/btrfs-util.c index ac96e63531..19d385ab7c 100644 --- a/src/basic/btrfs-util.c +++ b/src/basic/btrfs-util.c @@ -38,6 +38,7 @@ #endif #include "alloc-util.h" +#include "blockdev-util.h" #include "btrfs-ctree.h" #include "btrfs-util.h" #include "chattr-util.h" @@ -50,7 +51,6 @@ #include "missing.h" #include "path-util.h" #include "rm-rf.h" -#include "selinux-util.h" #include "smack-util.h" #include "sparse-endian.h" #include "stat-util.h" @@ -174,24 +174,6 @@ int btrfs_subvol_make(const char *path) { return 0; } -int btrfs_subvol_make_label(const char *path) { - int r; - - assert(path); - - r = mac_selinux_create_file_prepare(path, S_IFDIR); - if (r < 0) - return r; - - r = btrfs_subvol_make(path); - mac_selinux_create_file_clear(); - - if (r < 0) - return r; - - return mac_smack_fix(path, false, false); -} - int btrfs_subvol_set_read_only_fd(int fd, bool b) { uint64_t flags, nflags; struct stat st; diff --git a/src/basic/btrfs-util.h b/src/basic/btrfs-util.h index 2c78daa37b..952b3c26da 100644 --- a/src/basic/btrfs-util.h +++ b/src/basic/btrfs-util.h @@ -84,7 +84,6 @@ int btrfs_resize_loopback_fd(int fd, uint64_t size, bool grow_only); int btrfs_resize_loopback(const char *path, uint64_t size, bool grow_only); int btrfs_subvol_make(const char *path); -int btrfs_subvol_make_label(const char *path); int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags); int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags); diff --git a/src/basic/build.h b/src/basic/build.h index ab2a838ce9..0d078efe6f 100644 --- a/src/basic/build.h +++ b/src/basic/build.h @@ -140,6 +140,12 @@ #define _IDN_FEATURE_ "-IDN" #endif +#if HAVE_PCRE2 +#define _PCRE2_FEATURE_ "+PCRE2" +#else +#define _PCRE2_FEATURE_ "-PCRE2" +#endif + #define _CGROUP_HIEARCHY_ "default-hierarchy=" DEFAULT_HIERARCHY_NAME #define SYSTEMD_FEATURES \ @@ -163,4 +169,5 @@ _KMOD_FEATURE_ " " \ _IDN2_FEATURE_ " " \ _IDN_FEATURE_ " " \ + _PCRE2_FEATURE_ " " \ _CGROUP_HIEARCHY_ diff --git a/src/basic/calendarspec.c b/src/basic/calendarspec.c index e6add0c383..fd78022773 100644 --- a/src/basic/calendarspec.c +++ b/src/basic/calendarspec.c @@ -35,6 +35,7 @@ #include "fileio.h" #include "macro.h" #include "parse-util.h" +#include "process-util.h" #include "string-util.h" #include "time-util.h" @@ -156,6 +157,11 @@ static void fix_year(CalendarComponent *c) { int calendar_spec_normalize(CalendarSpec *c) { assert(c); + if (streq_ptr(c->timezone, "UTC")) { + c->utc = true; + c->timezone = mfree(c->timezone); + } + if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS) c->weekdays_bits = -1; @@ -1348,9 +1354,7 @@ typedef struct SpecNextResult { } SpecNextResult; int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) { - pid_t pid; - SpecNextResult *shared; - SpecNextResult tmp; + SpecNextResult *shared, tmp; int r; if (isempty(spec->timezone)) @@ -1360,15 +1364,12 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) if (shared == MAP_FAILED) return negative_errno(); - pid = fork(); - - if (pid == -1) { - int fork_errno = errno; + r = safe_fork("(sd-calendar)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL); + if (r < 0) { (void) munmap(shared, sizeof *shared); - return -fork_errno; + return r; } - - if (pid == 0) { + if (r == 0) { if (setenv("TZ", spec->timezone, 1) != 0) { shared->return_value = negative_errno(); _exit(EXIT_FAILURE); @@ -1381,14 +1382,8 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) _exit(EXIT_SUCCESS); } - r = wait_for_terminate(pid, NULL); - if (r < 0) { - (void) munmap(shared, sizeof *shared); - return r; - } - tmp = *shared; - if (munmap(shared, sizeof *shared) != 0) + if (munmap(shared, sizeof *shared) < 0) return negative_errno(); if (tmp.return_value == 0) diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index f599d0f0f1..7c0ba92110 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -779,13 +779,11 @@ int cg_create(const char *controller, const char *path) { if (r < 0) return r; - if (mkdir(fs, 0755) < 0) { - - if (errno == EEXIST) - return 0; - - return -errno; - } + r = mkdir_errno_wrapper(fs, 0755); + if (r == -EEXIST) + return 0; + if (r < 0) + return r; r = cg_hybrid_unified(); if (r < 0) diff --git a/src/basic/def.h b/src/basic/def.h index 77ab735aed..43e7e17008 100644 --- a/src/basic/def.h +++ b/src/basic/def.h @@ -53,9 +53,10 @@ "/usr/lib/kbd/keymaps/\0" #endif -#define UNIX_SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" -#define DEFAULT_SYSTEM_BUS_ADDRESS UNIX_SYSTEM_BUS_ADDRESS -#define UNIX_USER_BUS_ADDRESS_FMT "unix:path=%s/bus" +/* Note that we use the new /run prefix here (instead of /var/run) since we require them to be aliases and that way we + * become independent of /var being mounted */ +#define DEFAULT_SYSTEM_BUS_ADDRESS "unix:path=/run/dbus/system_bus_socket" +#define DEFAULT_USER_BUS_ADDRESS_FMT "unix:path=%s/bus" #define PLYMOUTH_SOCKET { \ .un.sun_family = AF_UNIX, \ diff --git a/src/basic/device-nodes.h b/src/basic/device-nodes.h index 7dd8a772a5..49f4ccc729 100644 --- a/src/basic/device-nodes.h +++ b/src/basic/device-nodes.h @@ -29,11 +29,6 @@ int encode_devnode_name(const char *str, char *str_enc, size_t len); int whitelisted_char_for_devnode(char c, const char *additional); -#define SYS_BLOCK_PATH_MAX(suffix) \ - (STRLEN("/sys/dev/block/") + DECIMAL_STR_MAX(dev_t) + 1 + DECIMAL_STR_MAX(dev_t) + strlen_ptr(suffix)) -#define xsprintf_sys_block_path(buf, suffix, devno) \ - xsprintf(buf, "/sys/dev/block/%u:%u%s", major(devno), minor(devno), strempty(suffix)) - #define DEV_NUM_PATH_MAX \ (STRLEN("/dev/block/") + DECIMAL_STR_MAX(dev_t) + 1 + DECIMAL_STR_MAX(dev_t)) #define xsprintf_dev_num_path(buf, type, devno) \ diff --git a/src/basic/errno-list.c b/src/basic/errno-list.c index d8eedfc0ad..7eb28b8fd1 100644 --- a/src/basic/errno-list.c +++ b/src/basic/errno-list.c @@ -18,6 +18,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <errno.h> #include <string.h> #include "errno-list.h" diff --git a/src/basic/errno-list.h b/src/basic/errno-list.h index 4e9b75a7ea..38beaf96dd 100644 --- a/src/basic/errno-list.h +++ b/src/basic/errno-list.h @@ -20,6 +20,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <stdbool.h> /* * MAX_ERRNO is defined as 4095 in linux/err.h * We use the same value here. @@ -28,3 +29,6 @@ const char *errno_to_name(int id); int errno_from_name(const char *name); +static inline bool errno_is_valid(int n) { + return n > 0 && n <= ERRNO_MAX; +} diff --git a/src/basic/ether-addr-util.c b/src/basic/ether-addr-util.c index bbe8bf0006..bfbd1a4931 100644 --- a/src/basic/ether-addr-util.c +++ b/src/basic/ether-addr-util.c @@ -18,6 +18,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <errno.h> #include <net/ethernet.h> #include <stdio.h> #include <sys/types.h> diff --git a/src/basic/exec-util.c b/src/basic/exec-util.c index 82ccbdf5ec..0829b3d836 100644 --- a/src/basic/exec-util.c +++ b/src/basic/exec-util.c @@ -48,27 +48,26 @@ assert_cc(EAGAIN == EWOULDBLOCK); static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) { pid_t _pid; + int r; if (null_or_empty_path(path)) { log_debug("%s is empty (a mask).", path); return 0; } - _pid = fork(); - if (_pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - if (_pid == 0) { + r = safe_fork("(direxec)", FORK_DEATHSIG|FORK_LOG, &_pid); + if (r < 0) + return r; + if (r == 0) { char *_argv[2]; - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - if (stdout_fd >= 0) { /* If the fd happens to be in the right place, go along with that */ if (stdout_fd != STDOUT_FILENO && dup2(stdout_fd, STDOUT_FILENO) < 0) return -errno; - fd_cloexec(STDOUT_FILENO, false); + (void) fd_cloexec(STDOUT_FILENO, false); } if (!argv) { @@ -83,7 +82,6 @@ static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) { _exit(EXIT_FAILURE); } - log_debug("Spawned %s as " PID_FMT ".", path, _pid); *pid = _pid; return 1; } @@ -107,11 +105,6 @@ static int do_execute( * If callbacks is nonnull, execution is serial. Otherwise, we default to parallel. */ - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE, (const char* const*) directories); if (r < 0) return r; @@ -153,7 +146,7 @@ static int do_execute( return log_oom(); t = NULL; } else { - r = wait_for_terminate_and_warn(t, pid, true); + r = wait_for_terminate_and_check(t, pid, WAIT_LOG); if (r < 0) continue; @@ -183,7 +176,7 @@ static int do_execute( t = hashmap_remove(pids, PID_TO_PTR(pid)); assert(t); - wait_for_terminate_and_warn(t, pid, true); + (void) wait_for_terminate_and_check(t, pid, WAIT_LOG); } return 0; @@ -196,10 +189,9 @@ int execute_directories( void* const callback_args[_STDOUT_CONSUME_MAX], char *argv[]) { - pid_t executor_pid; - char *name; char **dirs = (char**) directories; _cleanup_close_ int fd = -1; + char *name; int r; assert(!strv_isempty(dirs)); @@ -222,24 +214,14 @@ int execute_directories( * them to finish. Optionally a timeout is applied. If a file with the same name * exists in more than one directory, the earliest one wins. */ - executor_pid = fork(); - if (executor_pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - - if (executor_pid == 0) { + r = safe_fork("(sd-executor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + if (r < 0) + return r; + if (r == 0) { r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv); _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); } - r = wait_for_terminate_and_warn(name, executor_pid, true); - if (r < 0) - return log_error_errno(r, "Execution failed: %m"); - if (r > 0) { - /* non-zero return code from child */ - log_error("Forker process failed."); - return -EREMOTEIO; - } - if (!callbacks) return 0; diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index a4a5c6b3f6..404361e8c1 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -192,9 +192,9 @@ int fd_cloexec(int fd, bool cloexec) { } void stdio_unset_cloexec(void) { - fd_cloexec(STDIN_FILENO, false); - fd_cloexec(STDOUT_FILENO, false); - fd_cloexec(STDERR_FILENO, false); + (void) fd_cloexec(STDIN_FILENO, false); + (void) fd_cloexec(STDOUT_FILENO, false); + (void) fd_cloexec(STDERR_FILENO, false); } _pure_ static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { @@ -227,20 +227,21 @@ int close_all_fds(const int except[], unsigned n_except) { assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0); for (fd = 3; fd < (int) rl.rlim_max; fd ++) { + int q; if (fd_in_set(fd, except, n_except)) continue; - if (close_nointr(fd) < 0) - if (errno != EBADF && r == 0) - r = -errno; + q = close_nointr(fd); + if (q < 0 && q != -EBADF && r >= 0) + r = q; } return r; } FOREACH_DIRENT(de, d, return -errno) { - int fd = -1; + int fd = -1, q; if (safe_atoi(de->d_name, &fd) < 0) /* Let's better ignore this, just in case */ @@ -255,11 +256,9 @@ int close_all_fds(const int except[], unsigned n_except) { if (fd_in_set(fd, except, n_except)) continue; - if (close_nointr(fd) < 0) { - /* Valgrind has its own FD and doesn't want to have it closed */ - if (errno != EBADF && r == 0) - r = -errno; - } + q = close_nointr(fd); + if (q < 0 && q != -EBADF && r >= 0) /* Valgrind has its own FD and doesn't want to have it closed */ + r = q; } return r; diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 4e02d5b344..26d6174664 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -62,12 +62,30 @@ int write_string_stream_ts( WriteStringFileFlags flags, struct timespec *ts) { + bool needs_nl; + assert(f); assert(line); - fputs(line, f); - if (!(flags & WRITE_STRING_FILE_AVOID_NEWLINE) && !endswith(line, "\n")) - fputc('\n', f); + if (ferror(f)) + return -EIO; + + needs_nl = !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) && !endswith(line, "\n"); + + if (needs_nl && (flags & WRITE_STRING_FILE_DISABLE_BUFFER)) { + /* If STDIO buffering was disabled, then let's append the newline character to the string itself, so + * that the write goes out in one go, instead of two */ + + line = strjoina(line, "\n"); + needs_nl = false; + } + + if (fputs(line, f) == EOF) + return -errno; + + if (needs_nl) + if (fputc('\n', f) == EOF) + return -errno; if (ts) { struct timespec twice[2] = {*ts, *ts}; @@ -908,14 +926,16 @@ int write_env_file(const char *fname, char **l) { } int executable_is_script(const char *path, char **interpreter) { - int r; _cleanup_free_ char *line = NULL; - int len; + size_t len; char *ans; + int r; assert(path); r = read_one_line_file(path, &line); + if (r == -ENOBUFS) /* First line overly long? if so, then it's not a script */ + return 0; if (r < 0) return r; @@ -1205,8 +1225,7 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { if (!filename_is_valid(fn)) return -EINVAL; - if (!extra) - extra = ""; + extra = strempty(extra); t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1); if (!t) @@ -1239,8 +1258,7 @@ int tempfn_random(const char *p, const char *extra, char **ret) { if (!filename_is_valid(fn)) return -EINVAL; - if (!extra) - extra = ""; + extra = strempty(extra); t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1); if (!t) @@ -1280,8 +1298,7 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) { return r; } - if (!extra) - extra = ""; + extra = strempty(extra); t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1); if (!t) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 4ca36faf09..a8e50d4c78 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -39,6 +39,7 @@ #include "mkdir.h" #include "parse-util.h" #include "path-util.h" +#include "process-util.h" #include "stat-util.h" #include "stdio-util.h" #include "string-util.h" @@ -315,43 +316,60 @@ 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; - int r; + char fdpath[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; + _cleanup_close_ int fd = -1; + int r, ret = 0; assert(path); - if (parents) - mkdir_parents(path, 0755); - - fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, - IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode); - if (fd < 0) - return -errno; + /* Note that touch_file() does not follow symlinks: if invoked on an existing symlink, then it is the symlink + * itself which is updated, not its target + * + * Returns the first error we encounter, but tries to apply as much as possible. */ - if (mode != MODE_INVALID) { - r = fchmod(fd, mode); - if (r < 0) + if (parents) + (void) mkdir_parents(path, 0755); + + /* Initially, we try to open the node with O_PATH, so that we get a reference to the node. This is useful in + * case the path refers to an existing device or socket node, as we can open it successfully in all cases, and + * won't trigger any driver magic or so. */ + fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) { + if (errno != ENOENT) return -errno; - } - if (uid != UID_INVALID || gid != GID_INVALID) { - r = fchown(fd, uid, gid); - if (r < 0) + /* if the node doesn't exist yet, we create it, but with O_EXCL, so that we only create a regular file + * here, and nothing else */ + fd = open(path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode); + if (fd < 0) return -errno; } + /* Let's make a path from the fd, and operate on that. With this logic, we can adjust the access mode, + * ownership and time of the file node in all cases, even if the fd refers to an O_PATH object — which is + * something fchown(), fchmod(), futimensat() don't allow. */ + xsprintf(fdpath, "/proc/self/fd/%i", fd); + + if (mode != MODE_INVALID) + if (chmod(fdpath, mode) < 0) + ret = -errno; + + if (uid_is_valid(uid) || gid_is_valid(gid)) + if (chown(fdpath, uid, gid) < 0 && ret >= 0) + ret = -errno; + if (stamp != USEC_INFINITY) { struct timespec ts[2]; timespec_store(&ts[0], stamp); ts[1] = ts[0]; - r = futimens(fd, ts); + r = utimensat(AT_FDCWD, fdpath, ts, 0); } else - r = futimens(fd, NULL); - if (r < 0) + r = utimensat(AT_FDCWD, fdpath, NULL, 0); + if (r < 0 && ret >= 0) return -errno; - return 0; + return ret; } int touch(const char *path) { @@ -593,16 +611,35 @@ int inotify_add_watch_fd(int fd, int what, uint32_t mask) { return r; } +static bool safe_transition(const struct stat *a, const struct stat *b) { + /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to + * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files + * making us believe we read something safe even though it isn't safe in the specific context we open it in. */ + + if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */ + return true; + + return a->st_uid == b->st_uid; /* Otherwise we need to stay within the same UID */ +} + int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) { _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL; _cleanup_close_ int fd = -1; unsigned max_follow = 32; /* how many symlinks to follow before giving up and returning ELOOP */ + struct stat previous_stat; bool exists = true; char *todo; int r; assert(path); + /* Either the file may be missing, or we return an fd to the final object, but both make no sense */ + if ((flags & (CHASE_NONEXISTENT|CHASE_OPEN)) == (CHASE_NONEXISTENT|CHASE_OPEN)) + return -EINVAL; + + if (isempty(path)) + return -EINVAL; + /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following * symlinks relative to a root directory, instead of the root of the host. * @@ -623,13 +660,23 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, * function what to do when encountering a symlink with an absolute path as directory: prefix it by the * specified path. */ + /* A root directory of "/" or "" is identical to none */ + if (isempty(original_root) || path_equal(original_root, "/")) + original_root = NULL; + if (original_root) { r = path_make_absolute_cwd(original_root, &root); if (r < 0) return r; - if (flags & CHASE_PREFIX_ROOT) + if (flags & CHASE_PREFIX_ROOT) { + + /* We don't support relative paths in combination with a root directory */ + if (!path_is_absolute(path)) + return -EINVAL; + path = prefix_roota(root, path); + } } r = path_make_absolute_cwd(path, &buffer); @@ -640,6 +687,11 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, if (fd < 0) return -errno; + if (flags & CHASE_SAFE) { + if (fstat(fd, &previous_stat) < 0) + return -errno; + } + todo = buffer; for (;;) { _cleanup_free_ char *first = NULL; @@ -678,7 +730,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, /* Two dots? Then chop off the last bit of what we already found out. */ if (path_equal(first, "/..")) { _cleanup_free_ char *parent = NULL; - int fd_parent = -1; + _cleanup_close_ int fd_parent = -1; /* If we already are at the top, then going up will not change anything. This is in-line with * how the kernel handles this. */ @@ -701,8 +753,19 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, if (fd_parent < 0) return -errno; + if (flags & CHASE_SAFE) { + if (fstat(fd_parent, &st) < 0) + return -errno; + + if (!safe_transition(&previous_stat, &st)) + return -EPERM; + + previous_stat = st; + } + safe_close(fd); fd = fd_parent; + fd_parent = -1; continue; } @@ -735,6 +798,12 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, if (fstat(child, &st) < 0) return -errno; + if ((flags & CHASE_SAFE) && + !safe_transition(&previous_stat, &st)) + return -EPERM; + + previous_stat = st; + if ((flags & CHASE_NO_AUTOFS) && fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0) return -EREMOTE; @@ -765,6 +834,16 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, if (fd < 0) return -errno; + if (flags & CHASE_SAFE) { + if (fstat(fd, &st) < 0) + return -errno; + + if (!safe_transition(&previous_stat, &st)) + return -EPERM; + + previous_stat = st; + } + free(done); /* Note that we do not revalidate the root, we take it as is. */ @@ -821,6 +900,19 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, done = NULL; } + if (flags & CHASE_OPEN) { + int q; + + /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a proper fd by + * opening /proc/self/fd/xyz. */ + + assert(fd >= 0); + q = fd; + fd = -1; + + return q; + } + return exists; } diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index a7ba61625d..4dba1ea56a 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -29,6 +29,7 @@ #include <unistd.h> #include "time-util.h" +#include "util.h" int unlink_noerrno(const char *path); @@ -80,22 +81,25 @@ union inotify_event_buffer { int inotify_add_watch_fd(int fd, int what, uint32_t mask); enum { - CHASE_PREFIX_ROOT = 1, /* If set, the specified path will be prefixed by the specified root before beginning the iteration */ - CHASE_NONEXISTENT = 2, /* If set, it's OK if the path doesn't actually exist. */ - CHASE_NO_AUTOFS = 4, /* If set, return -EREMOTE if autofs mount point found */ + CHASE_PREFIX_ROOT = 1U << 0, /* If set, the specified path will be prefixed by the specified root before beginning the iteration */ + CHASE_NONEXISTENT = 1U << 1, /* If set, it's OK if the path doesn't actually exist. */ + CHASE_NO_AUTOFS = 1U << 2, /* If set, return -EREMOTE if autofs mount point found */ + CHASE_SAFE = 1U << 3, /* If set, return EPERM if we ever traverse from unprivileged to privileged files or directories */ + CHASE_OPEN = 1U << 4, /* If set, return an O_PATH object to the final component */ }; int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret); /* Useful for usage with _cleanup_(), removes a directory and frees the pointer */ static inline void rmdir_and_free(char *p) { + PROTECT_ERRNO; (void) rmdir(p); free(p); } DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rmdir_and_free); static inline void unlink_and_free(char *p) { - (void) unlink(p); + (void) unlink_noerrno(p); free(p); } DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free); diff --git a/src/basic/gcrypt-util.c b/src/basic/gcrypt-util.c new file mode 100644 index 0000000000..1bfb776725 --- /dev/null +++ b/src/basic/gcrypt-util.c @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#if HAVE_GCRYPT +#include <gcrypt.h> + +#include "gcrypt-util.h" +#include "hexdecoct.h" + +void initialize_libgcrypt(bool secmem) { + const char *p; + if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) + return; + + p = gcry_check_version("1.4.5"); + assert(p); + + /* Turn off "secmem". Clients which wish to make use of this + * feature should initialize the library manually */ + if (!secmem) + gcry_control(GCRYCTL_DISABLE_SECMEM); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); +} + +int string_hashsum(const char *s, size_t len, int md_algorithm, char **out) { + gcry_md_hd_t md = NULL; + size_t hash_size; + void *hash; + char *enc; + + initialize_libgcrypt(false); + + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); + + gcry_md_open(&md, md_algorithm, 0); + if (!md) + return -EIO; + + gcry_md_write(md, s, len); + + hash = gcry_md_read(md, 0); + if (!hash) + return -EIO; + + enc = hexmem(hash, hash_size); + if (!enc) + return -ENOMEM; + + *out = enc; + return 0; +} +#endif diff --git a/src/basic/gcrypt-util.h b/src/basic/gcrypt-util.h new file mode 100644 index 0000000000..69faf08e56 --- /dev/null +++ b/src/basic/gcrypt-util.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2016 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> + +#if HAVE_GCRYPT +#include <gcrypt.h> + +void initialize_libgcrypt(bool secmem); +int string_hashsum(const char *s, size_t len, int md_algorithm, char **out); +#endif + +static inline int string_hashsum_sha224(const char *s, size_t len, char **out) { +#if HAVE_GCRYPT + return string_hashsum(s, len, GCRY_MD_SHA224, out); +#else + return -EOPNOTSUPP; +#endif +} + +static inline int string_hashsum_sha256(const char *s, size_t len, char **out) { +#if HAVE_GCRYPT + return string_hashsum(s, len, GCRY_MD_SHA256, out); +#else + return -EOPNOTSUPP; +#endif +} diff --git a/src/basic/generate-af-list.sh b/src/basic/generate-af-list.sh index 8d9cdd1836..fa74198e58 100755 --- a/src/basic/generate-af-list.sh +++ b/src/basic/generate-af-list.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/sh +set -eu $1 -E -dM -include sys/socket.h - </dev/null | \ grep -Ev 'AF_UNSPEC|AF_MAX' | \ diff --git a/src/basic/generate-arphrd-list.sh b/src/basic/generate-arphrd-list.sh index ee207fb38e..e4e7271c4d 100755 --- a/src/basic/generate-arphrd-list.sh +++ b/src/basic/generate-arphrd-list.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/sh +set -eu $1 -dM -include net/if_arp.h - </dev/null | \ awk '/^#define[ \t]+ARPHRD_[^ \t]+[ \t]+[^ \t]/ { print $2; }' | \ diff --git a/src/basic/generate-cap-list.sh b/src/basic/generate-cap-list.sh index 1d4a562e7c..0628d2425f 100755 --- a/src/basic/generate-cap-list.sh +++ b/src/basic/generate-cap-list.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/sh +set -eu $1 -dM -include linux/capability.h -include "$2" -include "$3" - </dev/null | \ awk '/^#define[ \t]+CAP_[A-Z_]+[ \t]+/ { print $2; }' | \ diff --git a/src/basic/generate-errno-list.sh b/src/basic/generate-errno-list.sh index e2bab8b320..953d5e37b8 100755 --- a/src/basic/generate-errno-list.sh +++ b/src/basic/generate-errno-list.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/sh +set -eu $1 -dM -include errno.h - </dev/null | \ awk '/^#define[ \t]+E[^ _]+[ \t]+/ { print $2; }' diff --git a/src/basic/generate-socket-protocol-list.sh b/src/basic/generate-socket-protocol-list.sh new file mode 100755 index 0000000000..a9b1e0fb57 --- /dev/null +++ b/src/basic/generate-socket-protocol-list.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu + +$1 -dM -include netinet/in.h - </dev/null | \ + awk '/^#define[ \t]+IPPROTO_[^ \t]+[ \t]+[^ \t]/ { print $2; }' | \ + sed -e 's/IPPROTO_//' diff --git a/src/basic/gunicode.c b/src/basic/gunicode.c index e6ac0545a4..8aff4a0fc5 100644 --- a/src/basic/gunicode.c +++ b/src/basic/gunicode.c @@ -4,8 +4,6 @@ * Copyright 2000, 2005 Red Hat, Inc. */ -#include <stdlib.h> - #include "gunicode.h" #define unichar uint32_t diff --git a/src/basic/hash-funcs.c b/src/basic/hash-funcs.c index e69f81558d..5267758769 100644 --- a/src/basic/hash-funcs.c +++ b/src/basic/hash-funcs.c @@ -19,6 +19,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <string.h> + #include "hash-funcs.h" void string_hash_func(const void *p, struct siphash *state) { diff --git a/src/basic/hostname-util.h b/src/basic/hostname-util.h index d837d6f28c..edae52e3db 100644 --- a/src/basic/hostname-util.h +++ b/src/basic/hostname-util.h @@ -21,6 +21,7 @@ ***/ #include <stdbool.h> +#include <stdio.h> #include "macro.h" diff --git a/src/basic/io-util.c b/src/basic/io-util.c index 77c9bdc739..08ad42ed95 100644 --- a/src/basic/io-util.c +++ b/src/basic/io-util.c @@ -33,6 +33,7 @@ int flush_fd(int fd) { .fd = fd, .events = POLLIN, }; + int count = 0; /* Read from the specified file descriptor, until POLLIN is not set anymore, throwing away everything * read. Note that some file descriptors (notable IP sockets) will trigger POLLIN even when no data can be read @@ -52,7 +53,7 @@ int flush_fd(int fd) { return -errno; } else if (r == 0) - return 0; + return count; l = read(fd, buf, sizeof(buf)); if (l < 0) { @@ -61,11 +62,13 @@ int flush_fd(int fd) { continue; if (errno == EAGAIN) - return 0; + return count; return -errno; } else if (l == 0) - return 0; + return count; + + count += (int) l; } } diff --git a/src/basic/journal-importer.c b/src/basic/journal-importer.c index 6942c370cb..11054589e3 100644 --- a/src/basic/journal-importer.c +++ b/src/basic/journal-importer.c @@ -18,6 +18,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <errno.h> #include <unistd.h> #include "alloc-util.h" diff --git a/src/basic/label.c b/src/basic/label.c index ce81d286ab..18c9a23fea 100644 --- a/src/basic/label.c +++ b/src/basic/label.c @@ -22,6 +22,7 @@ #include <sys/stat.h> #include <unistd.h> +#include "btrfs-util.h" #include "label.h" #include "macro.h" #include "selinux-util.h" @@ -41,16 +42,17 @@ int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { return 0; } -int mkdir_label(const char *path, mode_t mode) { +int symlink_label(const char *old_path, const char *new_path) { int r; - assert(path); + assert(old_path); + assert(new_path); - r = mac_selinux_create_file_prepare(path, S_IFDIR); + r = mac_selinux_create_file_prepare(new_path, S_IFLNK); if (r < 0) return r; - if (mkdir(path, mode) < 0) + if (symlink(old_path, new_path) < 0) r = -errno; mac_selinux_create_file_clear(); @@ -58,26 +60,23 @@ int mkdir_label(const char *path, mode_t mode) { if (r < 0) return r; - return mac_smack_fix(path, false, false); + return mac_smack_fix(new_path, false, false); } -int symlink_label(const char *old_path, const char *new_path) { +int btrfs_subvol_make_label(const char *path) { int r; - assert(old_path); - assert(new_path); + assert(path); - r = mac_selinux_create_file_prepare(new_path, S_IFLNK); + r = mac_selinux_create_file_prepare(path, S_IFDIR); if (r < 0) return r; - if (symlink(old_path, new_path) < 0) - r = -errno; - + r = btrfs_subvol_make(path); mac_selinux_create_file_clear(); if (r < 0) return r; - return mac_smack_fix(new_path, false, false); + return mac_smack_fix(path, false, false); } diff --git a/src/basic/label.h b/src/basic/label.h index 86447c2e98..d73dacec4f 100644 --- a/src/basic/label.h +++ b/src/basic/label.h @@ -27,3 +27,5 @@ int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs); int mkdir_label(const char *path, mode_t mode); int symlink_label(const char *old_path, const char *new_path); + +int btrfs_subvol_make_label(const char *path); diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h index 60ce017a15..f75dcbc3d1 100644 --- a/src/basic/locale-util.h +++ b/src/basic/locale-util.h @@ -22,6 +22,7 @@ #include <libintl.h> #include <stdbool.h> +#include <locale.h> #include "macro.h" @@ -75,3 +76,10 @@ LocaleVariable locale_variable_from_string(const char *s) _pure_; int get_keymaps(char ***l); bool keymap_is_valid(const char *name); + +static inline void freelocalep(locale_t *p) { + if (*p == (locale_t) 0) + return; + + freelocale(*p); +} diff --git a/src/basic/log.c b/src/basic/log.c index 20d9588e2f..12ee65a5c3 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -54,6 +54,7 @@ #include "syslog-util.h" #include "terminal-util.h" #include "time-util.h" +#include "utf8.h" #include "util.h" #define SNDBUF_SIZE (8*1024*1024) @@ -76,40 +77,40 @@ static bool show_location = false; static bool upgrade_syslog_to_journal = false; static bool always_reopen_console = false; static bool open_when_needed = false; +static bool prohibit_ipc = false; /* Akin to glibc's __abort_msg; which is private and we hence cannot * use here. */ static char *log_abort_msg = NULL; -void log_close_console(void) { +static void log_close_console(void) { if (console_fd < 0) return; - if (getpid_cached() == 1) { - if (console_fd >= 3) - safe_close(console_fd); + if (console_fd >= 3) + safe_close(console_fd); - console_fd = -1; - } + console_fd = -1; } static int log_open_console(void) { - if (console_fd >= 0) + if (!always_reopen_console) { + console_fd = STDERR_FILENO; return 0; + } - if (always_reopen_console) { + if (console_fd < 3) { console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); if (console_fd < 0) return console_fd; - } else - console_fd = STDERR_FILENO; + } return 0; } -void log_close_kmsg(void) { +static void log_close_kmsg(void) { kmsg_fd = safe_close(kmsg_fd); } @@ -125,7 +126,7 @@ static int log_open_kmsg(void) { return 0; } -void log_close_syslog(void) { +static void log_close_syslog(void) { syslog_fd = safe_close(syslog_fd); } @@ -197,7 +198,7 @@ fail: return r; } -void log_close_journal(void) { +static void log_close_journal(void) { journal_fd = safe_close(journal_fd); } @@ -239,7 +240,8 @@ int log_open(void) { /* If we don't use the console we close it here, to not get * killed by SAK. If we don't use syslog we close it here so * that we are not confused by somebody deleting the socket in - * the fs. If we don't use /dev/kmsg we still keep it open, + * the fs, and to make sure we don't use it if prohibit_ipc is + * set. If we don't use /dev/kmsg we still keep it open, * because there is no reason to close it. */ if (log_target == LOG_TARGET_NULL) { @@ -249,11 +251,12 @@ int log_open(void) { return 0; } - if (!IN_SET(log_target, LOG_TARGET_AUTO, LOG_TARGET_SAFE) || + if (log_target != LOG_TARGET_AUTO || getpid_cached() == 1 || isatty(STDERR_FILENO) <= 0) { - if (IN_SET(log_target, LOG_TARGET_AUTO, + if (!prohibit_ipc && + IN_SET(log_target, LOG_TARGET_AUTO, LOG_TARGET_JOURNAL_OR_KMSG, LOG_TARGET_JOURNAL)) { r = log_open_journal(); @@ -264,7 +267,8 @@ int log_open(void) { } } - if (IN_SET(log_target, LOG_TARGET_SYSLOG_OR_KMSG, + if (!prohibit_ipc && + IN_SET(log_target, LOG_TARGET_SYSLOG_OR_KMSG, LOG_TARGET_SYSLOG)) { r = log_open_syslog(); if (r >= 0) { @@ -275,7 +279,6 @@ int log_open(void) { } if (IN_SET(log_target, LOG_TARGET_AUTO, - LOG_TARGET_SAFE, LOG_TARGET_JOURNAL_OR_KMSG, LOG_TARGET_SYSLOG_OR_KMSG, LOG_TARGET_KMSG)) { @@ -627,7 +630,6 @@ int log_dispatch_internal( if (k <= 0 && IN_SET(log_target, LOG_TARGET_AUTO, - LOG_TARGET_SAFE, LOG_TARGET_SYSLOG_OR_KMSG, LOG_TARGET_JOURNAL_OR_KMSG, LOG_TARGET_KMSG)) { @@ -1219,17 +1221,18 @@ static const char *const log_target_table[_LOG_TARGET_MAX] = { [LOG_TARGET_SYSLOG] = "syslog", [LOG_TARGET_SYSLOG_OR_KMSG] = "syslog-or-kmsg", [LOG_TARGET_AUTO] = "auto", - [LOG_TARGET_SAFE] = "safe", - [LOG_TARGET_NULL] = "null" + [LOG_TARGET_NULL] = "null", }; DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget); void log_received_signal(int level, const struct signalfd_siginfo *si) { - if (si->ssi_pid > 0) { + assert(si); + + if (pid_is_valid(si->ssi_pid)) { _cleanup_free_ char *p = NULL; - get_process_comm(si->ssi_pid, &p); + (void) get_process_comm(si->ssi_pid, &p); log_full(level, "Received SIG%s from PID %"PRIu32" (%s).", @@ -1239,7 +1242,6 @@ void log_received_signal(int level, const struct signalfd_siginfo *si) { log_full(level, "Received SIG%s.", signal_to_string(si->ssi_signo)); - } int log_syntax_internal( @@ -1289,8 +1291,37 @@ int log_syntax_internal( NULL); } +int log_syntax_invalid_utf8_internal( + const char *unit, + int level, + const char *config_file, + unsigned config_line, + const char *file, + int line, + const char *func, + const char *rvalue) { + + _cleanup_free_ char *p = NULL; + + if (rvalue) + p = utf8_escape_invalid(rvalue); + + log_syntax_internal(unit, level, config_file, config_line, 0, file, line, func, + "String is not UTF-8 clean, ignoring assignment: %s", strna(p)); + + return -EINVAL; +} + void log_set_upgrade_syslog_to_journal(bool b) { upgrade_syslog_to_journal = b; + + /* Make the change effective immediately */ + if (b) { + if (log_target == LOG_TARGET_SYSLOG) + log_target = LOG_TARGET_JOURNAL; + else if (log_target == LOG_TARGET_SYSLOG_OR_KMSG) + log_target = LOG_TARGET_JOURNAL_OR_KMSG; + } } void log_set_always_reopen_console(bool b) { @@ -1300,3 +1331,14 @@ void log_set_always_reopen_console(bool b) { void log_set_open_when_needed(bool b) { open_when_needed = b; } + +void log_set_prohibit_ipc(bool b) { + prohibit_ipc = b; +} + +int log_emergency_level(void) { + /* Returns the log level to use for log_emergency() logging. We use LOG_EMERG only when we are PID 1, as only + * then the system of the whole system is obviously affected. */ + + return getpid_cached() == 1 ? LOG_EMERG : LOG_ERR; +} diff --git a/src/basic/log.h b/src/basic/log.h index aa5976c3c0..5b5a25bd6d 100644 --- a/src/basic/log.h +++ b/src/basic/log.h @@ -20,18 +20,16 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <errno.h> #include <stdarg.h> #include <stdbool.h> #include <stdlib.h> -#include <sys/signalfd.h> -#include <sys/socket.h> #include <syslog.h> -#include "sd-id128.h" - #include "macro.h" -#include "process-util.h" + +/* Some structures we reference but don't want to pull in headers for */ +struct iovec; +struct signalfd_siginfo; typedef enum LogRealm { LOG_REALM_SYSTEMD, @@ -52,7 +50,6 @@ typedef enum LogTarget{ LOG_TARGET_SYSLOG, LOG_TARGET_SYSLOG_OR_KMSG, LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */ - LOG_TARGET_SAFE, /* console if stderr is tty, KMSG otherwise */ LOG_TARGET_NULL, _LOG_TARGET_MAX, _LOG_TARGET_INVALID = -1 @@ -97,11 +94,6 @@ int log_open(void); void log_close(void); void log_forget_fds(void); -void log_close_syslog(void); -void log_close_journal(void); -void log_close_kmsg(void); -void log_close_console(void); - void log_parse_environment_realm(LogRealm realm); #define log_parse_environment() \ log_parse_environment_realm(LOG_REALM) @@ -194,7 +186,7 @@ int log_struct_iovec_internal( const char *file, int line, const char *func, - const struct iovec input_iovec[], + const struct iovec *input_iovec, size_t n_input_iovec); /* This modifies the buffer passed! */ @@ -240,9 +232,9 @@ void log_assert_failed_return_realm( /* Logging with level */ #define log_full_errno_realm(realm, level, error, ...) \ ({ \ - int _level = (level), _e = (error); \ - (log_get_max_level_realm((realm)) >= LOG_PRI(_level)) \ - ? log_internal_realm(LOG_REALM_PLUS_LEVEL((realm), _level), _e, \ + int _level = (level), _e = (error), _realm = (realm); \ + (log_get_max_level_realm(_realm) >= LOG_PRI(_level)) \ + ? log_internal_realm(LOG_REALM_PLUS_LEVEL(_realm, _level), _e, \ __FILE__, __LINE__, __func__, __VA_ARGS__) \ : -abs(_e); \ }) @@ -252,13 +244,15 @@ void log_assert_failed_return_realm( #define log_full(level, ...) log_full_errno((level), 0, __VA_ARGS__) +int log_emergency_level(void); + /* Normal logging */ #define log_debug(...) log_full(LOG_DEBUG, __VA_ARGS__) #define log_info(...) log_full(LOG_INFO, __VA_ARGS__) #define log_notice(...) log_full(LOG_NOTICE, __VA_ARGS__) #define log_warning(...) log_full(LOG_WARNING, __VA_ARGS__) #define log_error(...) log_full(LOG_ERR, __VA_ARGS__) -#define log_emergency(...) log_full(getpid_cached() == 1 ? LOG_EMERG : LOG_ERR, __VA_ARGS__) +#define log_emergency(...) log_full(log_emergency_level(), __VA_ARGS__) /* Logging triggered by an errno-like error */ #define log_debug_errno(error, ...) log_full_errno(LOG_DEBUG, error, __VA_ARGS__) @@ -266,7 +260,7 @@ void log_assert_failed_return_realm( #define log_notice_errno(error, ...) log_full_errno(LOG_NOTICE, error, __VA_ARGS__) #define log_warning_errno(error, ...) log_full_errno(LOG_WARNING, error, __VA_ARGS__) #define log_error_errno(error, ...) log_full_errno(LOG_ERR, error, __VA_ARGS__) -#define log_emergency_errno(error, ...) log_full_errno(getpid_cached() == 1 ? LOG_EMERG : LOG_ERR, error, __VA_ARGS__) +#define log_emergency_errno(error, ...) log_full_errno(log_emergency_level(), error, __VA_ARGS__) #ifdef LOG_TRACE # define log_trace(...) log_debug(__VA_ARGS__) @@ -302,10 +296,20 @@ LogTarget log_target_from_string(const char *s) _pure_; void log_received_signal(int level, const struct signalfd_siginfo *si); +/* If turned on, any requests for a log target involving "syslog" will be implicitly upgraded to the equivalent journal target */ void log_set_upgrade_syslog_to_journal(bool b); + +/* If turned on, and log_open() is called, we'll not use STDERR_FILENO for logging ever, but rather open /dev/console */ void log_set_always_reopen_console(bool b); + +/* If turned on, we'll open the log stream implicitly if needed on each individual log call. This is normally not + * desired as we want to reuse our logging streams. It is useful however */ void log_set_open_when_needed(bool b); +/* If turned on, then we'll never use IPC-based logging, i.e. never log to syslog or the journal. We'll only log to + * stderr, the console or kmsg */ +void log_set_prohibit_ipc(bool b); + int log_syntax_internal( const char *unit, int level, @@ -317,6 +321,16 @@ int log_syntax_internal( const char *func, const char *format, ...) _printf_(9, 10); +int log_syntax_invalid_utf8_internal( + const char *unit, + int level, + const char *config_file, + unsigned config_line, + const char *file, + int line, + const char *func, + const char *rvalue); + #define log_syntax(unit, level, config_file, config_line, error, ...) \ ({ \ int _level = (level), _e = (error); \ @@ -328,10 +342,9 @@ int log_syntax_internal( #define log_syntax_invalid_utf8(unit, level, config_file, config_line, rvalue) \ ({ \ int _level = (level); \ - if (log_get_max_level() >= LOG_PRI(_level)) { \ - _cleanup_free_ char *_p = NULL; \ - _p = utf8_escape_invalid(rvalue); \ - log_syntax_internal(unit, _level, config_file, config_line, 0, __FILE__, __LINE__, __func__, \ - "String is not UTF-8 clean, ignoring assignment: %s", strna(_p)); \ - } \ + (log_get_max_level() >= LOG_PRI(_level)) \ + ? log_syntax_invalid_utf8_internal(unit, _level, config_file, config_line, __FILE__, __LINE__, __func__, rvalue) \ + : -EINVAL; \ }) + +#define DEBUG_LOGGING _unlikely_(log_get_max_level() >= LOG_DEBUG) diff --git a/src/basic/macro.h b/src/basic/macro.h index 02d22de833..89bdd852a9 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -139,11 +139,17 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) { return 1UL << (sizeof(u) * 8 - __builtin_clzl(u - 1UL)); } +#ifndef __COVERITY__ +# define VOID_0 ((void)0) +#else +# define VOID_0 ((void*)0) +#endif + #define ELEMENTSOF(x) \ __extension__ (__builtin_choose_expr( \ !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ sizeof(x)/sizeof((x)[0]), \ - (void)0)) + VOID_0)) /* * STRLEN - return the length of a string literal, minus the trailing NUL byte. @@ -181,7 +187,7 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) { __builtin_constant_p(_B) && \ __builtin_types_compatible_p(typeof(_A), typeof(_B)), \ ((_A) > (_B)) ? (_A) : (_B), \ - (void)0)) + VOID_0)) /* takes two types and returns the size of the larger one */ #define MAXSIZE(A, B) (sizeof(union _packed_ { typeof(A) a; typeof(B) b; })) diff --git a/src/basic/meson.build b/src/basic/meson.build index a37e279e57..44cd31ecbe 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -15,7 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with systemd; If not, see <http://www.gnu.org/licenses/>. -basic_sources_plain = files(''' +basic_sources = files(''' MurmurHash2.c MurmurHash2.h af-list.c @@ -35,6 +35,8 @@ basic_sources_plain = files(''' bitmap.c bitmap.h blkid-util.h + blockdev-util.c + blockdev-util.h bpf-program.c bpf-program.h btrfs-ctree.h @@ -148,6 +150,8 @@ basic_sources_plain = files(''' proc-cmdline.h process-util.c process-util.h + procfs-util.c + procfs-util.h random-util.c random-util.h ratelimit.c @@ -176,6 +180,8 @@ basic_sources_plain = files(''' smack-util.c smack-util.h socket-label.c + socket-protocol-list.c + socket-protocol-list.h socket-util.c socket-util.h sparse-endian.h @@ -201,10 +207,10 @@ basic_sources_plain = files(''' time-util.h umask-util.h unaligned.h - unit-name.c - unit-name.h unit-def.c unit-def.h + unit-name.c + unit-name.h user-util.c user-util.h utf8.c @@ -255,11 +261,19 @@ errno_list_txt = custom_target( command : [generate_errno_list, cpp], capture : true) +generate_socket_protocol_list = find_program('generate-socket-protocol-list.sh') +socket_protocol_list_txt = custom_target( + 'socket-protocol-list.txt', + output : 'socket-protocol-list.txt', + command : [generate_socket_protocol_list, cpp], + capture : true) + generated_gperf_headers = [] foreach item : [['af', af_list_txt, 'af', ''], ['arphrd', arphrd_list_txt, 'arphrd', 'ARPHRD_'], ['cap', cap_list_txt, 'capability', ''], - ['errno', errno_list_txt, 'errno', '']] + ['errno', errno_list_txt, 'errno', ''], + ['socket-protocol', socket_protocol_list_txt, 'socket_protocol', 'IPPROTO_']] fname = '@0@-from-name.gperf'.format(item[0]) gperf_file = custom_target( @@ -294,7 +308,7 @@ foreach item : [['af', af_list_txt, 'af', ''], generated_gperf_headers += [target1, target2] endforeach -basic_sources = basic_sources_plain + [missing_h] + generated_gperf_headers +basic_sources += [missing_h] + generated_gperf_headers libbasic = static_library( 'basic', @@ -303,6 +317,16 @@ libbasic = static_library( dependencies : [threads, libcap, libblkid, - libselinux, - ], + libselinux], + c_args : ['-fvisibility=default'], install : false) + +# A convenience library that is separate from libbasic to avoid +# unnecessary linking to libgcrypt. +libbasic_gcrypt = static_library( + 'basic-gcrypt', + 'gcrypt-util.c', + 'gcrypt-util.h', + include_directories : includes, + dependencies : [libgcrypt], + c_args : ['-fvisibility=default']) diff --git a/src/basic/missing.h b/src/basic/missing.h index 790f9f55a5..1280e6c410 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -206,6 +206,32 @@ struct sockaddr_vm { #endif #if ! HAVE_LINUX_BTRFS_H +#define BTRFS_IOC_QGROUP_ASSIGN _IOW(BTRFS_IOCTL_MAGIC, 41, \ + struct btrfs_ioctl_qgroup_assign_args) +#define BTRFS_IOC_QGROUP_CREATE _IOW(BTRFS_IOCTL_MAGIC, 42, \ + struct btrfs_ioctl_qgroup_create_args) +#define BTRFS_IOC_QUOTA_RESCAN _IOW(BTRFS_IOCTL_MAGIC, 44, \ + struct btrfs_ioctl_quota_rescan_args) +#define BTRFS_IOC_QUOTA_RESCAN_STATUS _IOR(BTRFS_IOCTL_MAGIC, 45, \ + struct btrfs_ioctl_quota_rescan_args) + +struct btrfs_ioctl_quota_rescan_args { + __u64 flags; + __u64 progress; + __u64 reserved[6]; +}; + +struct btrfs_ioctl_qgroup_assign_args { + __u64 assign; + __u64 src; + __u64 dst; +}; + +struct btrfs_ioctl_qgroup_create_args { + __u64 create; + __u64 qgroupid; +}; + struct btrfs_ioctl_vol_args { int64_t fd; char name[BTRFS_PATH_NAME_MAX + 1]; @@ -543,6 +569,38 @@ struct btrfs_ioctl_quota_ctl_args { #define PR_SET_CHILD_SUBREAPER 36 #endif +#ifndef PR_SET_MM_ARG_START +#define PR_SET_MM_ARG_START 8 +#endif + +#ifndef PR_SET_MM_ARG_END +#define PR_SET_MM_ARG_END 9 +#endif + +#ifndef PR_SET_MM_ENV_START +#define PR_SET_MM_ENV_START 10 +#endif + +#ifndef PR_SET_MM_ENV_END +#define PR_SET_MM_ENV_END 11 +#endif + +#ifndef EFIVARFS_MAGIC +#define EFIVARFS_MAGIC 0xde5e81e4 +#endif + +#ifndef SMACK_MAGIC +#define SMACK_MAGIC 0x43415d53 +#endif + +#ifndef DM_DEFERRED_REMOVE +#define DM_DEFERRED_REMOVE (1 << 17) +#endif + +#ifndef MAX_HANDLE_SZ +#define MAX_HANDLE_SZ 128 +#endif + #if ! HAVE_SECURE_GETENV # if HAVE___SECURE_GETENV # define secure_getenv __secure_getenv @@ -563,6 +621,10 @@ struct btrfs_ioctl_quota_ctl_args { # define SO_REUSEPORT 15 #endif +#ifndef SO_PEERGROUPS +# define SO_PEERGROUPS 59 +#endif + #ifndef EVIOCREVOKE # define EVIOCREVOKE _IOW('E', 0x91, int) #endif @@ -653,18 +715,28 @@ struct input_mask { #define IFLA_MACVLAN_MAX (__IFLA_MACVLAN_MAX - 1) #endif -#if !HAVE_IFLA_IPVLAN_MODE +#if !HAVE_IFLA_IPVLAN_FLAGS #define IFLA_IPVLAN_UNSPEC 0 #define IFLA_IPVLAN_MODE 1 -#define __IFLA_IPVLAN_MAX 2 +#define IFLA_IPVLAN_FLAGS 2 +#define __IFLA_IPVLAN_MAX 3 #define IFLA_IPVLAN_MAX (__IFLA_IPVLAN_MAX - 1) #define IPVLAN_MODE_L2 0 #define IPVLAN_MODE_L3 1 +#define IPVLAN_MODE_L3S 2 #define IPVLAN_MAX 2 #endif +#if !HAVE_IPVLAN_F_PRIVATE +#define IPVLAN_F_PRIVATE 0x01 +#define IPVLAN_F_VEPA 0x02 +#define __IPVLAN_F_PRIVATE_MAX 3 + +#define HAVE_IPVLAN_F_PRIVATE_MAX (__HAVE_IPVLAN_F_PRIVATE_MAX - 1) +#endif + #if !HAVE_IFLA_VTI_REMOTE #define IFLA_VTI_UNSPEC 0 #define IFLA_VTI_LINK 1 diff --git a/src/basic/missing_syscall.h b/src/basic/missing_syscall.h index fd82c11e94..c938d0d976 100644 --- a/src/basic/missing_syscall.h +++ b/src/basic/missing_syscall.h @@ -330,10 +330,14 @@ static inline ssize_t copy_file_range(int fd_in, loff_t *off_in, # define __NR_bpf 321 # elif defined __aarch64__ # define __NR_bpf 280 +# elif defined __arm__ +# define __NR_bpf 386 # elif defined __sparc__ # define __NR_bpf 349 # elif defined __s390__ # define __NR_bpf 351 +# elif defined __tilegx__ +# define __NR_bpf 280 # else # warning "__NR_bpf not defined for your architecture" # endif diff --git a/src/basic/mkdir-label.c b/src/basic/mkdir-label.c index 16eb7ce265..6f3a46f467 100644 --- a/src/basic/mkdir-label.c +++ b/src/basic/mkdir-label.c @@ -20,11 +20,32 @@ ***/ #include <stdio.h> +#include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include "label.h" +#include "macro.h" #include "mkdir.h" +#include "selinux-util.h" +#include "smack-util.h" + +int mkdir_label(const char *path, mode_t mode) { + int r; + + assert(path); + + r = mac_selinux_create_file_prepare(path, S_IFDIR); + if (r < 0) + return r; + + r = mkdir_errno_wrapper(path, mode); + mac_selinux_create_file_clear(); + if (r < 0) + return r; + + return mac_smack_fix(path, false, false); +} int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid, bool follow_symlink) { return mkdir_safe_internal(path, mode, uid, gid, follow_symlink, mkdir_label); diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c index 4386b38c4a..de4746c867 100644 --- a/src/basic/mkdir.c +++ b/src/basic/mkdir.c @@ -35,6 +35,8 @@ int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, boo struct stat st; int r; + assert(_mkdir != mkdir); + if (_mkdir(path, mode) >= 0) { r = chmod_and_chown(path, mode, uid, gid); if (r < 0) @@ -68,8 +70,14 @@ int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, boo return 0; } +int mkdir_errno_wrapper(const char *pathname, mode_t mode) { + if (mkdir(pathname, mode) < 0) + return -errno; + return 0; +} + int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, bool follow_symlink) { - return mkdir_safe_internal(path, mode, uid, gid, follow_symlink, mkdir); + return mkdir_safe_internal(path, mode, uid, gid, follow_symlink, mkdir_errno_wrapper); } int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { @@ -77,6 +85,7 @@ int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mk int r; assert(path); + assert(_mkdir != mkdir); if (prefix && !path_startswith(path, prefix)) return -ENOTDIR; @@ -104,8 +113,7 @@ int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mk e = p + strcspn(p, "/"); p = e + strspn(e, "/"); - /* Is this the last component? If so, then we're - * done */ + /* Is this the last component? If so, then we're done */ if (*p == 0) return 0; @@ -116,13 +124,13 @@ int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mk continue; r = _mkdir(t, mode); - if (r < 0 && errno != EEXIST) - return -errno; + if (r < 0 && r != -EEXIST) + return r; } } int mkdir_parents(const char *path, mode_t mode) { - return mkdir_parents_internal(NULL, path, mode, mkdir); + return mkdir_parents_internal(NULL, path, mode, mkdir_errno_wrapper); } int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { @@ -130,17 +138,19 @@ int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_fu /* Like mkdir -p */ + assert(_mkdir != mkdir); + r = mkdir_parents_internal(prefix, path, mode, _mkdir); if (r < 0) return r; r = _mkdir(path, mode); - if (r < 0 && (errno != EEXIST || is_dir(path, true) <= 0)) - return -errno; + if (r < 0 && (r != -EEXIST || is_dir(path, true) <= 0)) + return r; return 0; } int mkdir_p(const char *path, mode_t mode) { - return mkdir_p_internal(NULL, path, mode, mkdir); + return mkdir_p_internal(NULL, path, mode, mkdir_errno_wrapper); } diff --git a/src/basic/mkdir.h b/src/basic/mkdir.h index 04a537f8a8..d6c2d579a3 100644 --- a/src/basic/mkdir.h +++ b/src/basic/mkdir.h @@ -23,6 +23,7 @@ #include <sys/types.h> +int mkdir_errno_wrapper(const char *pathname, mode_t mode); int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, bool follow_symlink); int mkdir_parents(const char *path, mode_t mode); int mkdir_p(const char *path, mode_t mode); diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index d03f60e01a..2c22753dea 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -28,6 +28,7 @@ #include "alloc-util.h" #include "errno-list.h" #include "extract-word.h" +#include "locale-util.h" #include "macro.h" #include "parse-util.h" #include "process-util.h" @@ -83,7 +84,7 @@ int parse_mode(const char *s, mode_t *ret) { l = strtol(s, &x, 8); if (errno > 0) return -errno; - if (!x || x == s || *x) + if (!x || x == s || *x != 0) return -EINVAL; if (l < 0 || l > 07777) return -ERANGE; @@ -283,7 +284,8 @@ int parse_errno(const char *t) { if (r < 0) return r; - if (e < 0 || e > ERRNO_MAX) + /* 0 is also allowed here */ + if (!errno_is_valid(e) && e != 0) return -ERANGE; return e; @@ -390,7 +392,7 @@ int safe_atou(const char *s, unsigned *ret_u) { l = strtoul(s, &x, 0); if (errno > 0) return -errno; - if (!x || x == s || *x) + if (!x || x == s || *x != 0) return -EINVAL; if (s[0] == '-') return -ERANGE; @@ -412,7 +414,7 @@ int safe_atoi(const char *s, int *ret_i) { l = strtol(s, &x, 0); if (errno > 0) return -errno; - if (!x || x == s || *x) + if (!x || x == s || *x != 0) return -EINVAL; if ((long) (int) l != l) return -ERANGE; @@ -434,7 +436,7 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) { l = strtoull(s, &x, 0); if (errno > 0) return -errno; - if (!x || x == s || *x) + if (!x || x == s || *x != 0) return -EINVAL; if (*s == '-') return -ERANGE; @@ -454,7 +456,7 @@ int safe_atolli(const char *s, long long int *ret_lli) { l = strtoll(s, &x, 0); if (errno > 0) return -errno; - if (!x || x == s || *x) + if (!x || x == s || *x != 0) return -EINVAL; *ret_lli = l; @@ -474,7 +476,7 @@ int safe_atou8(const char *s, uint8_t *ret) { l = strtoul(s, &x, 0); if (errno > 0) return -errno; - if (!x || x == s || *x) + if (!x || x == s || *x != 0) return -EINVAL; if (s[0] == '-') return -ERANGE; @@ -498,7 +500,7 @@ int safe_atou16(const char *s, uint16_t *ret) { l = strtoul(s, &x, 0); if (errno > 0) return -errno; - if (!x || x == s || *x) + if (!x || x == s || *x != 0) return -EINVAL; if (s[0] == '-') return -ERANGE; @@ -520,7 +522,7 @@ int safe_atoi16(const char *s, int16_t *ret) { l = strtol(s, &x, 0); if (errno > 0) return -errno; - if (!x || x == s || *x) + if (!x || x == s || *x != 0) return -EINVAL; if ((long) (int16_t) l != l) return -ERANGE; @@ -530,9 +532,9 @@ int safe_atoi16(const char *s, int16_t *ret) { } int safe_atod(const char *s, double *ret_d) { + _cleanup_(freelocalep) locale_t loc = (locale_t) 0; char *x = NULL; double d = 0; - locale_t loc; assert(s); assert(ret_d); @@ -543,16 +545,11 @@ int safe_atod(const char *s, double *ret_d) { errno = 0; d = strtod_l(s, &x, loc); - if (errno > 0) { - freelocale(loc); + if (errno > 0) return -errno; - } - if (!x || x == s || *x) { - freelocale(loc); + if (!x || x == s || *x != 0) return -EINVAL; - } - freelocale(loc); *ret_d = (double) d; return 0; } @@ -595,19 +592,20 @@ int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) { int parse_percent_unbounded(const char *p) { const char *pc, *n; - unsigned v; - int r; + int r, v; pc = endswith(p, "%"); if (!pc) return -EINVAL; n = strndupa(p, pc - p); - r = safe_atou(n, &v); + r = safe_atoi(n, &v); if (r < 0) return r; + if (v < 0) + return -ERANGE; - return (int) v; + return v; } int parse_percent(const char *p) { diff --git a/src/basic/path-util.c b/src/basic/path-util.c index ab4778d4ed..df94629385 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -81,14 +81,36 @@ char *path_make_absolute(const char *p, const char *prefix) { /* Makes every item in the list an absolute path by prepending * the prefix, if specified and necessary */ - if (path_is_absolute(p) || !prefix) + if (path_is_absolute(p) || isempty(prefix)) return strdup(p); - return strjoin(prefix, "/", p); + if (endswith(prefix, "/")) + return strjoin(prefix, p); + else + return strjoin(prefix, "/", p); +} + +int safe_getcwd(char **ret) { + char *cwd; + + cwd = get_current_dir_name(); + if (!cwd) + return negative_errno(); + + /* Let's make sure the directory is really absolute, to protect us from the logic behind + * CVE-2018-1000001 */ + if (cwd[0] != '/') { + free(cwd); + return -ENOMEDIUM; + } + + *ret = cwd; + return 0; } int path_make_absolute_cwd(const char *p, char **ret) { char *c; + int r; assert(p); assert(ret); @@ -101,11 +123,14 @@ int path_make_absolute_cwd(const char *p, char **ret) { else { _cleanup_free_ char *cwd = NULL; - cwd = get_current_dir_name(); - if (!cwd) - return negative_errno(); + r = safe_getcwd(&cwd); + if (r < 0) + return r; - c = strjoin(cwd, "/", p); + if (endswith(cwd, "/")) + c = strjoin(cwd, p); + else + c = strjoin(cwd, "/", p); } if (!c) return -ENOMEM; diff --git a/src/basic/path-util.h b/src/basic/path-util.h index f79cdf928e..89c285e076 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -41,6 +41,7 @@ bool is_path(const char *p) _pure_; int path_split_and_make_absolute(const char *p, char ***ret); bool path_is_absolute(const char *p) _pure_; char* path_make_absolute(const char *p, const char *prefix); +int safe_getcwd(char **ret); int path_make_absolute_cwd(const char *p, char **ret); int path_make_relative(const char *from_dir, const char *to_path, char **_r); char* path_kill_slashes(char *path); diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 17c94f44a0..dc7c9ef9ef 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -56,6 +56,7 @@ #include "stat-util.h" #include "string-table.h" #include "string-util.h" +#include "terminal-util.h" #include "user-util.h" #include "util.h" @@ -296,10 +297,17 @@ int rename_process(const char name[]) { 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 */ + l = strlen(name); - /* First step, change the comm field. */ - (void) prctl(PR_SET_NAME, 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 > 15) /* Linux process names can be 15 chars at max */ truncated = true; @@ -679,32 +687,43 @@ int wait_for_terminate(pid_t pid, siginfo_t *status) { * A warning is emitted if the process terminates abnormally, * and also if it returns non-zero unless check_exit_code is true. */ -int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code) { - int r; +int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags) { + _cleanup_free_ char *buffer = NULL; siginfo_t status; + int r, prio; - assert(name); assert(pid > 1); + if (!name) { + r = get_process_comm(pid, &buffer); + if (r < 0) + log_debug_errno(r, "Failed to acquire process name of " PID_FMT ", ignoring: %m", pid); + else + name = buffer; + } + + prio = flags & WAIT_LOG_ABNORMAL ? LOG_ERR : LOG_DEBUG; + r = wait_for_terminate(pid, &status); if (r < 0) - return log_warning_errno(r, "Failed to wait for %s: %m", name); + return log_full_errno(prio, r, "Failed to wait for %s: %m", strna(name)); if (status.si_code == CLD_EXITED) { - if (status.si_status != 0) - log_full(check_exit_code ? LOG_WARNING : LOG_DEBUG, - "%s failed with error code %i.", name, status.si_status); + if (status.si_status != EXIT_SUCCESS) + log_full(flags & WAIT_LOG_NON_ZERO_EXIT_STATUS ? LOG_ERR : LOG_DEBUG, + "%s failed with exit status %i.", strna(name), status.si_status); else log_debug("%s succeeded.", name); return status.si_status; + } else if (IN_SET(status.si_code, CLD_KILLED, CLD_DUMPED)) { - log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status)); + log_full(prio, "%s terminated by signal %s.", strna(name), signal_to_string(status.si_status)); return -EPROTO; } - log_warning("%s failed due to unknown reason.", name); + log_full(prio, "%s failed due to unknown reason.", strna(name)); return -EPROTO; } @@ -777,6 +796,8 @@ void sigkill_wait(pid_t pid) { } void sigkill_waitp(pid_t *pid) { + PROTECT_ERRNO; + if (!pid) return; if (*pid <= 1) @@ -928,6 +949,17 @@ noreturn void freeze(void) { sync(); + /* Let's not freeze right away, but keep reaping zombies. */ + for (;;) { + int r; + siginfo_t si = {}; + + r = waitid(P_ALL, 0, &si, WEXITED); + if (r < 0 && errno != EINTR) + break; + } + + /* waitid() failed with an unexpected error, things are really borked. Freeze now! */ for (;;) pause(); } @@ -1075,7 +1107,7 @@ int ioprio_parse_priority(const char *s, int *ret) { static pid_t cached_pid = CACHED_PID_UNSET; -static void reset_cached_pid(void) { +void reset_cached_pid(void) { /* Invoked in the child after a fork(), i.e. at the first moment the PID changed */ cached_pid = CACHED_PID_UNSET; } @@ -1134,6 +1166,244 @@ int must_be_root(void) { return -EPERM; } +int safe_fork_full( + const char *name, + const int except_fds[], + size_t n_except_fds, + ForkFlags flags, + pid_t *ret_pid) { + + pid_t original_pid, pid; + sigset_t saved_ss, ss; + bool block_signals = false; + int prio, r; + + /* A wrapper around fork(), that does a couple of important initializations in addition to mere forking. Always + * returns the child's PID in *ret_pid. Returns == 0 in the child, and > 0 in the parent. */ + + prio = flags & FORK_LOG ? LOG_ERR : LOG_DEBUG; + + original_pid = getpid_cached(); + + if (flags & (FORK_RESET_SIGNALS|FORK_DEATHSIG)) { + + /* We temporarily block all signals, so that the new child has them blocked initially. This way, we can + * be sure that SIGTERMs are not lost we might send to the child. */ + + if (sigfillset(&ss) < 0) + return log_full_errno(prio, errno, "Failed to reset signal set: %m"); + + block_signals = true; + + } else if (flags & FORK_WAIT) { + + /* Let's block SIGCHLD at least, so that we can safely watch for the child process */ + + if (sigemptyset(&ss) < 0) + return log_full_errno(prio, errno, "Failed to clear signal set: %m"); + + if (sigaddset(&ss, SIGCHLD) < 0) + return log_full_errno(prio, errno, "Failed to add SIGCHLD to signal set: %m"); + + block_signals = true; + } + + if (block_signals) + if (sigprocmask(SIG_SETMASK, &ss, &saved_ss) < 0) + return log_full_errno(prio, errno, "Failed to set signal mask: %m"); + + if (flags & FORK_NEW_MOUNTNS) + pid = raw_clone(SIGCHLD|CLONE_NEWNS); + else + pid = fork(); + if (pid < 0) { + r = -errno; + + if (block_signals) /* undo what we did above */ + (void) sigprocmask(SIG_SETMASK, &saved_ss, NULL); + + return log_full_errno(prio, r, "Failed to fork: %m"); + } + if (pid > 0) { + /* We are in the parent process */ + + log_debug("Successfully forked off '%s' as PID " PID_FMT ".", strna(name), pid); + + if (flags & FORK_WAIT) { + r = wait_for_terminate_and_check(name, pid, (flags & FORK_LOG ? WAIT_LOG : 0)); + if (r < 0) + return r; + if (r != EXIT_SUCCESS) /* exit status > 0 should be treated as failure, too */ + return -EPROTO; + } + + if (block_signals) /* undo what we did above */ + (void) sigprocmask(SIG_SETMASK, &saved_ss, NULL); + + if (ret_pid) + *ret_pid = pid; + + return 1; + } + + /* We are in the child process */ + + if (flags & FORK_REOPEN_LOG) { + /* 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); + } + + if (name) { + r = rename_process(name); + if (r < 0) + log_full_errno(flags & FORK_LOG ? LOG_WARNING : LOG_DEBUG, + r, "Failed to rename process, ignoring: %m"); + } + + if (flags & FORK_DEATHSIG) + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) { + log_full_errno(prio, errno, "Failed to set death signal: %m"); + _exit(EXIT_FAILURE); + } + + if (flags & FORK_RESET_SIGNALS) { + r = reset_all_signal_handlers(); + if (r < 0) { + log_full_errno(prio, r, "Failed to reset signal handlers: %m"); + _exit(EXIT_FAILURE); + } + + /* This implicitly undoes the signal mask stuff we did before the fork()ing above */ + r = reset_signal_mask(); + if (r < 0) { + log_full_errno(prio, r, "Failed to reset signal mask: %m"); + _exit(EXIT_FAILURE); + } + } else if (block_signals) { /* undo what we did above */ + if (sigprocmask(SIG_SETMASK, &saved_ss, NULL) < 0) { + log_full_errno(prio, errno, "Failed to restore signal mask: %m"); + _exit(EXIT_FAILURE); + } + } + + if (flags & FORK_DEATHSIG) { + pid_t ppid; + /* Let's see if the parent PID is still the one we started from? If not, then the parent + * already died by the time we set PR_SET_PDEATHSIG, hence let's emulate the effect */ + + ppid = getppid(); + if (ppid == 0) + /* Parent is in a differn't PID namespace. */; + else if (ppid != original_pid) { + log_debug("Parent died early, raising SIGTERM."); + (void) raise(SIGTERM); + _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(); + + r = close_all_fds(except_fds, n_except_fds); + if (r < 0) { + log_full_errno(prio, r, "Failed to close all 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 (r < 0) { + log_full_errno(prio, r, "Failed to connect stdin/stdout to /dev/null: %m"); + _exit(EXIT_FAILURE); + } + } + + if (ret_pid) + *ret_pid = getpid_cached(); + + return 0; +} + +int fork_agent(const char *name, const int except[], unsigned n_except, pid_t *ret_pid, const char *path, ...) { + bool stdout_is_tty, stderr_is_tty; + unsigned n, i; + va_list ap; + char **l; + int r; + + assert(path); + + /* Spawns a temporary TTY agent, making sure it goes away when we go away */ + + r = safe_fork_full(name, except, n_except, FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS, ret_pid); + if (r < 0) + return r; + if (r > 0) + return 0; + + /* In the child: */ + + stdout_is_tty = isatty(STDOUT_FILENO); + stderr_is_tty = isatty(STDERR_FILENO); + + if (!stdout_is_tty || !stderr_is_tty) { + int fd; + + /* Detach from stdout/stderr. and reopen + * /dev/tty for them. This is important to + * ensure that when systemctl is started via + * popen() or a similar call that expects to + * read EOF we actually do generate EOF and + * not delay this indefinitely by because we + * keep an unused copy of stdin around. */ + fd = open("/dev/tty", O_WRONLY); + if (fd < 0) { + log_error_errno(errno, "Failed to open /dev/tty: %m"); + _exit(EXIT_FAILURE); + } + + if (!stdout_is_tty && dup2(fd, STDOUT_FILENO) < 0) { + log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); + _exit(EXIT_FAILURE); + } + + if (!stderr_is_tty && dup2(fd, STDERR_FILENO) < 0) { + log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); + _exit(EXIT_FAILURE); + } + + if (fd > STDERR_FILENO) + close(fd); + } + + /* Count arguments */ + va_start(ap, path); + for (n = 0; va_arg(ap, char*); n++) + ; + va_end(ap); + + /* Allocate strv */ + l = alloca(sizeof(char *) * (n + 1)); + + /* Fill in arguments */ + va_start(ap, path); + for (i = 0; i <= n; i++) + l[i] = va_arg(ap, char*); + va_end(ap); + + execv(path, l); + _exit(EXIT_FAILURE); +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", diff --git a/src/basic/process-util.h b/src/basic/process-util.h index 1b7e692060..9fabe4a5be 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -21,6 +21,7 @@ ***/ #include <alloca.h> +#include <errno.h> #include <sched.h> #include <signal.h> #include <stdbool.h> @@ -61,7 +62,16 @@ int get_process_environ(pid_t pid, char **environ); int get_process_ppid(pid_t pid, pid_t *ppid); int wait_for_terminate(pid_t pid, siginfo_t *status); -int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code); + +typedef enum WaitFlags { + WAIT_LOG_ABNORMAL = 1U << 0, + WAIT_LOG_NON_ZERO_EXIT_STATUS = 1U << 1, + + /* A shortcut for requesting the most complete logging */ + WAIT_LOG = WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS, +} WaitFlags; + +int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags); int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout); void sigkill_wait(pid_t pid); @@ -106,8 +116,13 @@ int sigchld_code_from_string(const char *s) _pure_; int sched_policy_to_string_alloc(int i, char **s); int sched_policy_from_string(const char *s); -#define PTR_TO_PID(p) ((pid_t) ((uintptr_t) p)) -#define PID_TO_PTR(p) ((void*) ((uintptr_t) p)) +static inline pid_t PTR_TO_PID(const void *p) { + return (pid_t) ((uintptr_t) p); +} + +static inline void* PID_TO_PTR(pid_t pid) { + return (void*) ((uintptr_t) pid); +} void valgrind_summary_hack(void); @@ -137,8 +152,54 @@ static inline bool pid_is_valid(pid_t p) { return p > 0; } +static inline int sched_policy_to_string_alloc_with_check(int n, char **s) { + if (!sched_policy_is_valid(n)) + return -EINVAL; + + return sched_policy_to_string_alloc(n, s); +} + int ioprio_parse_priority(const char *s, int *ret); pid_t getpid_cached(void); +void reset_cached_pid(void); int must_be_root(void); + +typedef enum ForkFlags { + FORK_RESET_SIGNALS = 1U << 0, + FORK_CLOSE_ALL_FDS = 1U << 1, + FORK_DEATHSIG = 1U << 2, + FORK_NULL_STDIO = 1U << 3, + FORK_REOPEN_LOG = 1U << 4, + FORK_LOG = 1U << 5, + FORK_WAIT = 1U << 6, + FORK_NEW_MOUNTNS = 1U << 7, +} ForkFlags; + +int safe_fork_full(const char *name, 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); +} + +int fork_agent(const char *name, const int except[], unsigned n_except, pid_t *pid, const char *path, ...); + +#if SIZEOF_PID_T == 4 +/* The highest possibly (theoretic) pid_t value on this architecture. */ +#define PID_T_MAX ((pid_t) INT32_MAX) +/* The maximum number of concurrent processes Linux allows on this architecture, as well as the highest valid PID value + * the kernel will potentially assign. This reflects a value compiled into the kernel (PID_MAX_LIMIT), and sets the + * upper boundary on what may be written to the /proc/sys/kernel/pid_max sysctl (but do note that the sysctl is off by + * 1, since PID 0 can never exist and there can hence only be one process less than the limit would suggest). Since + * these values are documented in proc(5) we feel quite confident that they are stable enough for the near future at + * least to define them here too. */ +#define TASKS_MAX 4194303U +#elif SIZEOF_PID_T == 2 +#define PID_T_MAX ((pid_t) INT16_MAX) +#define TASKS_MAX 32767U +#else +#error "Unknown pid_t size" +#endif + +assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX) diff --git a/src/basic/procfs-util.c b/src/basic/procfs-util.c new file mode 100644 index 0000000000..9bb42cc7ba --- /dev/null +++ b/src/basic/procfs-util.c @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <errno.h> + +#include "alloc-util.h" +#include "fileio.h" +#include "parse-util.h" +#include "process-util.h" +#include "procfs-util.h" +#include "stdio-util.h" +#include "string-util.h" + +int procfs_tasks_get_limit(uint64_t *ret) { + _cleanup_free_ char *value = NULL; + uint64_t pid_max, threads_max; + int r; + + assert(ret); + + /* So there are two sysctl files that control the system limit of processes: + * + * 1. kernel.threads-max: this is probably the sysctl that makes more sense, as it directly puts a limit on + * concurrent tasks. + * + * 2. kernel.pid_max: this limits the numeric range PIDs can take, and thus indirectly also limits the number + * of concurrent threads. AFAICS it's primarily a compatibility concept: some crappy old code used a signed + * 16bit type for PIDs, hence the kernel provides a way to ensure the PIDs never go beyond INT16_MAX by + * default. + * + * By default #2 is set to much lower values than #1, hence the limit people come into contact with first, as + * it's the lowest boundary they need to bump when they want higher number of processes. + * + * Also note the weird definition of #2: PIDs assigned will be kept below this value, which means the number of + * tasks that can be created is one lower, as PID 0 is not a valid process ID. */ + + r = read_one_line_file("/proc/sys/kernel/pid_max", &value); + if (r < 0) + return r; + + r = safe_atou64(value, &pid_max); + if (r < 0) + return r; + + value = mfree(value); + r = read_one_line_file("/proc/sys/kernel/threads-max", &value); + if (r < 0) + return r; + + r = safe_atou64(value, &threads_max); + if (r < 0) + return r; + + /* Subtract one from pid_max, since PID 0 is not a valid PID */ + *ret = MIN(pid_max-1, threads_max); + return 0; +} + +int procfs_tasks_set_limit(uint64_t limit) { + char buffer[DECIMAL_STR_MAX(uint64_t)+1]; + _cleanup_free_ char *value = NULL; + uint64_t pid_max; + int r; + + if (limit == 0) /* This makes no sense, we are userspace and hence count as tasks too, and we want to live, + * hence the limit conceptually has to be above 0. Also, most likely if anyone asks for a zero + * limit he/she probably means "no limit", hence let's better refuse this to avoid + * confusion. */ + return -EINVAL; + + /* The Linux kernel doesn't allow this value to go below 20, hence don't allow this either, higher values than + * TASKS_MAX are not accepted by the pid_max sysctl. We'll treat anything this high as "unbounded" and hence + * set it to the maximum. */ + limit = CLAMP(limit, 20U, TASKS_MAX); + + r = read_one_line_file("/proc/sys/kernel/pid_max", &value); + if (r < 0) + return r; + r = safe_atou64(value, &pid_max); + if (r < 0) + return r; + + /* As pid_max is about the numeric pid_t range we'll bump it if necessary, but only ever increase it, never + * decrease it, as threads-max is the much more relevant sysctl. */ + if (limit > pid_max-1) { + sprintf(buffer, "%" PRIu64, limit+1); /* Add one, since PID 0 is not a valid PID */ + r = write_string_file("/proc/sys/kernel/pid_max", buffer, WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return r; + } + + sprintf(buffer, "%" PRIu64, limit); + r = write_string_file("/proc/sys/kernel/threads-max", buffer, WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) { + uint64_t threads_max; + + /* Hmm, we couldn't write this? If so, maybe it was already set properly? In that case let's not + * generate an error */ + + value = mfree(value); + if (read_one_line_file("/proc/sys/kernel/threads-max", &value) < 0) + return r; /* return original error */ + + if (safe_atou64(value, &threads_max) < 0) + return r; /* return original error */ + + if (MIN(pid_max-1, threads_max) != limit) + return r; /* return original error */ + + /* Yay! Value set already matches what we were trying to set, hence consider this a success. */ + } + + return 0; +} + +int procfs_tasks_get_current(uint64_t *ret) { + _cleanup_free_ char *value = NULL; + const char *p, *nr; + size_t n; + int r; + + assert(ret); + + r = read_one_line_file("/proc/loadavg", &value); + if (r < 0) + return r; + + /* Look for the second part of the fourth field, which is separated by a slash from the first part. None of the + * earlier fields use a slash, hence let's use this to find the right spot. */ + p = strchr(value, '/'); + if (!p) + return -EINVAL; + + p++; + n = strspn(p, DIGITS); + nr = strndupa(p, n); + + return safe_atou64(nr, ret); +} diff --git a/src/basic/procfs-util.h b/src/basic/procfs-util.h new file mode 100644 index 0000000000..7466acd7f3 --- /dev/null +++ b/src/basic/procfs-util.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <inttypes.h> + +int procfs_tasks_get_limit(uint64_t *ret); +int procfs_tasks_set_limit(uint64_t limit); +int procfs_tasks_get_current(uint64_t *ret); diff --git a/src/basic/random-util.c b/src/basic/random-util.c index 1bc8000896..7457815fa2 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -21,11 +21,12 @@ #include <elf.h> #include <errno.h> #include <fcntl.h> +#include <linux/random.h> #include <stdbool.h> +#include <stdint.h> #include <stdlib.h> +#include <string.h> #include <sys/time.h> -#include <linux/random.h> -#include <stdint.h> #if HAVE_SYS_AUXV_H # include <sys/auxv.h> diff --git a/src/basic/raw-clone.h b/src/basic/raw-clone.h index f01b73a8fe..8c95380305 100644 --- a/src/basic/raw-clone.h +++ b/src/basic/raw-clone.h @@ -30,28 +30,27 @@ * raw_clone() - uses clone to create a new process with clone flags * @flags: Flags to pass to the clone system call * - * Uses the clone system call to create a new process with the cloning - * flags and termination signal passed in the flags parameter. Opposed - * to glibc's clone funtion, using this function does not set up a - * separate stack for the child, but relies on copy-on-write semantics - * on the one stack at a common virtual address, just as fork does. + * Uses the clone system call to create a new process with the cloning flags and termination signal passed in the flags + * parameter. Opposed to glibc's clone funtion, using this function does not set up a separate stack for the child, but + * relies on copy-on-write semantics on the one stack at a common virtual address, just as fork does. * - * To obtain copy-on-write semantics, flags must not contain CLONE_VM, - * and thus CLONE_THREAD and CLONE_SIGHAND (which require CLONE_VM) are - * not usabale. - * Additionally, as this function does not pass the ptid, newtls and ctid - * parameters to the kernel, flags must not contain CLONE_PARENT_SETTID, - * CLONE_CHILD_SETTID, CLONE_CHILD_CLEARTID or CLONE_SETTLS. + * To obtain copy-on-write semantics, flags must not contain CLONE_VM, and thus CLONE_THREAD and CLONE_SIGHAND (which + * require CLONE_VM) are not usable. + * + * Additionally, as this function does not pass the ptid, newtls and ctid parameters to the kernel, flags must not + * contain CLONE_PARENT_SETTID, CLONE_CHILD_SETTID, CLONE_CHILD_CLEARTID or CLONE_SETTLS. * * Returns: 0 in the child process and the child process id in the parent. */ -static inline int raw_clone(unsigned long flags) { +static inline pid_t raw_clone(unsigned long flags) { + pid_t ret; + assert((flags & (CLONE_VM|CLONE_PARENT_SETTID|CLONE_CHILD_SETTID| CLONE_CHILD_CLEARTID|CLONE_SETTLS)) == 0); #if defined(__s390x__) || defined(__s390__) || defined(__CRIS__) /* On s390/s390x and cris the order of the first and second arguments * of the raw clone() system call is reversed. */ - return (int) syscall(__NR_clone, NULL, flags); + ret = (pid_t) syscall(__NR_clone, NULL, flags); #elif defined(__sparc__) && defined(__arch64__) { /** @@ -60,8 +59,8 @@ static inline int raw_clone(unsigned long flags) { * %o1. Inline assembly is needed to get the flag returned * in %o1. */ - int in_child; - int child_pid; + int in_child, child_pid; + asm volatile("mov %2, %%g1\n\t" "mov %3, %%o0\n\t" "mov 0 , %%o1\n\t" @@ -71,12 +70,15 @@ static inline int raw_clone(unsigned long flags) { "=r"(in_child), "=r"(child_pid) : "i"(__NR_clone), "r"(flags) : "%o1", "%o0", "%g1" ); - if (in_child) - return 0; - else - return child_pid; + + ret = in_child ? 0 : child_pid; } #else - return (int) syscall(__NR_clone, flags, NULL); + ret = (pid_t) syscall(__NR_clone, flags, NULL); #endif + + if (ret == 0) + reset_cached_pid(); + + return ret; } diff --git a/src/basic/rm-rf.h b/src/basic/rm-rf.h index 1127e326b2..ad63e9be40 100644 --- a/src/basic/rm-rf.h +++ b/src/basic/rm-rf.h @@ -22,6 +22,8 @@ #include <sys/stat.h> +#include "util.h" + typedef enum RemoveFlags { REMOVE_ONLY_DIRECTORIES = 1, REMOVE_ROOT = 2, @@ -34,6 +36,7 @@ int rm_rf(const char *path, RemoveFlags flags); /* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */ static inline void rm_rf_physical_and_free(char *p) { + PROTECT_ERRNO; (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); free(p); } diff --git a/src/basic/securebits-util.c b/src/basic/securebits-util.c index b5f6418a6c..441d386f9e 100644 --- a/src/basic/securebits-util.c +++ b/src/basic/securebits-util.c @@ -19,6 +19,7 @@ ***/ #include <errno.h> +#include <stdio.h> #include "alloc-util.h" #include "extract-word.h" diff --git a/src/basic/securebits-util.h b/src/basic/securebits-util.h index aaa192f0a5..069d215488 100644 --- a/src/basic/securebits-util.h +++ b/src/basic/securebits-util.h @@ -24,6 +24,14 @@ int secure_bits_to_string_alloc(int i, char **s); int secure_bits_from_string(const char *s); + static inline bool secure_bits_is_valid(int i) { return ((SECURE_ALL_BITS | SECURE_ALL_LOCKS) & i) == i; } + +static inline int secure_bits_to_string_alloc_with_check(int n, char **s) { + if (!secure_bits_is_valid(n)) + return -EINVAL; + + return secure_bits_to_string_alloc(n, s); +} diff --git a/src/basic/signal-util.h b/src/basic/signal-util.h index 76b239b1fc..f6c3396ebe 100644 --- a/src/basic/signal-util.h +++ b/src/basic/signal-util.h @@ -55,3 +55,10 @@ static inline void block_signals_reset(sigset_t *ss) { static inline bool SIGNAL_VALID(int signo) { return signo > 0 && signo < _NSIG; } + +static inline const char* signal_to_string_with_check(int n) { + if (!SIGNAL_VALID(n)) + return NULL; + + return signal_to_string(n); +} diff --git a/src/basic/smack-util.c b/src/basic/smack-util.c index e0f1c9f1c7..f0018f013f 100644 --- a/src/basic/smack-util.c +++ b/src/basic/smack-util.c @@ -136,7 +136,7 @@ int mac_smack_apply_pid(pid_t pid, const char *label) { int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { struct stat st; - int r = 0; + int r; assert(path); diff --git a/src/basic/socket-label.c b/src/basic/socket-label.c index 20be406371..97f3ebe2af 100644 --- a/src/basic/socket-label.c +++ b/src/basic/socket-label.c @@ -29,6 +29,7 @@ #include "alloc-util.h" #include "fd-util.h" +#include "fs-util.h" #include "log.h" #include "macro.h" #include "missing.h" @@ -51,6 +52,7 @@ int socket_address_listen( const char *label) { _cleanup_close_ int fd = -1; + const char *p; int r, one; assert(a); @@ -112,19 +114,23 @@ int socket_address_listen( if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) return -errno; - if (socket_address_family(a) == AF_UNIX && a->sockaddr.un.sun_path[0] != 0) { + p = socket_address_get_path(a); + if (p) { /* Create parents */ - (void) mkdir_parents_label(a->sockaddr.un.sun_path, directory_mode); + (void) mkdir_parents_label(p, directory_mode); /* Enforce the right access mode for the socket */ RUN_WITH_UMASK(~socket_mode) { r = mac_selinux_bind(fd, &a->sockaddr.sa, a->size); if (r == -EADDRINUSE) { /* Unlink and try again */ - unlink(a->sockaddr.un.sun_path); - if (bind(fd, &a->sockaddr.sa, a->size) < 0) - return -errno; - } else if (r < 0) + + if (unlink(p) < 0) + return r; /* didn't work, return original error */ + + r = mac_selinux_bind(fd, &a->sockaddr.sa, a->size); + } + if (r < 0) return r; } } else { @@ -136,6 +142,11 @@ int socket_address_listen( if (listen(fd, backlog) < 0) return -errno; + /* Let's trigger an inotify event on the socket node, so that anyone waiting for this socket to be connectable + * gets notified */ + if (p) + (void) touch(p); + r = fd; fd = -1; diff --git a/src/basic/socket-protocol-list.c b/src/basic/socket-protocol-list.c new file mode 100644 index 0000000000..9ab93d1c7e --- /dev/null +++ b/src/basic/socket-protocol-list.c @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <netinet/in.h> +#include <string.h> + +#include "socket-protocol-list.h" +#include "macro.h" + +static const struct socket_protocol_name* lookup_socket_protocol(register const char *str, register GPERF_LEN_TYPE len); + +#include "socket-protocol-from-name.h" +#include "socket-protocol-to-name.h" + +const char *socket_protocol_to_name(int id) { + + if (id < 0) + return NULL; + + if (id >= (int) ELEMENTSOF(socket_protocol_names)) + return NULL; + + return socket_protocol_names[id]; +} + +int socket_protocol_from_name(const char *name) { + const struct socket_protocol_name *sc; + + assert(name); + + sc = lookup_socket_protocol(name, strlen(name)); + if (!sc) + return 0; + + return sc->id; +} + +int socket_protocol_max(void) { + return ELEMENTSOF(socket_protocol_names); +} diff --git a/src/basic/socket-protocol-list.h b/src/basic/socket-protocol-list.h new file mode 100644 index 0000000000..12fd053382 --- /dev/null +++ b/src/basic/socket-protocol-list.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +const char *socket_protocol_to_name(int id); +int socket_protocol_from_name(const char *name); + +int socket_protocol_max(void); diff --git a/src/basic/socket-protocol-to-name.awk b/src/basic/socket-protocol-to-name.awk new file mode 100644 index 0000000000..4848a7631a --- /dev/null +++ b/src/basic/socket-protocol-to-name.awk @@ -0,0 +1,9 @@ +BEGIN{ + print "static const char* const socket_protocol_names[] = { " +} +!/HOPOPTS/ { + printf " [IPPROTO_%s] = \"%s\",\n", $1, tolower($1) +} +END{ + print "};" +} diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index a458fc2902..2c70cade14 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -41,6 +41,7 @@ #include "missing.h" #include "parse-util.h" #include "path-util.h" +#include "process-util.h" #include "socket-util.h" #include "string-table.h" #include "string-util.h" @@ -55,9 +56,19 @@ # define IDN_FLAGS 0 #endif +static const char* const socket_address_type_table[] = { + [SOCK_STREAM] = "Stream", + [SOCK_DGRAM] = "Datagram", + [SOCK_RAW] = "Raw", + [SOCK_RDM] = "ReliableDatagram", + [SOCK_SEQPACKET] = "SequentialPacket", + [SOCK_DCCP] = "DatagramCongestionControl", +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_address_type, int); + int socket_address_parse(SocketAddress *a, const char *s) { char *e, *n; - unsigned u; int r; assert(a); @@ -67,6 +78,8 @@ int socket_address_parse(SocketAddress *a, const char *s) { a->type = SOCK_STREAM; if (*s == '[') { + uint16_t port; + /* IPv6 in [x:.....:z]:p notation */ e = strchr(s+1, ']'); @@ -84,15 +97,12 @@ int socket_address_parse(SocketAddress *a, const char *s) { return -EINVAL; e++; - r = safe_atou(e, &u); + r = parse_ip_port(e, &port); if (r < 0) return r; - if (u <= 0 || u > 0xFFFF) - return -EINVAL; - a->sockaddr.in6.sin6_family = AF_INET6; - a->sockaddr.in6.sin6_port = htobe16((uint16_t)u); + a->sockaddr.in6.sin6_port = htobe16(port); a->size = sizeof(struct sockaddr_in6); } else if (*s == '/') { @@ -123,12 +133,13 @@ int socket_address_parse(SocketAddress *a, const char *s) { } else if (startswith(s, "vsock:")) { /* AF_VSOCK socket in vsock:cid:port notation */ const char *cid_start = s + STRLEN("vsock:"); + unsigned port; e = strchr(cid_start, ':'); if (!e) return -EINVAL; - r = safe_atou(e+1, &u); + r = safe_atou(e+1, &port); if (r < 0) return r; @@ -141,19 +152,18 @@ int socket_address_parse(SocketAddress *a, const char *s) { a->sockaddr.vm.svm_cid = VMADDR_CID_ANY; a->sockaddr.vm.svm_family = AF_VSOCK; - a->sockaddr.vm.svm_port = u; + a->sockaddr.vm.svm_port = port; a->size = sizeof(struct sockaddr_vm); } else { + uint16_t port; + e = strchr(s, ':'); if (e) { - r = safe_atou(e+1, &u); + r = parse_ip_port(e + 1, &port); if (r < 0) return r; - if (u <= 0 || u > 0xFFFF) - return -EINVAL; - n = strndupa(s, e-s); /* IPv4 in w.x.y.z:p notation? */ @@ -164,7 +174,7 @@ int socket_address_parse(SocketAddress *a, const char *s) { if (r > 0) { /* Gotcha, it's a traditional IPv4 address */ a->sockaddr.in.sin_family = AF_INET; - a->sockaddr.in.sin_port = htobe16((uint16_t)u); + a->sockaddr.in.sin_port = htobe16(port); a->size = sizeof(struct sockaddr_in); } else { unsigned idx; @@ -178,7 +188,7 @@ int socket_address_parse(SocketAddress *a, const char *s) { return -EINVAL; a->sockaddr.in6.sin6_family = AF_INET6; - a->sockaddr.in6.sin6_port = htobe16((uint16_t)u); + a->sockaddr.in6.sin6_port = htobe16(port); a->sockaddr.in6.sin6_scope_id = idx; a->sockaddr.in6.sin6_addr = in6addr_any; a->size = sizeof(struct sockaddr_in6); @@ -186,21 +196,18 @@ int socket_address_parse(SocketAddress *a, const char *s) { } else { /* Just a port */ - r = safe_atou(s, &u); + r = parse_ip_port(s, &port); if (r < 0) return r; - if (u <= 0 || u > 0xFFFF) - return -EINVAL; - if (socket_ipv6_is_supported()) { a->sockaddr.in6.sin6_family = AF_INET6; - a->sockaddr.in6.sin6_port = htobe16((uint16_t)u); + a->sockaddr.in6.sin6_port = htobe16(port); a->sockaddr.in6.sin6_addr = in6addr_any; a->size = sizeof(struct sockaddr_in6); } else { a->sockaddr.in.sin_family = AF_INET; - a->sockaddr.in.sin_port = htobe16((uint16_t)u); + a->sockaddr.in.sin_port = htobe16(port); a->sockaddr.in.sin_addr.s_addr = INADDR_ANY; a->size = sizeof(struct sockaddr_in); } @@ -528,22 +535,25 @@ bool socket_address_matches_fd(const SocketAddress *a, int fd) { return socket_address_equal(a, &b); } -int sockaddr_port(const struct sockaddr *_sa, unsigned *port) { +int sockaddr_port(const struct sockaddr *_sa, unsigned *ret_port) { union sockaddr_union *sa = (union sockaddr_union*) _sa; + /* Note, this returns the port as 'unsigned' rather than 'uint16_t', as AF_VSOCK knows larger ports */ + assert(sa); switch (sa->sa.sa_family) { + case AF_INET: - *port = be16toh(sa->in.sin_port); + *ret_port = be16toh(sa->in.sin_port); return 0; case AF_INET6: - *port = be16toh(sa->in6.sin6_port); + *ret_port = be16toh(sa->in6.sin6_port); return 0; case AF_VSOCK: - *port = sa->vm.svm_port; + *ret_port = sa->vm.svm_port; return 0; default: @@ -807,6 +817,18 @@ static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIN DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); +SocketAddressBindIPv6Only parse_socket_address_bind_ipv6_only_or_bool(const char *n) { + int r; + + r = parse_boolean(n); + if (r > 0) + return SOCKET_ADDRESS_IPV6_ONLY; + if (r == 0) + return SOCKET_ADDRESS_BOTH; + + return socket_address_bind_ipv6_only_from_string(n); +} + bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b) { assert(a); assert(b); @@ -941,58 +963,80 @@ int getpeercred(int fd, struct ucred *ucred) { if (n != sizeof(struct ucred)) return -EIO; - /* Check if the data is actually useful and not suppressed due - * to namespacing issues */ - if (u.pid <= 0) - return -ENODATA; - if (u.uid == UID_INVALID) - return -ENODATA; - if (u.gid == GID_INVALID) + /* Check if the data is actually useful and not suppressed due to namespacing issues */ + if (!pid_is_valid(u.pid)) return -ENODATA; + /* Note that we don't check UID/GID here, as namespace translation works differently there: instead of + * receiving in "invalid" user/group we get the overflow UID/GID. */ + *ucred = u; return 0; } int getpeersec(int fd, char **ret) { + _cleanup_free_ char *s = NULL; socklen_t n = 64; - char *s; - int r; assert(fd >= 0); assert(ret); - s = new0(char, n); - if (!s) - return -ENOMEM; + for (;;) { + s = new0(char, n+1); + if (!s) + return -ENOMEM; - r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); - if (r < 0) { - free(s); + if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0) + break; if (errno != ERANGE) return -errno; - s = new0(char, n); - if (!s) - return -ENOMEM; - - r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); - if (r < 0) { - free(s); - return -errno; - } + s = mfree(s); } - if (isempty(s)) { - free(s); + if (isempty(s)) return -EOPNOTSUPP; - } *ret = s; + s = NULL; + return 0; } +int getpeergroups(int fd, gid_t **ret) { + socklen_t n = sizeof(gid_t) * 64; + _cleanup_free_ gid_t *d = NULL; + + assert(fd); + assert(ret); + + for (;;) { + d = malloc(n); + if (!d) + return -ENOMEM; + + if (getsockopt(fd, SOL_SOCKET, SO_PEERGROUPS, d, &n) >= 0) + break; + + if (errno != ERANGE) + return -errno; + + d = mfree(d); + } + + assert_se(n % sizeof(gid_t) == 0); + n /= sizeof(gid_t); + + if ((socklen_t) (int) n != n) + return -E2BIG; + + *ret = d; + d = NULL; + + return (int) n; +} + int send_one_fd_sa( int transport_fd, int fd, diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index 272e74b0cc..49c937aef5 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -36,16 +36,26 @@ #include "util.h" union sockaddr_union { + /* The minimal, abstract version */ struct sockaddr sa; + + /* The libc provided version that allocates "enough room" for every protocol */ + struct sockaddr_storage storage; + + /* Protoctol-specific implementations */ struct sockaddr_in in; struct sockaddr_in6 in6; struct sockaddr_un un; struct sockaddr_nl nl; - struct sockaddr_storage storage; struct sockaddr_ll ll; struct sockaddr_vm vm; + /* Ensure there is enough space to store Infiniband addresses */ uint8_t ll_buffer[offsetof(struct sockaddr_ll, sll_addr) + CONST_MAX(ETH_ALEN, INFINIBAND_ALEN)]; + + /* Ensure there is enough space after the AF_UNIX sun_path for one more NUL byte, just to be sure that the path + * component is always followed by at least one NUL byte. */ + uint8_t un_buffer[sizeof(struct sockaddr_un) + 1]; }; typedef struct SocketAddress { @@ -72,6 +82,9 @@ typedef enum SocketAddressBindIPv6Only { #define socket_address_family(a) ((a)->sockaddr.sa.sa_family) +const char* socket_address_type_to_string(int t) _const_; +int socket_address_type_from_string(const char *s) _pure_; + int socket_address_parse(SocketAddress *a, const char *s); int socket_address_parse_and_warn(SocketAddress *a, const char *s); int socket_address_parse_netlink(SocketAddress *a, const char *s); @@ -117,6 +130,7 @@ int getnameinfo_pretty(int fd, char **ret); const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_; SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_; +SocketAddressBindIPv6Only parse_socket_address_bind_ipv6_only_or_bool(const char *s); int netlink_family_to_string_alloc(int b, char **s); int netlink_family_from_string(const char *s) _pure_; @@ -134,6 +148,7 @@ bool address_label_valid(const char *p); int getpeercred(int fd, struct ucred *ucred); int getpeersec(int fd, char **ret); +int getpeergroups(int fd, gid_t **ret); int send_one_fd_sa(int transport_fd, int fd, diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 96fc8b3787..3a54103f1b 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -21,10 +21,11 @@ #include <dirent.h> #include <errno.h> #include <fcntl.h> -#include <sys/stat.h> -#include <sys/types.h> #include <linux/magic.h> +#include <sched.h> +#include <sys/stat.h> #include <sys/statvfs.h> +#include <sys/types.h> #include <unistd.h> #include "dirent-util.h" diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 7e2f596edc..9f2c01d864 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -30,6 +30,7 @@ #include "gunicode.h" #include "macro.h" #include "string-util.h" +#include "terminal-util.h" #include "utf8.h" #include "util.h" @@ -648,7 +649,17 @@ char *strreplace(const char *text, const char *old_string, const char *new_strin return ret; } -char *strip_tab_ansi(char **ibuf, size_t *_isz) { +static void advance_offsets(ssize_t diff, size_t offsets[2], size_t shift[2], size_t size) { + if (!offsets) + return; + + if ((size_t) diff < offsets[0]) + shift[0] += size; + if ((size_t) diff < offsets[1]) + shift[1] += size; +} + +char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { const char *i, *begin = NULL; enum { STATE_OTHER, @@ -656,7 +667,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) { STATE_BRACKET } state = STATE_OTHER; char *obuf = NULL; - size_t osz = 0, isz; + size_t osz = 0, isz, shift[2] = {}; FILE *f; assert(ibuf); @@ -684,15 +695,18 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) { break; else if (*i == '\x1B') state = STATE_ESCAPE; - else if (*i == '\t') + else if (*i == '\t') { fputs(" ", f); - else + advance_offsets(i - *ibuf, highlight, shift, 7); + } else fputc(*i, f); + break; case STATE_ESCAPE: if (i >= *ibuf + isz) { /* EOT */ fputc('\x1B', f); + advance_offsets(i - *ibuf, highlight, shift, 1); break; } else if (*i == '[') { state = STATE_BRACKET; @@ -700,6 +714,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) { } else { fputc('\x1B', f); fputc(*i, f); + advance_offsets(i - *ibuf, highlight, shift, 1); state = STATE_OTHER; } @@ -711,6 +726,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) { (!(*i >= '0' && *i <= '9') && !IN_SET(*i, ';', 'm'))) { fputc('\x1B', f); fputc('[', f); + advance_offsets(i - *ibuf, highlight, shift, 2); state = STATE_OTHER; i = begin-1; } else if (*i == 'm') @@ -732,6 +748,11 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) { if (_isz) *_isz = osz; + if (highlight) { + highlight[0] += shift[0]; + highlight[1] += shift[1]; + } + return obuf; } diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 09a737ad37..08eda4fce0 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -52,15 +52,15 @@ static inline bool streq_ptr(const char *a, const char *b) { } static inline const char* strempty(const char *s) { - return s ? s : ""; + return s ?: ""; } static inline const char* strnull(const char *s) { - return s ? s : "(null)"; + return s ?: "(null)"; } static inline const char *strna(const char *s) { - return s ? s : "n/a"; + return s ?: "n/a"; } static inline bool isempty(const char *p) { @@ -177,7 +177,7 @@ char* strshorten(char *s, size_t l); char *strreplace(const char *text, const char *old_string, const char *new_string); -char *strip_tab_ansi(char **p, size_t *l); +char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]); char *strextend_with_separator(char **x, const char *separator, ...) _sentinel_; diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 48ee799ad4..42336e8fdf 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -875,31 +875,30 @@ bool on_tty(void) { } int make_stdio(int fd) { - int r, s, t; + int r = 0; assert(fd >= 0); - r = dup2(fd, STDIN_FILENO); - s = dup2(fd, STDOUT_FILENO); - t = dup2(fd, STDERR_FILENO); + if (dup2(fd, STDIN_FILENO) < 0 && r >= 0) + r = -errno; + if (dup2(fd, STDOUT_FILENO) < 0 && r >= 0) + r = -errno; + if (dup2(fd, STDERR_FILENO) < 0 && r >= 0) + r = -errno; if (fd >= 3) safe_close(fd); - if (r < 0 || s < 0 || t < 0) - return -errno; - - /* Explicitly unset O_CLOEXEC, since if fd was < 3, then - * dup2() was a NOP and the bit hence possibly set. */ + /* Explicitly unset O_CLOEXEC, since if fd was < 3, then dup2() was a NOP and the bit hence possibly set. */ stdio_unset_cloexec(); - return 0; + return r; } int make_null_stdio(void) { int null_fd; - null_fd = open("/dev/null", O_RDWR|O_NOCTTY); + null_fd = open("/dev/null", O_RDWR|O_NOCTTY|O_CLOEXEC); if (null_fd < 0) return -errno; @@ -1094,7 +1093,6 @@ int ptsname_namespace(int pty, char **ret) { int openpt_in_namespace(pid_t pid, int flags) { _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; _cleanup_close_pair_ int pair[2] = { -1, -1 }; - siginfo_t si; pid_t child; int r; @@ -1107,11 +1105,10 @@ int openpt_in_namespace(pid_t pid, int flags) { if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) return -errno; - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { + r = safe_fork("(sd-openpt)", FORK_RESET_SIGNALS|FORK_DEATHSIG, &child); + if (r < 0) + return r; + if (r == 0) { int master; pair[0] = safe_close(pair[0]); @@ -1135,10 +1132,10 @@ int openpt_in_namespace(pid_t pid, int flags) { pair[1] = safe_close(pair[1]); - r = wait_for_terminate(child, &si); + r = wait_for_terminate_and_check("(sd-openpt)", child, 0); if (r < 0) return r; - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + if (r != EXIT_SUCCESS) return -EIO; return receive_one_fd(pair[0], 0); @@ -1147,7 +1144,6 @@ int openpt_in_namespace(pid_t pid, int flags) { int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; _cleanup_close_pair_ int pair[2] = { -1, -1 }; - siginfo_t si; pid_t child; int r; @@ -1158,11 +1154,10 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) return -errno; - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { + r = safe_fork("(sd-terminal)", FORK_RESET_SIGNALS|FORK_DEATHSIG, &child); + if (r < 0) + return r; + if (r == 0) { int master; pair[0] = safe_close(pair[0]); @@ -1183,10 +1178,10 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { pair[1] = safe_close(pair[1]); - r = wait_for_terminate(child, &si); + r = wait_for_terminate_and_check("(sd-terminal)", child, 0); if (r < 0) return r; - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + if (r != EXIT_SUCCESS) return -EIO; return receive_one_fd(pair[0], 0); diff --git a/src/basic/time-util.c b/src/basic/time-util.c index d56576ddbe..4a341e208f 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -38,6 +38,7 @@ #include "macro.h" #include "parse-util.h" #include "path-util.h" +#include "process-util.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -887,7 +888,6 @@ int parse_timestamp(const char *t, usec_t *usec) { char *last_space, *tz = NULL; ParseTimestampResult *shared, tmp; int r; - pid_t pid; last_space = strrchr(t, ' '); if (last_space != NULL && timezone_is_valid(last_space + 1)) @@ -900,15 +900,12 @@ int parse_timestamp(const char *t, usec_t *usec) { if (shared == MAP_FAILED) return negative_errno(); - pid = fork(); - - if (pid == -1) { - int fork_errno = errno; + r = safe_fork("(sd-timestamp)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL); + if (r < 0) { (void) munmap(shared, sizeof *shared); - return -fork_errno; + return r; } - - if (pid == 0) { + if (r == 0) { bool with_tz = true; if (setenv("TZ", tz, 1) != 0) { @@ -931,12 +928,6 @@ int parse_timestamp(const char *t, usec_t *usec) { _exit(EXIT_SUCCESS); } - r = wait_for_terminate(pid, NULL); - if (r < 0) { - (void) munmap(shared, sizeof *shared); - return r; - } - tmp = *shared; if (munmap(shared, sizeof *shared) != 0) return negative_errno(); diff --git a/src/basic/unaligned.h b/src/basic/unaligned.h index 201b3d227e..73302b4239 100644 --- a/src/basic/unaligned.h +++ b/src/basic/unaligned.h @@ -26,89 +26,77 @@ /* BE */ static inline uint16_t unaligned_read_be16(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; - return (((uint16_t) u[0]) << 8) | - ((uint16_t) u[1]); + return be16toh(u->x); } static inline uint32_t unaligned_read_be32(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; - return (((uint32_t) unaligned_read_be16(u)) << 16) | - ((uint32_t) unaligned_read_be16(u + 2)); + return be32toh(u->x); } static inline uint64_t unaligned_read_be64(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; - return (((uint64_t) unaligned_read_be32(u)) << 32) | - ((uint64_t) unaligned_read_be32(u + 4)); + return be64toh(u->x); } static inline void unaligned_write_be16(void *_u, uint16_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; - u[0] = (uint8_t) (a >> 8); - u[1] = (uint8_t) a; + u->x = be16toh(a); } static inline void unaligned_write_be32(void *_u, uint32_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; - unaligned_write_be16(u, (uint16_t) (a >> 16)); - unaligned_write_be16(u + 2, (uint16_t) a); + u->x = be32toh(a); } static inline void unaligned_write_be64(void *_u, uint64_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; - unaligned_write_be32(u, (uint32_t) (a >> 32)); - unaligned_write_be32(u + 4, (uint32_t) a); + u->x = be64toh(a); } /* LE */ static inline uint16_t unaligned_read_le16(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; - return (((uint16_t) u[1]) << 8) | - ((uint16_t) u[0]); + return le16toh(u->x); } static inline uint32_t unaligned_read_le32(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; - return (((uint32_t) unaligned_read_le16(u + 2)) << 16) | - ((uint32_t) unaligned_read_le16(u)); + return le32toh(u->x); } static inline uint64_t unaligned_read_le64(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; - return (((uint64_t) unaligned_read_le32(u + 4)) << 32) | - ((uint64_t) unaligned_read_le32(u)); + return le64toh(u->x); } static inline void unaligned_write_le16(void *_u, uint16_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; - u[0] = (uint8_t) a; - u[1] = (uint8_t) (a >> 8); + u->x = le16toh(a); } static inline void unaligned_write_le32(void *_u, uint32_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; - unaligned_write_le16(u, (uint16_t) a); - unaligned_write_le16(u + 2, (uint16_t) (a >> 16)); + u->x = le32toh(a); } static inline void unaligned_write_le64(void *_u, uint64_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; - unaligned_write_le32(u, (uint32_t) a); - unaligned_write_le32(u + 4, (uint32_t) (a >> 32)); + u->x = le64toh(a); } #if __BYTE_ORDER == __BIG_ENDIAN diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c index 403f288b57..0fa0472ee1 100644 --- a/src/basic/unit-name.c +++ b/src/basic/unit-name.c @@ -28,6 +28,7 @@ #include "glob-util.h" #include "hexdecoct.h" #include "path-util.h" +#include "special.h" #include "string-util.h" #include "strv.h" #include "unit-name.h" @@ -673,7 +674,7 @@ int slice_build_parent_slice(const char *slice, char **ret) { if (!slice_name_is_valid(slice)) return -EINVAL; - if (streq(slice, "-.slice")) { + if (streq(slice, SPECIAL_ROOT_SLICE)) { *ret = NULL; return 0; } @@ -686,7 +687,7 @@ int slice_build_parent_slice(const char *slice, char **ret) { if (dash) strcpy(dash, ".slice"); else { - r = free_and_strdup(&s, "-.slice"); + r = free_and_strdup(&s, SPECIAL_ROOT_SLICE); if (r < 0) { free(s); return r; @@ -710,7 +711,7 @@ int slice_build_subslice(const char *slice, const char*name, char **ret) { if (!unit_prefix_is_valid(name)) return -EINVAL; - if (streq(slice, "-.slice")) + if (streq(slice, SPECIAL_ROOT_SLICE)) subslice = strappend(name, ".slice"); else { char *e; @@ -735,7 +736,7 @@ bool slice_name_is_valid(const char *name) { if (!unit_name_is_valid(name, UNIT_NAME_PLAIN)) return false; - if (streq(name, "-.slice")) + if (streq(name, SPECIAL_ROOT_SLICE)) return true; e = endswith(name, ".slice"); diff --git a/src/basic/user-util.c b/src/basic/user-util.c index abb0b76866..17a9b5a8f1 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -137,7 +137,8 @@ int get_user_creds( return 0; } - if (STR_IN_SET(*username, NOBODY_USER_NAME, "65534")) { + if (synthesize_nobody() && + STR_IN_SET(*username, NOBODY_USER_NAME, "65534")) { *username = NOBODY_USER_NAME; if (uid) @@ -243,7 +244,8 @@ int get_group_creds(const char **groupname, gid_t *gid) { return 0; } - if (STR_IN_SET(*groupname, NOBODY_GROUP_NAME, "65534")) { + if (synthesize_nobody() && + STR_IN_SET(*groupname, NOBODY_GROUP_NAME, "65534")) { *groupname = NOBODY_GROUP_NAME; if (gid) @@ -283,7 +285,8 @@ char* uid_to_name(uid_t uid) { /* Shortcut things to avoid NSS lookups */ if (uid == 0) return strdup("root"); - if (uid == UID_NOBODY) + if (synthesize_nobody() && + uid == UID_NOBODY) return strdup(NOBODY_USER_NAME); if (uid_is_valid(uid)) { @@ -323,7 +326,8 @@ char* gid_to_name(gid_t gid) { if (gid == 0) return strdup("root"); - if (gid == GID_NOBODY) + if (synthesize_nobody() && + gid == GID_NOBODY) return strdup(NOBODY_GROUP_NAME); if (gid_is_valid(gid)) { @@ -358,8 +362,9 @@ char* gid_to_name(gid_t gid) { } int in_gid(gid_t gid) { + long ngroups_max; gid_t *gids; - int ngroups_max, r, i; + int r, i; if (getgid() == gid) return 1; @@ -373,7 +378,7 @@ int in_gid(gid_t gid) { ngroups_max = sysconf(_SC_NGROUPS_MAX); assert(ngroups_max > 0); - gids = alloca(sizeof(gid_t) * ngroups_max); + gids = newa(gid_t, ngroups_max); r = getgroups(ngroups_max, gids); if (r < 0) @@ -426,7 +431,8 @@ int get_home_dir(char **_h) { *_h = h; return 0; } - if (u == UID_NOBODY) { + if (synthesize_nobody() && + u == UID_NOBODY) { h = strdup("/"); if (!h) return -ENOMEM; @@ -481,7 +487,8 @@ int get_shell(char **_s) { *_s = s; return 0; } - if (u == UID_NOBODY) { + if (synthesize_nobody() && + u == UID_NOBODY) { s = strdup("/sbin/nologin"); if (!s) return -ENOMEM; @@ -689,3 +696,24 @@ int maybe_setgroups(size_t size, const gid_t *list) { return 0; } + +bool synthesize_nobody(void) { + +#ifdef NOLEGACY + return true; +#else + /* Returns true when we shall synthesize the "nobody" user (which we do by default). This can be turned off by + * touching /etc/systemd/dont-synthesize-nobody in order to provide upgrade compatibility with legacy systems + * that used the "nobody" user name and group name for other UIDs/GIDs than 65534. + * + * Note that we do not employ any kind of synchronization on the following caching variable. If the variable is + * accessed in multi-threaded programs in the worst case it might happen that we initialize twice, but that + * shouldn't matter as each initialization should come to the same result. */ + static int cache = -1; + + if (cache < 0) + cache = access("/etc/systemd/dont-synthesize-nobody", F_OK) < 0; + + return cache; +#endif +} diff --git a/src/basic/user-util.h b/src/basic/user-util.h index 79adf91ee9..5f0391f2b8 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -97,3 +97,5 @@ bool valid_gecos(const char *d); bool valid_home(const char *p); int maybe_setgroups(size_t size, const gid_t *list); + +bool synthesize_nobody(void); diff --git a/src/basic/util.c b/src/basic/util.c index 8f9f2b902b..c7f1513f3e 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -52,6 +52,7 @@ #include "parse-util.h" #include "path-util.h" #include "process-util.h" +#include "procfs-util.h" #include "set.h" #include "signal-util.h" #include "stat-util.h" @@ -61,6 +62,7 @@ #include "umask-util.h" #include "user-util.h" #include "util.h" +#include "virt.h" int saved_argc = 0; char **saved_argv = NULL; @@ -118,45 +120,6 @@ int socket_from_display(const char *display, char **path) { return 0; } -int block_get_whole_disk(dev_t d, dev_t *ret) { - char p[SYS_BLOCK_PATH_MAX("/partition")]; - _cleanup_free_ char *s = NULL; - int r; - unsigned n, m; - - assert(ret); - - /* If it has a queue this is good enough for us */ - xsprintf_sys_block_path(p, "/queue", d); - if (access(p, F_OK) >= 0) { - *ret = d; - return 0; - } - - /* If it is a partition find the originating device */ - xsprintf_sys_block_path(p, "/partition", d); - if (access(p, F_OK) < 0) - return -ENOENT; - - /* Get parent dev_t */ - xsprintf_sys_block_path(p, "/../dev", d); - r = read_one_line_file(p, &s); - if (r < 0) - return r; - - r = sscanf(s, "%u:%u", &m, &n); - if (r != 2) - return -EINVAL; - - /* Only return this if it is really good enough for us. */ - xsprintf_sys_block_path(p, "/queue", makedev(m, n)); - if (access(p, F_OK) < 0) - return -ENOENT; - - *ret = makedev(m, n); - return 0; -} - bool kexec_loaded(void) { _cleanup_free_ char *s = NULL; @@ -184,112 +147,6 @@ int prot_from_flags(int flags) { } } -int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) { - bool stdout_is_tty, stderr_is_tty; - pid_t parent_pid, agent_pid; - sigset_t ss, saved_ss; - unsigned n, i; - va_list ap; - char **l; - - assert(pid); - assert(path); - - /* Spawns a temporary TTY agent, making sure it goes away when - * we go away */ - - parent_pid = getpid_cached(); - - /* First we temporarily block all signals, so that the new - * child has them blocked initially. This way, we can be sure - * that SIGTERMs are not lost we might send to the agent. */ - assert_se(sigfillset(&ss) >= 0); - assert_se(sigprocmask(SIG_SETMASK, &ss, &saved_ss) >= 0); - - agent_pid = fork(); - if (agent_pid < 0) { - assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); - return -errno; - } - - if (agent_pid != 0) { - assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); - *pid = agent_pid; - return 0; - } - - /* In the child: - * - * Make sure the agent goes away when the parent dies */ - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) - _exit(EXIT_FAILURE); - - /* Make sure we actually can kill the agent, if we need to, in - * case somebody invoked us from a shell script that trapped - * SIGTERM or so... */ - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - /* Check whether our parent died before we were able - * to set the death signal and unblock the signals */ - if (getppid() != parent_pid) - _exit(EXIT_SUCCESS); - - /* Don't leak fds to the agent */ - close_all_fds(except, n_except); - - stdout_is_tty = isatty(STDOUT_FILENO); - stderr_is_tty = isatty(STDERR_FILENO); - - if (!stdout_is_tty || !stderr_is_tty) { - int fd; - - /* Detach from stdout/stderr. and reopen - * /dev/tty for them. This is important to - * ensure that when systemctl is started via - * popen() or a similar call that expects to - * read EOF we actually do generate EOF and - * not delay this indefinitely by because we - * keep an unused copy of stdin around. */ - fd = open("/dev/tty", O_WRONLY); - if (fd < 0) { - log_error_errno(errno, "Failed to open /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - if (!stdout_is_tty && dup2(fd, STDOUT_FILENO) < 0) { - log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - if (!stderr_is_tty && dup2(fd, STDERR_FILENO) < 0) { - log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - if (fd > STDERR_FILENO) - close(fd); - } - - /* Count arguments */ - va_start(ap, path); - for (n = 0; va_arg(ap, char*); n++) - ; - va_end(ap); - - /* Allocate strv */ - l = alloca(sizeof(char *) * (n + 1)); - - /* Fill in arguments */ - va_start(ap, path); - for (i = 0; i <= n; i++) - l[i] = va_arg(ap, char*); - va_end(ap); - - execv(path, l); - _exit(EXIT_FAILURE); -} - bool in_initrd(void) { struct statfs s; @@ -617,31 +474,22 @@ uint64_t physical_memory_scale(uint64_t v, uint64_t max) { uint64_t system_tasks_max(void) { -#if SIZEOF_PID_T == 4 -#define TASKS_MAX ((uint64_t) (INT32_MAX-1)) -#elif SIZEOF_PID_T == 2 -#define TASKS_MAX ((uint64_t) (INT16_MAX-1)) -#else -#error "Unknown pid_t size" -#endif - - _cleanup_free_ char *value = NULL, *root = NULL; uint64_t a = TASKS_MAX, b = TASKS_MAX; + _cleanup_free_ char *root = NULL; /* Determine the maximum number of tasks that may run on this system. We check three sources to determine this * limit: * - * a) the maximum value for the pid_t type + * a) the maximum tasks value the kernel allows on this architecture * b) the cgroups pids_max attribute for the system - * c) the kernel's configure maximum PID value + * c) the kernel's configured maximum PID value * * And then pick the smallest of the three */ - if (read_one_line_file("/proc/sys/kernel/pid_max", &value) >= 0) - (void) safe_atou64(value, &a); + (void) procfs_tasks_get_limit(&a); if (cg_get_root_path(&root) >= 0) { - value = mfree(value); + _cleanup_free_ char *value = NULL; if (cg_get_attribute("pids", root, "pids.max", &value) >= 0) (void) safe_atou64(value, &b); @@ -699,131 +547,76 @@ int version(void) { return 0; } -int get_block_device(const char *path, dev_t *dev) { - struct stat st; - struct statfs sfs; - - assert(path); - assert(dev); - - /* Get's the block device directly backing a file system. If - * the block device is encrypted, returns the device mapper - * block device. */ - - if (lstat(path, &st)) - return -errno; - - if (major(st.st_dev) != 0) { - *dev = st.st_dev; - return 1; - } - - if (statfs(path, &sfs) < 0) - return -errno; - - if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) - return btrfs_get_block_device(path, dev); - - return 0; +/* This is a direct translation of str_verscmp from boot.c */ +static bool is_digit(int c) { + return c >= '0' && c <= '9'; } -int get_block_device_harder(const char *path, dev_t *dev) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *t = NULL; - char p[SYS_BLOCK_PATH_MAX("/slaves")]; - struct dirent *de, *found = NULL; - const char *q; - unsigned maj, min; - dev_t dt; - int r; - - assert(path); - assert(dev); +static int c_order(int c) { + if (c == 0 || is_digit(c)) + return 0; - /* Gets the backing block device for a file system, and - * handles LUKS encrypted file systems, looking for its - * immediate parent, if there is one. */ + if ((c >= 'a') && (c <= 'z')) + return c; - r = get_block_device(path, &dt); - if (r <= 0) - return r; + return c + 0x10000; +} - xsprintf_sys_block_path(p, "/slaves", dt); - d = opendir(p); - if (!d) { - if (errno == ENOENT) - goto fallback; +int str_verscmp(const char *s1, const char *s2) { + const char *os1, *os2; - return -errno; - } + assert(s1); + assert(s2); - FOREACH_DIRENT_ALL(de, d, return -errno) { + os1 = s1; + os2 = s2; - if (dot_or_dot_dot(de->d_name)) - continue; + while (*s1 || *s2) { + int first; - if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN)) - continue; + while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) { + int order; - if (found) { - _cleanup_free_ char *u = NULL, *v = NULL, *a = NULL, *b = NULL; - - /* We found a device backed by multiple other devices. We don't really support automatic - * discovery on such setups, with the exception of dm-verity partitions. In this case there are - * two backing devices: the data partition and the hash partition. We are fine with such - * setups, however, only if both partitions are on the same physical device. Hence, let's - * verify this. */ - - u = strjoin(p, "/", de->d_name, "/../dev"); - if (!u) - return -ENOMEM; - - v = strjoin(p, "/", found->d_name, "/../dev"); - if (!v) - return -ENOMEM; - - r = read_one_line_file(u, &a); - if (r < 0) { - log_debug_errno(r, "Failed to read %s: %m", u); - goto fallback; - } - - r = read_one_line_file(v, &b); - if (r < 0) { - log_debug_errno(r, "Failed to read %s: %m", v); - goto fallback; - } - - /* Check if the parent device is the same. If not, then the two backing devices are on - * different physical devices, and we don't support that. */ - if (!streq(a, b)) - goto fallback; + order = c_order(*s1) - c_order(*s2); + if (order != 0) + return order; + s1++; + s2++; } - found = de; - } - - if (!found) - goto fallback; + while (*s1 == '0') + s1++; + while (*s2 == '0') + s2++; + + first = 0; + while (is_digit(*s1) && is_digit(*s2)) { + if (first == 0) + first = *s1 - *s2; + s1++; + s2++; + } - q = strjoina(p, "/", found->d_name, "/dev"); + if (is_digit(*s1)) + return 1; + if (is_digit(*s2)) + return -1; - r = read_one_line_file(q, &t); - if (r == -ENOENT) - goto fallback; - if (r < 0) - return r; + if (first != 0) + return first; + } - if (sscanf(t, "%u:%u", &maj, &min) != 2) - return -EINVAL; + return strcmp(os1, os2); +} - if (maj == 0) - goto fallback; +/* Turn off core dumps but only if we're running outside of a container. */ +void disable_coredumps(void) { + int r; - *dev = makedev(maj, min); - return 1; + if (detect_container() > 0) + return; -fallback: - *dev = dt; - return 1; + r = write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0); + if (r < 0) + log_debug_errno(r, "Failed to turn off coredumps, ignoring: %m"); } diff --git a/src/basic/util.h b/src/basic/util.h index a79907de3e..9d1b10756b 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -71,8 +71,6 @@ bool plymouth_running(void); bool display_is_local(const char *display) _pure_; int socket_from_display(const char *display, char **path); -int block_get_whole_disk(dev_t d, dev_t *ret); - #define NULSTR_FOREACH(i, l) \ for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1) @@ -86,8 +84,6 @@ bool kexec_loaded(void); int prot_from_flags(int flags) _const_; -int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...); - bool in_initrd(void); void in_initrd_force(bool value); @@ -194,5 +190,6 @@ int update_reboot_parameter_and_warn(const char *param); int version(void); -int get_block_device(const char *path, dev_t *dev); -int get_block_device_harder(const char *path, dev_t *dev); +int str_verscmp(const char *s1, const char *s2); + +void disable_coredumps(void); diff --git a/src/basic/verbs.c b/src/basic/verbs.c index cb42e6dd08..47644670da 100644 --- a/src/basic/verbs.c +++ b/src/basic/verbs.c @@ -22,13 +22,48 @@ #include <getopt.h> #include <stdbool.h> #include <stddef.h> +#include <string.h> +#include "env-util.h" #include "log.h" #include "macro.h" +#include "process-util.h" #include "string-util.h" #include "verbs.h" #include "virt.h" +/* Wraps running_in_chroot() which is used in various places, but also adds an environment variable check so external + * processes can reliably force this on. + */ +bool running_in_chroot_or_offline(void) { + int r; + + /* Added to support use cases like rpm-ostree, where from %post scripts we only want to execute "preset", but + * not "start"/"restart" for example. + * + * See ENVIRONMENT.md for docs. + */ + r = getenv_bool("SYSTEMD_OFFLINE"); + if (r < 0 && r != -ENXIO) + log_debug_errno(r, "Failed to parse $SYSTEMD_OFFLINE: %m"); + else if (r >= 0) + return r > 0; + + /* We've had this condition check for a long time which basically checks for legacy chroot case like Fedora's + * "mock", which is used for package builds. We don't want to try to start systemd services there, since + * without --new-chroot we don't even have systemd running, and even if we did, adding a concept of background + * daemons to builds would be an enormous change, requiring considering things like how the journal output is + * handled, etc. And there's really not a use case today for a build talking to a service. + * + * Note this call itself also looks for a different variable SYSTEMD_IGNORE_CHROOT=1. + */ + r = running_in_chroot(); + if (r < 0) + log_debug_errno(r, "running_in_chroot(): %m"); + + return r > 0; +} + int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { const Verb *verb; const char *name; @@ -84,12 +119,15 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { return -EINVAL; } - if ((verb->flags & VERB_NOCHROOT) && running_in_chroot() > 0) { - log_info("Running in chroot, ignoring request."); + if ((verb->flags & VERB_ONLINE_ONLY) && running_in_chroot_or_offline()) { + if (name) + log_info("Running in chroot, ignoring request: %s", name); + else + log_info("Running in chroot, ignoring request."); return 0; } - if (verb->flags & VERB_MUSTBEROOT) { + if (verb->flags & VERB_MUST_BE_ROOT) { r = must_be_root(); if (r < 0) return r; diff --git a/src/basic/verbs.h b/src/basic/verbs.h index 5f44a18f8e..d9259fc45f 100644 --- a/src/basic/verbs.h +++ b/src/basic/verbs.h @@ -23,9 +23,9 @@ #define VERB_ANY ((unsigned) -1) typedef enum VerbFlags { - VERB_DEFAULT = 1 << 0, - VERB_NOCHROOT = 1 << 1, - VERB_MUSTBEROOT = 1 << 2, + VERB_DEFAULT = 1 << 0, + VERB_ONLINE_ONLY = 1 << 1, + VERB_MUST_BE_ROOT = 1 << 2, } VerbFlags; typedef struct { @@ -35,4 +35,6 @@ typedef struct { int (* const dispatch)(int argc, char *argv[], void *userdata); } Verb; +bool running_in_chroot_or_offline(void); + int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); diff --git a/src/basic/virt.c b/src/basic/virt.c index b0db28add6..f4796b53bc 100644 --- a/src/basic/virt.c +++ b/src/basic/virt.c @@ -18,6 +18,9 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#if defined(__i386__) || defined(__x86_64__) +#include <cpuid.h> +#endif #include <errno.h> #include <stdint.h> #include <stdlib.h> @@ -56,30 +59,14 @@ static int detect_vm_cpuid(void) { { "bhyve bhyve ", VIRTUALIZATION_BHYVE }, }; - uint32_t eax, ecx; + uint32_t eax, ebx, ecx, edx; bool hypervisor; /* http://lwn.net/Articles/301888/ */ -#if defined (__i386__) -#define REG_a "eax" -#define REG_b "ebx" -#elif defined (__amd64__) -#define REG_a "rax" -#define REG_b "rbx" -#endif - /* First detect whether there is a hypervisor */ - eax = 1; - __asm__ __volatile__ ( - /* ebx/rbx is being used for PIC! */ - " push %%"REG_b" \n\t" - " cpuid \n\t" - " pop %%"REG_b" \n\t" - - : "=a" (eax), "=c" (ecx) - : "0" (eax) - ); + if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0) + return VIRTUALIZATION_NONE; hypervisor = !!(ecx & 0x80000000U); @@ -91,17 +78,11 @@ static int detect_vm_cpuid(void) { unsigned j; /* There is a hypervisor, see what it is */ - eax = 0x40000000U; - __asm__ __volatile__ ( - /* ebx/rbx is being used for PIC! */ - " push %%"REG_b" \n\t" - " cpuid \n\t" - " mov %%ebx, %1 \n\t" - " pop %%"REG_b" \n\t" - - : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2]) - : "0" (eax) - ); + __cpuid(0x40000000U, eax, ebx, ecx, edx); + + sig.sig32[0] = ebx; + sig.sig32[1] = ecx; + sig.sig32[2] = edx; log_debug("Virtualization found, CPUID=%s", sig.text); @@ -241,8 +222,10 @@ static int detect_vm_xen_dom0(void) { if (r == 0) { unsigned long features; - r = safe_atolu(domcap, &features); - if (r == 0) { + /* Here, we need to use sscanf() instead of safe_atoul() + * as the string lacks the leading "0x". */ + r = sscanf(domcap, "%lx", &features); + if (r == 1) { r = !!(features & (1U << XENFEAT_dom0)); log_debug("Virtualization XEN, found %s with value %08lx, " "XENFEAT_dom0 (indicating the 'hardware domain') is%s set.", |