diff --git a/NEWS b/NEWS index a5b539ee19..8e5ae59119 100644 --- a/NEWS +++ b/NEWS @@ -379,6 +379,15 @@ CHANGES WITH 255 in spe: * Options and verbs in man pages are now tagged with the version they were first introduced in. + * A new component "systemd-storagetm" has been added, which exposes all + local block devices as NVMe-TCP devices, fully automatically. It's + hooked into a new target unit storage-target-mode.target that is + suppsoed to be booted into via + rd.systemd.unit=storage-target-mode.target on the kernel command + line. This is intended to be used for installers and debugging to + quickly get access to the local disk. It's inspired by MacOS "target + disk mode". + Contributions from: 김인수, Abderrahim Kitouni, Adam Williamson, Alexandre Peixoto Ferreira, Alex Hudspith, Alvin Alvarado, André Paiusco, Antonio Alvarez Feijoo, Anton Lundin, diff --git a/TODO b/TODO index 3d30fb2982..021994a770 100644 --- a/TODO +++ b/TODO @@ -175,18 +175,18 @@ Features: * systemd-repart: add support for formatting dm-crypt + dm-integrity file systems. -* homed: add small tool that exposes a homed home dir via nvme-over-tcp (just a - bunch of sysfs writes). Then, teach homed/pam_systemd_homed with a user name - such as lennart%nvmettcp_192.168.100.77_8787_nqn to log in from any linux - host with the same home dir. Similar maybe for nbd, iscsi? this should then - first ask for the local root pw, to authenticate that logging in like this is - ok, and would then be followed by another password prompt asking for the - user's own password. Also, do something similar for CIFS: if you log in via +* homed: use systemd-storagetm to expose home dirs via nvme-tcp. Then, + teach homed/pam_systemd_homed with a user name such as + lennart%nvme_tcp_192.168.100.77_8787 to log in from any linux host with the + same home dir. Similar maybe for nbd, iscsi? this should then first ask for + the local root pw, to authenticate that logging in like this is ok, and would + then be followed by another password prompt asking for the user's own + password. Also, do something similar for CIFS: if you log in via lennart%cifs-someserver_someshare, then set up the homed dir for it - automatically. The PAM module should update the user name used for login to the - short version once it set up the user. Some care should be taken, so that the - long version can be still be resolved via NSS afterwards, to deal with PAM - clients that do not support PAM sessions where PAM_USER changes half-way. + automatically. The PAM module should update the user name used for login to + the short version once it set up the user. Some care should be taken, so that + the long version can be still be resolved via NSS afterwards, to deal with + PAM clients that do not support PAM sessions where PAM_USER changes half-way. * redefine /var/lib/extensions/ as the dir one can place all three of sysext, confext as well is multi-modal DDIs that qualify as both. Then introduce diff --git a/man/rules/meson.build b/man/rules/meson.build index 525bea94fd..45a1780fb2 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1045,6 +1045,7 @@ manpages = [ ['systemd-socket-proxyd', '8', [], ''], ['systemd-soft-reboot.service', '8', [], ''], ['systemd-stdio-bridge', '1', [], ''], + ['systemd-storagetm.service', '8', ['systemd-storagetm'], 'ENABLE_STORAGETM'], ['systemd-stub', '7', ['linuxaa64.efi.stub', 'linuxia32.efi.stub', 'linuxx64.efi.stub', 'sd-stub'], diff --git a/man/systemd-storagetm.service.xml b/man/systemd-storagetm.service.xml new file mode 100644 index 0000000000..4fa7958737 --- /dev/null +++ b/man/systemd-storagetm.service.xml @@ -0,0 +1,104 @@ + + + + + + + + systemd-storagetm.service + systemd + + + + systemd-storagetm.service + 8 + + + + systemd-storagetm.service + systemd-storagetm + Exposes all local block devices as NVMe-TCP mass storage devices + + + + systemd-storagetm.service + + + /usr/lib/systemd/systemd-storagetm + OPTIONS + DEVICE + + + + + Description + + systemd-storagetm.service is a service that exposes all local block devices as + NVMe-TCP mass storage devices. Its primary use-case is to be invoked by the + storage-target-mode.target unit that can be booted into. + + Warning: the NVMe disks are currently exposed without authentication or encryption, in read/write + mode. This means network peers may read from and write to the device without any restrictions. This + functionality should hence only be used in a local setup. + + Note that to function properly networking must be configured too. The recommended mechanism to boot + into a storage target mode is by adding rd.systemd.unit=storage-target-mode.target + ip=link-local on the kernel command line. Note that ip=link-local only + configures link-local IP, i.e. IPv4LL and IPv6LL, which means non-routable addresses. This is done for + security reasons, so that only systems on the local link can access the devices. Use + ip=dhcp to assign routable addresses too. For further details see + systemd-network-generator.service8. + + Unless the switch is used expects one or more block devices or regular files to expose + via NVMe-TCP as argument. + + + + Options + + The following options are understood: + + + + + Takes a string. If specified configures the NVMe Qualified Name to use for the + exposed NVMe-TCP mass storage devices. The NQN should follow the syntax described in NVM + Express Base Specification 2.0c, section 4.5 "NVMe Qualified Names". Note that the NQN + specified here will be suffixed with a dot and the the block device name before it is exposed on the + NVMe target. If not specified defaults to + nqn.2023-10.io.systemd:storagetm.ID, where ID is + replaced by a 128bit ID derived from + machine-id5. + + + + + + + + + If specified exposes all local block devices via NVMe-TCP, current and future + (i.e. it watches block devices come and go and updates the NVMe-TCP list as needed). Note that by + default any block devices that originate on the same block device as the block device backing the + current root file system are excluded. If the switch is specified twice this safety mechanism is + disabled. + + + + + + + + + + See Also + + systemd1, + systemd.special7 + + + + diff --git a/man/systemd.special.xml b/man/systemd.special.xml index 1d1796154e..8acad5c83e 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -83,6 +83,7 @@ sockets.target, soft-reboot.target, sound.target, + storage-target-mode.target, suspend.target, swap.target, sysinit.target, @@ -767,6 +768,18 @@ + + storage-target-mode.target + + A special target unit that can be booted into that selects the "Storage Target Mode" for + the OS. In this mode all local storage disks are exposed to external systems as block + devices. This invokes + systemd-storagetm.service8 + which exposes all local disks as NVMe-TCP devices for access over the network. It might as well + invoke other services too that make local disks available via other mechanisms. + + + suspend.target diff --git a/meson.build b/meson.build index 5f1331e6f3..5ac15d8356 100644 --- a/meson.build +++ b/meson.build @@ -1533,6 +1533,8 @@ have = get_option('sysupdate').require( error_message : 'fdisk and openssl required').allowed() conf.set10('ENABLE_SYSUPDATE', have) +conf.set10('ENABLE_STORAGETM', get_option('storagetm')) + have = get_option('importd').require( conf.get('HAVE_LIBCURL') == 1 and conf.get('HAVE_OPENSSL_OR_GCRYPT') == 1 and @@ -2196,10 +2198,11 @@ subdir('src/systemctl') subdir('src/sysupdate') subdir('src/sysusers') subdir('src/sysv-generator') +subdir('src/storagetm') subdir('src/timedate') subdir('src/timesync') -subdir('src/tpm2-setup') subdir('src/tmpfiles') +subdir('src/tpm2-setup') subdir('src/tty-ask-password-agent') subdir('src/update-done') subdir('src/update-utmp') @@ -2793,6 +2796,7 @@ foreach tuple : [ ['systemd-analyze', conf.get('ENABLE_ANALYZE') == 1], ['sysupdate'], ['sysusers'], + ['storagetm'], ['timedated'], ['timesyncd'], ['tmpfiles'], diff --git a/meson_options.txt b/meson_options.txt index 831d23e299..c31e3cd4d7 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -158,6 +158,8 @@ option('quotacheck', type : 'boolean', description : 'support for the quotacheck tools') option('sysusers', type : 'boolean', description : 'support for the sysusers configuration') +option('storagetm', type : 'boolean', + description : 'support for Storage Target Mode') option('tmpfiles', type : 'boolean', description : 'support for tmpfiles.d') option('importd', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index f2b606ea9b..32289cfd09 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -988,3 +988,11 @@ const char *accmode_to_string(int flags) { return NULL; } } + +char *format_proc_pid_fd_path(char buf[static PROC_PID_FD_PATH_MAX], pid_t pid, int fd) { + assert(buf); + assert(fd >= 0); + assert(pid >= 0); + assert_se(snprintf_ok(buf, PROC_PID_FD_PATH_MAX, "/proc/" PID_FMT "/fd/%i", pid == 0 ? getpid_cached() : pid, fd)); + return buf; +} diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index aba8f5f5af..5061e32196 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -131,6 +131,16 @@ static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int f #define FORMAT_PROC_FD_PATH(fd) \ format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd)) +/* The maximum length a buffer for a /proc//fd/ path needs */ +#define PROC_PID_FD_PATH_MAX \ + (STRLEN("/proc//fd/") + DECIMAL_STR_MAX(pid_t) + DECIMAL_STR_MAX(int)) + +char *format_proc_pid_fd_path(char buf[static PROC_PID_FD_PATH_MAX], pid_t pid, int fd); + +/* Kinda the same as FORMAT_PROC_FD_PATH(), but goes by PID rather than "self" symlink */ +#define FORMAT_PROC_PID_FD_PATH(pid, fd) \ + format_proc_pid_fd_path((char[PROC_PID_FD_PATH_MAX]) {}, (pid), (fd)) + const char *accmode_to_string(int flags); /* Like ASSERT_PTR, but for fds */ diff --git a/src/basic/glyph-util.c b/src/basic/glyph-util.c index bac75a053a..803bdd90e2 100644 --- a/src/basic/glyph-util.c +++ b/src/basic/glyph-util.c @@ -134,6 +134,8 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { [SPECIAL_GLYPH_SPARKLES] = u8"✨", [SPECIAL_GLYPH_LOW_BATTERY] = u8"🪫", [SPECIAL_GLYPH_WARNING_SIGN] = u8"⚠️", + [SPECIAL_GLYPH_COMPUTER_DISK] = u8"💽", + [SPECIAL_GLYPH_WORLD] = u8"🌍", }, }; diff --git a/src/basic/glyph-util.h b/src/basic/glyph-util.h index 451ec9af25..a7709976e1 100644 --- a/src/basic/glyph-util.h +++ b/src/basic/glyph-util.h @@ -47,6 +47,8 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_SPARKLES, SPECIAL_GLYPH_LOW_BATTERY, SPECIAL_GLYPH_WARNING_SIGN, + SPECIAL_GLYPH_COMPUTER_DISK, + SPECIAL_GLYPH_WORLD, _SPECIAL_GLYPH_MAX, _SPECIAL_GLYPH_INVALID = -EINVAL, } SpecialGlyph; diff --git a/src/basic/lock-util.c b/src/basic/lock-util.c index 7e67c2d583..047fd0184d 100644 --- a/src/basic/lock-util.c +++ b/src/basic/lock-util.c @@ -2,10 +2,12 @@ #include #include +#include #include #include #include #include +#include #include "alloc-util.h" #include "fd-util.h" @@ -14,6 +16,7 @@ #include "macro.h" #include "missing_fcntl.h" #include "path-util.h" +#include "process-util.h" int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret) { _cleanup_close_ int fd = -EBADF, dfd = -EBADF; @@ -186,3 +189,89 @@ int lock_generic(int fd, LockType type, int operation) { assert_not_reached(); } } + +int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeout) { + _cleanup_(sigkill_waitp) pid_t pid = 0; + int r; + + assert(fd >= 0); + + /* A version of lock_generic(), but with a time-out. We do this in a child process, since the kernel + * APIs natively don't support a timeout. We set a SIGALRM timer that will kill the child after the + * timeout is hit. Returns -ETIMEDOUT if the time-out is hit, and 0 on success. + * + * This only works for BSD and UNPOSIX locks, as only those are fd-bound, and hence can be acquired + * from any process that has access to the fd. POSIX locks OTOH are process-bound, and hence if we'd + * acquire them in a child process they'd remain unlocked in the parent. */ + + if (type == LOCK_NONE) + return 0; + if (!IN_SET(type, LOCK_BSD, LOCK_UNPOSIX)) /* Not for POSIX locks, see above. */ + return -EOPNOTSUPP; + + /* First, try without forking anything off */ + r = lock_generic(fd, type, operation | (timeout == USEC_INFINITY ? 0 : LOCK_NB)); + if (r != -EAGAIN || timeout == 0 || FLAGS_SET(operation, LOCK_NB)) + return r; + + /* If that didn't work, try with a child */ + + r = safe_fork("(sd-flock)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, &pid); + if (r < 0) + return log_error_errno(r, "Failed to flock block device in child process: %m"); + if (r == 0) { + struct sigevent sev = { + .sigev_notify = SIGEV_SIGNAL, + .sigev_signo = SIGALRM, + }; + timer_t id = 0; + + if (timer_create(CLOCK_MONOTONIC, &sev, &id) < 0) { + log_error_errno(errno, "Failed to allocate CLOCK_MONOTONIC timer: %m"); + _exit(EXIT_FAILURE); + } + + struct itimerspec its = {}; + timespec_store(&its.it_value, timeout); + + if (timer_settime(id, /* flags= */ 0, &its, NULL) < 0) { + log_error_errno(errno, "Failed to start CLOCK_MONOTONIC timer: %m"); + _exit(EXIT_FAILURE); + } + + if (lock_generic(fd, type, operation) < 0) { + log_error_errno(errno, "Unable to get an exclusive lock on the device: %m"); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + siginfo_t status; + r = wait_for_terminate(pid, &status); + if (r < 0) + return r; + + TAKE_PID(pid); + + switch (status.si_code) { + + case CLD_EXITED: + if (status.si_status != EXIT_SUCCESS) + return -EPROTO; + + return 0; + + case CLD_KILLED: + if (status.si_status == SIGALRM) + return -ETIMEDOUT; + + _fallthrough_; + + case CLD_DUMPED: + return -EPROTO; + + default: + assert_not_reached(); + } +} diff --git a/src/basic/lock-util.h b/src/basic/lock-util.h index b96ad85e21..91b332f803 100644 --- a/src/basic/lock-util.h +++ b/src/basic/lock-util.h @@ -41,3 +41,5 @@ typedef enum LockType { } LockType; int lock_generic(int fd, LockType type, int operation); + +int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeout); diff --git a/src/basic/meson.build b/src/basic/meson.build index 438e8493b8..d7450d8b44 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -273,6 +273,7 @@ libbasic = static_library( include_directories : basic_includes, dependencies : [libcap, libm, + librt, threads, userspace], c_args : ['-fvisibility=default'], diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c index 0a0de85aad..2101f617ad 100644 --- a/src/basic/namespace-util.c +++ b/src/basic/namespace-util.c @@ -219,7 +219,7 @@ int userns_acquire(const char *uid_map, const char *gid_map) { * and then kills the process again. This way we have a userns fd that is not bound to any * process. We can use that for file system mounts and similar. */ - r = safe_fork("(sd-mkuserns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_NEW_USERNS, &pid); + r = safe_fork("(sd-mkuserns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid); if (r < 0) return r; if (r == 0) diff --git a/src/basic/process-util.c b/src/basic/process-util.c index c5883b05d1..201c5596ae 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -1380,6 +1380,12 @@ pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata) { return pid; } +static int fork_flags_to_signal(ForkFlags flags) { + return (flags & FORK_DEATHSIG_SIGTERM) ? SIGTERM : + (flags & FORK_DEATHSIG_SIGINT) ? SIGINT : + SIGKILL; +} + int safe_fork_full( const char *name, const int stdio_fds[3], @@ -1409,9 +1415,10 @@ int safe_fork_full( fflush(stderr); /* This one shouldn't be necessary, stderr should be unbuffered anyway, but let's better be safe than sorry */ } - if (flags & (FORK_RESET_SIGNALS|FORK_DEATHSIG)) { - /* We temporarily block all signals, so that the new child has them blocked initially. This way, we can - * be sure that SIGTERMs are not lost we might send to the child. */ + if (flags & (FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGINT)) { + /* We temporarily block all signals, so that the new child has them blocked initially. This + * way, we can be sure that SIGTERMs are not lost we might send to the child. (Note that for + * FORK_DEATHSIG_SIGKILL we don't bother, since it cannot be blocked anyway.) */ assert_se(sigfillset(&ss) >= 0); block_signals = block_all = true; @@ -1512,8 +1519,8 @@ int safe_fork_full( r, "Failed to rename process, ignoring: %m"); } - if (flags & (FORK_DEATHSIG|FORK_DEATHSIG_SIGINT)) - if (prctl(PR_SET_PDEATHSIG, (flags & FORK_DEATHSIG_SIGINT) ? SIGINT : SIGTERM) < 0) { + if (flags & (FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGINT|FORK_DEATHSIG_SIGKILL)) + if (prctl(PR_SET_PDEATHSIG, fork_flags_to_signal(flags)) < 0) { log_full_errno(prio, errno, "Failed to set death signal: %m"); _exit(EXIT_FAILURE); } @@ -1538,7 +1545,7 @@ int safe_fork_full( } } - if (flags & FORK_DEATHSIG) { + if (flags & (FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGKILL|FORK_DEATHSIG_SIGINT)) { pid_t ppid; /* Let's see if the parent PID is still the one we started from? If not, then the parent * already died by the time we set PR_SET_PDEATHSIG, hence let's emulate the effect */ @@ -1547,8 +1554,9 @@ int safe_fork_full( if (ppid == 0) /* Parent is in a different PID namespace. */; else if (ppid != original_pid) { - log_debug("Parent died early, raising SIGTERM."); - (void) raise(SIGTERM); + int sig = fork_flags_to_signal(flags); + log_debug("Parent died early, raising %s.", signal_to_string(sig)); + (void) raise(sig); _exit(EXIT_FAILURE); } } @@ -1664,7 +1672,7 @@ int namespace_fork( r = safe_fork_full(outer_name, NULL, except_fds, n_except_fds, - (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid); + (flags|FORK_DEATHSIG_SIGINT|FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGKILL) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid); if (r < 0) return r; if (r == 0) { diff --git a/src/basic/process-util.h b/src/basic/process-util.h index a18a730936..af6cba13eb 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -160,22 +160,23 @@ pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata); typedef enum ForkFlags { FORK_RESET_SIGNALS = 1 << 0, /* Reset all signal handlers and signal mask */ FORK_CLOSE_ALL_FDS = 1 << 1, /* Close all open file descriptors in the child, except for 0,1,2 */ - FORK_DEATHSIG = 1 << 2, /* Set PR_DEATHSIG in the child to SIGTERM */ + FORK_DEATHSIG_SIGTERM = 1 << 2, /* Set PR_DEATHSIG in the child to SIGTERM */ FORK_DEATHSIG_SIGINT = 1 << 3, /* Set PR_DEATHSIG in the child to SIGINT */ - FORK_REARRANGE_STDIO = 1 << 4, /* Connect 0,1,2 to specified fds or /dev/null */ - FORK_REOPEN_LOG = 1 << 5, /* Reopen log connection */ - FORK_LOG = 1 << 6, /* Log above LOG_DEBUG log level about failures */ - FORK_WAIT = 1 << 7, /* Wait until child exited */ - FORK_NEW_MOUNTNS = 1 << 8, /* Run child in its own mount namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ - FORK_MOUNTNS_SLAVE = 1 << 9, /* Make child's mount namespace MS_SLAVE */ - FORK_PRIVATE_TMP = 1 << 10, /* Mount new /tmp/ in the child (combine with FORK_NEW_MOUNTNS!) */ - FORK_RLIMIT_NOFILE_SAFE = 1 << 11, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */ - FORK_STDOUT_TO_STDERR = 1 << 12, /* Make stdout a copy of stderr */ - FORK_FLUSH_STDIO = 1 << 13, /* fflush() stdout (and stderr) before forking */ - FORK_NEW_USERNS = 1 << 14, /* Run child in its own user namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ - FORK_CLOEXEC_OFF = 1 << 15, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */ - FORK_KEEP_NOTIFY_SOCKET = 1 << 16, /* Unless this specified, $NOTIFY_SOCKET will be unset. */ - FORK_DETACH = 1 << 17, /* Double fork if needed to ensure PID1/subreaper is parent */ + FORK_DEATHSIG_SIGKILL = 1 << 4, /* Set PR_DEATHSIG in the child to SIGKILL */ + FORK_REARRANGE_STDIO = 1 << 5, /* Connect 0,1,2 to specified fds or /dev/null */ + FORK_REOPEN_LOG = 1 << 6, /* Reopen log connection */ + FORK_LOG = 1 << 7, /* Log above LOG_DEBUG log level about failures */ + FORK_WAIT = 1 << 8, /* Wait until child exited */ + FORK_NEW_MOUNTNS = 1 << 9, /* Run child in its own mount namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ + FORK_MOUNTNS_SLAVE = 1 << 10, /* Make child's mount namespace MS_SLAVE */ + FORK_PRIVATE_TMP = 1 << 11, /* Mount new /tmp/ in the child (combine with FORK_NEW_MOUNTNS!) */ + FORK_RLIMIT_NOFILE_SAFE = 1 << 12, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */ + FORK_STDOUT_TO_STDERR = 1 << 13, /* Make stdout a copy of stderr */ + FORK_FLUSH_STDIO = 1 << 14, /* fflush() stdout (and stderr) before forking */ + FORK_NEW_USERNS = 1 << 15, /* Run child in its own user namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ + FORK_CLOEXEC_OFF = 1 << 16, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */ + FORK_KEEP_NOTIFY_SOCKET = 1 << 17, /* Unless this specified, $NOTIFY_SOCKET will be unset. */ + FORK_DETACH = 1 << 18, /* Double fork if needed to ensure PID1/subreaper is parent */ } ForkFlags; int safe_fork_full( diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 6cc6ff424a..b505a3ad0e 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1204,7 +1204,7 @@ int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_slave) { if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) return -errno; - r = namespace_fork("(sd-openptns)", "(sd-openpt)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG, + r = namespace_fork("(sd-openptns)", "(sd-openpt)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child); if (r < 0) return r; @@ -1255,7 +1255,7 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) return -errno; - r = namespace_fork("(sd-terminalns)", "(sd-terminal)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG, + r = namespace_fork("(sd-terminalns)", "(sd-terminal)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child); if (r < 0) return r; diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 19d8602faf..695b998b57 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -1044,7 +1044,7 @@ int parse_timestamp(const char *t, usec_t *ret) { if (shared == MAP_FAILED) return negative_errno(); - r = safe_fork("(sd-timestamp)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL); + r = safe_fork("(sd-timestamp)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, NULL); if (r < 0) { (void) munmap(shared, sizeof *shared); return r; diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c index ff2d795c7d..6bcfb68d8f 100644 --- a/src/core/exec-credential.c +++ b/src/core/exec-credential.c @@ -930,7 +930,7 @@ int exec_setup_credentials( if (r < 0 && r != -EEXIST) return r; - r = safe_fork("(sd-mkdcreds)", FORK_DEATHSIG|FORK_WAIT|FORK_NEW_MOUNTNS, NULL); + r = safe_fork("(sd-mkdcreds)", FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_NEW_MOUNTNS, NULL); if (r < 0) { _cleanup_(rmdir_and_freep) char *u = NULL; /* remove the temporary workspace if we can */ _cleanup_free_ char *t = NULL; diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 4cc303737e..b1467947e5 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -2136,7 +2136,7 @@ static int setup_private_users(uid_t ouid, gid_t ogid, uid_t uid, gid_t gid) { if (pipe2(errno_pipe, O_CLOEXEC) < 0) return -errno; - r = safe_fork("(sd-userns)", FORK_RESET_SIGNALS|FORK_DEATHSIG, &pid); + r = safe_fork("(sd-userns)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, &pid); if (r < 0) return r; if (r == 0) { diff --git a/src/core/unit.c b/src/core/unit.c index d12829c31f..c3613ca8fb 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -5426,7 +5426,7 @@ int unit_fork_helper_process(Unit *u, const char *name, PidRef *ret) { (void) unit_realize_cgroup(u); - r = safe_fork(name, FORK_REOPEN_LOG|FORK_DEATHSIG, &pid); + r = safe_fork(name, FORK_REOPEN_LOG|FORK_DEATHSIG_SIGTERM, &pid); if (r < 0) return r; if (r > 0) { diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index 4340d07df4..32c17664fd 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -1443,7 +1443,7 @@ static int forward_coredump_to_container(Context *context) { return log_debug_errno(r, "Failed to join namespaces of PID " PID_FMT ": %m", pid); r = namespace_fork("(sd-coredumpns)", "(sd-coredump)", NULL, 0, - FORK_RESET_SIGNALS|FORK_DEATHSIG, + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, pidnsfd, mntnsfd, netnsfd, usernsfd, rootfd, &child); if (r < 0) return log_debug_errno(r, "Failed to fork into namespaces of PID " PID_FMT ": %m", pid); diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 16abb8f62a..2245b350e4 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -1253,7 +1253,7 @@ static int run_debug(int argc, char **argv, void *userdata) { fork_name = strjoina("(", debugger_call[0], ")"); - r = safe_fork(fork_name, FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_FLUSH_STDIO, &pid); + r = safe_fork(fork_name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_FLUSH_STDIO, &pid); if (r < 0) goto finish; if (r == 0) { diff --git a/src/delta/delta.c b/src/delta/delta.c index d830c5e162..3337b7f081 100644 --- a/src/delta/delta.c +++ b/src/delta/delta.c @@ -168,7 +168,7 @@ static int found_override(const char *top, const char *bottom) { fflush(stdout); - r = safe_fork("(diff)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); + r = safe_fork("(diff)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); if (r < 0) return r; if (r == 0) { diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c index a09e7950cf..000ed69667 100644 --- a/src/fsck/fsck.c +++ b/src/fsck/fsck.c @@ -339,7 +339,7 @@ static int run(int argc, char *argv[]) { pipe(progress_pipe) < 0) return log_error_errno(errno, "pipe(): %m"); - r = safe_fork("(fsck)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &pid); + r = safe_fork("(fsck)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &pid); if (r < 0) return r; if (r == 0) { diff --git a/src/home/homectl.c b/src/home/homectl.c index 56b3de4a95..a6951c8562 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2025,7 +2025,7 @@ static int with_home(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_parse_error(r); - r = safe_fork("(with)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REOPEN_LOG, &pid); + r = safe_fork("(with)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REOPEN_LOG, &pid); if (r < 0) return r; if (r == 0) { diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 749670f94b..4ff35400c4 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -1184,7 +1184,7 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord r = safe_fork_full("(sd-homework)", (int[]) { stdin_fd, stdout_fd, STDERR_FILENO }, NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid); + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid); if (r < 0) return r; if (r == 0) { @@ -2080,7 +2080,7 @@ int home_killall(Home *h) { /* Let's kill everything matching the specified UID */ r = safe_fork("(sd-killer)", - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT|FORK_LOG|FORK_REOPEN_LOG, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_WAIT|FORK_LOG|FORK_REOPEN_LOG, NULL); if (r < 0) return r; diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index dff99e5a86..19f1cd5b85 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -91,7 +91,7 @@ int home_setup_cifs( if (!strextend_with_separator(&options, ",", h->cifs_extra_mount_options)) return log_oom(); - r = safe_fork("(mount)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_STDOUT_TO_STDERR, &mount_pid); + r = safe_fork("(mount)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_STDOUT_TO_STDERR, &mount_pid); if (r < 0) return r; if (r == 0) { diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c index 1b695309c0..6aae1d2626 100644 --- a/src/home/homework-fscrypt.c +++ b/src/home/homework-fscrypt.c @@ -314,7 +314,7 @@ int home_setup_fscrypt( if (uid_is_valid(h->uid)) { r = safe_fork("(sd-addkey)", - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_REOPEN_LOG, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_REOPEN_LOG, NULL); if (r < 0) return log_error_errno(r, "Failed install encryption key in user's keyring: %m"); diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 63d6c47ac1..5bd78a03ed 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -228,7 +228,7 @@ static int run_fsck(const char *node, const char *fstype) { } r = safe_fork("(fsck)", - FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, + FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, &fsck_pid); if (r < 0) return r; @@ -2596,7 +2596,7 @@ static int ext4_offline_resize_fs( /* resize2fs requires that the file system is force checked first, do so. */ r = safe_fork("(e2fsck)", - FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, + FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, &fsck_pid); if (r < 0) return r; @@ -2628,7 +2628,7 @@ static int ext4_offline_resize_fs( /* Resize the thing */ r = safe_fork("(e2resize)", - FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, + FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, &resize_pid); if (r < 0) return r; diff --git a/src/import/import-common.c b/src/import/import-common.c index 3a7a42c6b3..319aa078a7 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -39,7 +39,7 @@ int import_fork_tar_x(const char *path, pid_t *ret) { r = safe_fork_full("(tar)", (int[]) { pipefd[0], -EBADF, STDERR_FILENO }, NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG, &pid); + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG, &pid); if (r < 0) return r; if (r == 0) { @@ -108,7 +108,7 @@ int import_fork_tar_c(const char *path, pid_t *ret) { r = safe_fork_full("(tar)", (int[]) { -EBADF, pipefd[1], STDERR_FILENO }, NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG, &pid); + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG, &pid); if (r < 0) return r; if (r == 0) { diff --git a/src/import/importd.c b/src/import/importd.c index c5b60c2604..cb5156bf06 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -369,7 +369,7 @@ static int transfer_start(Transfer *t) { r = safe_fork_full("(sd-transfer)", (int[]) { t->stdin_fd, t->stdout_fd < 0 ? pipefd[1] : t->stdout_fd, pipefd[1] }, NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO, &t->pid); + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO, &t->pid); if (r < 0) return r; if (r == 0) { diff --git a/src/import/pull-common.c b/src/import/pull-common.c index 44919a19c5..5e1ea20a03 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -417,7 +417,7 @@ static int verify_gpg( r = safe_fork_full("(gpg)", (int[]) { gpg_pipe[0], -EBADF, STDERR_FILENO }, NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &pid); if (r < 0) return r; diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 3ffac96d7a..da0f20d3ce 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -91,7 +91,7 @@ static int spawn_child(const char* child, char** argv) { r = safe_fork_full("(remote)", (int[]) {STDIN_FILENO, fd[1], STDERR_FILENO }, NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &child_pid); + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &child_pid); if (r < 0) { safe_close_pair(fd); return r; diff --git a/src/libsystemd/sd-bus/bus-container.c b/src/libsystemd/sd-bus/bus-container.c index 71581d718c..4146a6efd3 100644 --- a/src/libsystemd/sd-bus/bus-container.c +++ b/src/libsystemd/sd-bus/bus-container.c @@ -51,7 +51,7 @@ int bus_container_connect_socket(sd_bus *b) { if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0) return log_debug_errno(errno, "Failed to create a socket pair: %m"); - r = namespace_fork("(sd-buscntrns)", "(sd-buscntr)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG, + r = namespace_fork("(sd-buscntrns)", "(sd-buscntr)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child); if (r < 0) return log_debug_errno(r, "Failed to create namespace for (sd-buscntr): %m"); diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c index 21a65e4dac..898f11d1ab 100644 --- a/src/libsystemd/sd-netlink/sd-netlink.c +++ b/src/libsystemd/sd-netlink/sd-netlink.c @@ -761,6 +761,12 @@ int sd_netlink_detach_event(sd_netlink *nl) { return 0; } +sd_event* sd_netlink_get_event(sd_netlink *nl) { + assert_return(nl, NULL); + + return nl->event; +} + int netlink_add_match_internal( sd_netlink *nl, sd_netlink_slot **ret_slot, diff --git a/src/login/inhibit.c b/src/login/inhibit.c index 61fbd5df14..ad73c4bde3 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -300,7 +300,7 @@ static int run(int argc, char *argv[]) { if (!arguments) return log_oom(); - r = safe_fork("(inhibit)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); + r = safe_fork("(inhibit)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); if (r < 0) return r; if (r == 0) { diff --git a/src/login/logind-brightness.c b/src/login/logind-brightness.c index d238cb74c9..40bcb39ce0 100644 --- a/src/login/logind-brightness.c +++ b/src/login/logind-brightness.c @@ -136,7 +136,7 @@ static int brightness_writer_fork(BrightnessWriter *w) { assert(w->child == 0); assert(!w->child_event_source); - r = safe_fork("(sd-bright)", FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_REOPEN_LOG, &w->child); + r = safe_fork("(sd-bright)", FORK_DEATHSIG_SIGKILL|FORK_REARRANGE_STDIO|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_REOPEN_LOG, &w->child); if (r < 0) return r; if (r == 0) { diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index b9ac931cd4..4620f32d62 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -248,7 +248,7 @@ int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) return -errno; - r = namespace_fork("(sd-addrns)", "(sd-addr)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG, + r = namespace_fork("(sd-addrns)", "(sd-addr)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, -1, -1, netns_fd, -1, -1, &child); if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m"); @@ -382,7 +382,7 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) return -errno; - r = namespace_fork("(sd-osrelns)", "(sd-osrel)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG, + r = namespace_fork("(sd-osrelns)", "(sd-osrel)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, pidns_fd, mntns_fd, -1, -1, root_fd, &child); if (r < 0) @@ -1103,7 +1103,7 @@ int bus_machine_method_open_root_directory(sd_bus_message *message, void *userda if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) return -errno; - r = namespace_fork("(sd-openrootns)", "(sd-openroot)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG, + r = namespace_fork("(sd-openrootns)", "(sd-openroot)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, -1, mntns_fd, -1, -1, root_fd, &child); if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m"); diff --git a/src/nspawn/nspawn-setuid.c b/src/nspawn/nspawn-setuid.c index 3c12648b9d..2d67c3d9de 100644 --- a/src/nspawn/nspawn-setuid.c +++ b/src/nspawn/nspawn-setuid.c @@ -30,7 +30,7 @@ static int spawn_getent(const char *database, const char *key, pid_t *rpid) { r = safe_fork_full("(getent)", (int[]) { -EBADF, pipe_fds[1], -EBADF }, NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &pid); if (r < 0) { safe_close_pair(pipe_fds); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index ca97453283..51791d9729 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -5276,7 +5276,7 @@ static int run_container( if (arg_private_network) { /* Move network interfaces back to the parent network namespace. We use `safe_fork` * to avoid having to move the parent to the child network namespace. */ - r = safe_fork(NULL, FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_WAIT|FORK_LOG, NULL); + r = safe_fork(NULL, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG, NULL); if (r < 0) return r; diff --git a/src/partition/repart.c b/src/partition/repart.c index 94c6d8e42c..c99168b08f 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -4673,7 +4673,7 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c * appear in the host namespace. Hence we fork a child that has its own file system namespace and * detached mount propagation. */ - r = safe_fork("(sd-copy)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL); + r = safe_fork("(sd-copy)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL); if (r < 0) return r; if (r == 0) { diff --git a/src/portable/portable.c b/src/portable/portable.c index f283530dd9..d72f3a02db 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -411,7 +411,7 @@ static int portable_extract_by_path( if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, seq) < 0) return log_debug_errno(errno, "Failed to allocated SOCK_SEQPACKET socket: %m"); - r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE|FORK_LOG, &child); + r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE|FORK_LOG, &child); if (r < 0) return r; if (r == 0) { diff --git a/src/quotacheck/quotacheck.c b/src/quotacheck/quotacheck.c index ef5fec3f10..27a914d737 100644 --- a/src/quotacheck/quotacheck.c +++ b/src/quotacheck/quotacheck.c @@ -79,7 +79,7 @@ static int run(int argc, char *argv[]) { return 0; } - r = safe_fork("(quotacheck)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_WAIT|FORK_LOG, NULL); + r = safe_fork("(quotacheck)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_WAIT|FORK_LOG, NULL); if (r < 0) return r; if (r == 0) { diff --git a/src/remount-fs/remount-fs.c b/src/remount-fs/remount-fs.c index 2874a0390d..37c7b389b4 100644 --- a/src/remount-fs/remount-fs.c +++ b/src/remount-fs/remount-fs.c @@ -51,7 +51,7 @@ static int do_remount(const char *path, bool force_rw, Hashmap **pids) { log_debug("Remounting %s...", path); r = safe_fork(force_rw ? "(remount-rw)" : "(remount)", - FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); if (r < 0) return r; if (r == 0) { diff --git a/src/resolve/test-resolved-stream.c b/src/resolve/test-resolved-stream.c index d1b38b3aad..847de04f90 100644 --- a/src/resolve/test-resolved-stream.c +++ b/src/resolve/test-resolved-stream.c @@ -151,7 +151,7 @@ static void *tls_dns_server(void *p) { r = safe_fork_full("(test-resolved-stream-tls-openssl)", (int[]) { fd_tls, fd_tls, STDOUT_FILENO }, NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &openssl_pid); assert_se(r >= 0); if (r == 0) { @@ -336,7 +336,7 @@ static void try_isolate_network(void) { /* First test if CLONE_NEWUSER/CLONE_NEWNET can actually work for us, i.e. we can open the namespaces * and then still access the build dir we are run from. We do that in a child process since it's * nasty if we have to go back from the namespace once we entered it and realized it cannot work. */ - r = safe_fork("(usernstest)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(usernstest)", FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT, NULL); if (r == 0) { /* child */ _cleanup_free_ char *rt = NULL, *d = NULL; diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index c6ccab081e..c906aec109 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -104,7 +104,7 @@ int block_device_get_whole_disk(sd_device *dev, sd_device **ret) { return 0; } -static int block_device_get_originating(sd_device *dev, sd_device **ret) { +int block_device_get_originating(sd_device *dev, sd_device **ret) { _cleanup_(sd_device_unrefp) sd_device *first_found = NULL; const char *suffix; dev_t devnum = 0; /* avoid false maybe-uninitialized warning */ diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index ea093c49a6..954a23df3e 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -27,6 +27,7 @@ int block_device_new_from_path(const char *path, BlockDeviceLookupFlag flag, sd_ int block_device_is_whole_disk(sd_device *dev); int block_device_get_whole_disk(sd_device *dev, sd_device **ret); +int block_device_get_originating(sd_device *dev, sd_device **ret); int block_get_whole_disk(dev_t d, dev_t *ret); int block_get_originating(dev_t d, dev_t *ret); diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c index 2665930209..039080f052 100644 --- a/src/shared/calendarspec.c +++ b/src/shared/calendarspec.c @@ -1401,7 +1401,7 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *ret_n if (shared == MAP_FAILED) return negative_errno(); - r = safe_fork("(sd-calendar)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL); + r = safe_fork("(sd-calendar)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, NULL); if (r < 0) { (void) munmap(shared, sizeof *shared); return r; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index ef23c4a255..2687eafaf6 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1718,7 +1718,7 @@ static int run_fsck(int node_fd, const char *fstype) { "(fsck)", NULL, &node_fd, 1, /* Leave the node fd open */ - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_CLOEXEC_OFF, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_CLOEXEC_OFF, &pid); if (r < 0) return log_debug_errno(r, "Failed to fork off fsck: %m"); @@ -3410,7 +3410,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ goto finish; } - r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, &child); + r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, &child); if (r < 0) goto finish; if (r == 0) { diff --git a/src/shared/edit-util.c b/src/shared/edit-util.c index 96ad721343..045839b663 100644 --- a/src/shared/edit-util.c +++ b/src/shared/edit-util.c @@ -270,7 +270,7 @@ static int run_editor(const EditFileContext *context) { assert(context); - r = safe_fork("(editor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(editor)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_WAIT, NULL); if (r < 0) return r; if (r == 0) { /* Child */ diff --git a/src/shared/exec-util.c b/src/shared/exec-util.c index 5da613bc17..852b3c0e3d 100644 --- a/src/shared/exec-util.c +++ b/src/shared/exec-util.c @@ -45,7 +45,7 @@ static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid, b return 0; } - r = safe_fork("(direxec)", FORK_DEATHSIG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &_pid); + r = safe_fork("(direxec)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &_pid); if (r < 0) return r; if (r == 0) { @@ -250,7 +250,7 @@ int execute_strv( * 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. */ - r = safe_fork("(sd-executor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &executor_pid); + r = safe_fork("(sd-executor)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &executor_pid); if (r < 0) return r; if (r == 0) { @@ -541,7 +541,7 @@ int fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret NULL, except, n_except, - FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE, + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE, ret_pid); if (r < 0) return r; diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index d3053ceabb..a5d04003bd 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -1692,7 +1692,7 @@ static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) { if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) return -errno; - r = namespace_fork("(sd-bootidns)", "(sd-bootid)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG, + r = namespace_fork("(sd-bootidns)", "(sd-bootid)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, pidnsfd, mntnsfd, -1, -1, rootfd, &child); if (r < 0) return r; diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index 6ea080869f..4e58b6e871 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -155,7 +155,7 @@ static int do_mcopy(const char *node, const char *root) { if (strv_extend(&argv, "::") < 0) return log_oom(); - r = safe_fork("(mcopy)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, NULL); + r = safe_fork("(mcopy)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, NULL); if (r < 0) return r; if (r == 0) { @@ -336,7 +336,7 @@ int make_filesystem( _cleanup_(unlink_and_freep) char *protofile = NULL; char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {}; int stdio_fds[3] = { -EBADF, STDERR_FILENO, STDERR_FILENO}; - ForkFlags flags = FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT| + ForkFlags flags = FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT| FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO|FORK_REOPEN_LOG; int r; diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 02640a13a9..4f2acce513 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -981,7 +981,7 @@ static int mount_in_namespace_legacy( goto finish; } - r = namespace_fork("(sd-bindmnt)", "(sd-bindmnt-inner)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG, + r = namespace_fork("(sd-bindmnt)", "(sd-bindmnt-inner)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, pidns_fd, mntns_fd, -1, -1, root_fd, &child); if (r < 0) goto finish; @@ -1184,7 +1184,7 @@ static int mount_in_namespace( "(sd-bindmnt-inner)", /* except_fds= */ NULL, /* n_except_fds= */ 0, - FORK_RESET_SIGNALS|FORK_DEATHSIG, + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, pidns_fd, mntns_fd, /* netns_fd= */ -1, diff --git a/src/shared/pager.c b/src/shared/pager.c index 2e9c82cde7..19deefab56 100644 --- a/src/shared/pager.c +++ b/src/shared/pager.c @@ -316,7 +316,7 @@ int show_man_page(const char *desc, bool null_stdio) { } else args[1] = desc; - r = safe_fork("(man)", FORK_RESET_SIGNALS|FORK_DEATHSIG|(null_stdio ? FORK_REARRANGE_STDIO : 0)|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); + r = safe_fork("(man)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|(null_stdio ? FORK_REARRANGE_STDIO : 0)|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); if (r < 0) return r; if (r == 0) { diff --git a/src/shared/varlink.c b/src/shared/varlink.c index 23ce56ca9f..2ba942f30e 100644 --- a/src/shared/varlink.c +++ b/src/shared/varlink.c @@ -452,7 +452,7 @@ int varlink_connect_exec(Varlink **ret, const char *_command, char **_argv) { /* stdio_fds= */ NULL, /* except_fds= */ (int[]) { pair[1] }, /* n_except_fds= */ 1, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REOPEN_LOG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &pid); if (r < 0) return log_debug_errno(r, "Failed to spawn process: %m"); diff --git a/src/socket-activate/socket-activate.c b/src/socket-activate/socket-activate.c index 644bed0dcc..78ecb29e60 100644 --- a/src/socket-activate/socket-activate.c +++ b/src/socket-activate/socket-activate.c @@ -223,7 +223,7 @@ static int fork_and_exec_process(const char *child, char **argv, int fd) { return log_oom(); r = safe_fork("(activate)", - FORK_RESET_SIGNALS | FORK_DEATHSIG | FORK_RLIMIT_NOFILE_SAFE | FORK_LOG, + FORK_RESET_SIGNALS | FORK_DEATHSIG_SIGTERM | FORK_RLIMIT_NOFILE_SAFE | FORK_LOG, &child_pid); if (r < 0) return r; diff --git a/src/storagetm/meson.build b/src/storagetm/meson.build new file mode 100644 index 0000000000..f95210af4c --- /dev/null +++ b/src/storagetm/meson.build @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + libexec_template + { + 'name' : 'systemd-storagetm', + 'conditions' : [ + 'ENABLE_STORAGETM', + ], + 'sources' : files('storagetm.c'), + }, +] diff --git a/src/storagetm/storagetm.c b/src/storagetm/storagetm.c new file mode 100644 index 0000000000..dfde848434 --- /dev/null +++ b/src/storagetm/storagetm.c @@ -0,0 +1,1047 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "af-list.h" +#include "alloc-util.h" +#include "blockdev-util.h" +#include "build.h" +#include "daemon-util.h" +#include "device-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-util.h" +#include "fs-util.h" +#include "local-addresses.h" +#include "loop-util.h" +#include "main-func.h" +#include "parse-argument.h" +#include "path-util.h" +#include "pretty-print.h" +#include "process-util.h" +#include "random-util.h" +#include "recurse-dir.h" +#include "socket-util.h" +#include "terminal-util.h" +#include "udev-util.h" + +static char **arg_devices = NULL; +static char *arg_nqn = NULL; +static int arg_all = 0; + +STATIC_DESTRUCTOR_REGISTER(arg_devices, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_nqn, freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-storagetm", "8", &link); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...] [DEVICE...]\n" + "\n%sExpose a block device or regular file as NVMe-TCP volume.%s\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --nqn=STRING Select NQN (NVMe Qualified Name)\n" + " -a --all Expose all devices\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_NQN = 0x100, + ARG_VERSION, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "nqn", required_argument, NULL, ARG_NQN }, + { "all", no_argument, NULL, 'a' }, + {} + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "ha", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case ARG_NQN: + if (!filename_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NQN invalid: %s", optarg); + + if (free_and_strdup(&arg_nqn, optarg) < 0) + return log_oom(); + + break; + + case 'a': + arg_all++; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (arg_all > 0) { + if (argc > optind) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expects no further arguments if --all/-a is specified."); + } else { + if (optind >= argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expecting device name or --all/-a."); + + for (int i = optind; i < argc; i++) + if (!path_is_valid(argv[i])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", argv[i]); + + arg_devices = strv_copy(argv + optind); + } + + if (!arg_nqn) { + sd_id128_t id; + + r = sd_id128_get_machine_app_specific(SD_ID128_MAKE(b4,f9,4e,52,b8,e2,45,db,88,84,6e,2e,c3,f4,ef,18), &id); + if (r < 0) + return log_error_errno(r, "Failed to get machine ID: %m"); + + /* See NVM Express Base Specification 2.0c, 4.5 "NVMe Qualified Names" */ + if (asprintf(&arg_nqn, "nqn.2023-10.io.systemd:storagetm." SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)) < 0) + return log_oom(); + } + + return 1; +} + +typedef struct NvmeSubsystem { + char *name; + struct stat device_stat; + int device_fd; + int nvme_all_subsystems_fd; /* The /sys/kernel/config/nvmet/subsystems/ dir, that contains all subsystems */ + int nvme_our_subsystem_fd; /* Our private subsystem dir below it. */ + char *device; +} NvmeSubsystem; + +static NvmeSubsystem* nvme_subsystem_free(NvmeSubsystem *s) { + if (!s) + return NULL; + + free(s->name); + safe_close(s->nvme_all_subsystems_fd); + safe_close(s->nvme_our_subsystem_fd); + safe_close(s->device_fd); + free(s->device); + + return mfree(s); +} + +static int nvme_subsystem_unlink(NvmeSubsystem *s) { + int r; + + assert(s); + + if (s->nvme_our_subsystem_fd >= 0) { + _cleanup_close_ int namespaces_fd = -EBADF; + + namespaces_fd = openat(s->nvme_our_subsystem_fd, "namespaces", O_CLOEXEC|O_DIRECTORY|O_RDONLY); + if (namespaces_fd < 0) + log_warning_errno(errno, "Failed to open 'namespaces' directory of subsystem '%s': %m", s->name); + else { + _cleanup_free_ DirectoryEntries *de = NULL; + + r = readdir_all(namespaces_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de); + if (r < 0) + log_warning_errno(r, "Failed to read 'namespaces' dir of subsystem '%s', ignoring: %m", s->name); + else { + FOREACH_ARRAY(ee, de->entries, de->n_entries) { + _cleanup_free_ char *enable_fn = NULL; + const struct dirent *e = *ee; + + enable_fn = path_join(e->d_name, "enable"); + if (!enable_fn) + return log_oom(); + + r = write_string_file_at(namespaces_fd, enable_fn, "0", WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + log_warning_errno(r, "Failed to disable namespace '%s' of NVME subsystem '%s', ignoring: %m", e->d_name, s->name); + + if (unlinkat(namespaces_fd, e->d_name, AT_REMOVEDIR) < 0 && errno != ENOENT) + log_warning_errno(errno, "Failed to remove namespace '%s' of NVME subsystem '%s', ignoring: %m", e->d_name, s->name); + } + } + } + + s->nvme_our_subsystem_fd = safe_close(s->nvme_our_subsystem_fd); + } + + if (s->nvme_all_subsystems_fd >= 0 && s->name) { + if (unlinkat(s->nvme_all_subsystems_fd, s->name, AT_REMOVEDIR) < 0 && errno != ENOENT) + log_warning_errno(errno, "Failed to remove NVME subsystem '%s', ignoring: %m", s->name); + + s->nvme_all_subsystems_fd = safe_close(s->nvme_all_subsystems_fd); /* Invalidate the subsystems/ dir fd, to remember we unlinked the thing already */ + + log_info("NVME subsystem '%s' removed.", s->name); + } + + return 0; +} + +static NvmeSubsystem *nvme_subsystem_destroy(NvmeSubsystem *s) { + if (!s) + return NULL; + + (void) nvme_subsystem_unlink(s); + + return nvme_subsystem_free(s); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(NvmeSubsystem*, nvme_subsystem_destroy); + +static int nvme_subsystem_add(const char *node, int consumed_fd, NvmeSubsystem **ret) { + _cleanup_close_ int fd = consumed_fd; /* always take possession of the fd */ + int r; + + assert(node); + assert(ret); + + _cleanup_free_ char *fname = NULL; + r = path_extract_filename(node, &fname); + if (r < 0) + return log_error_errno(r, "Failed to extract file name from path: %s", node); + + _cleanup_free_ char *j = NULL; + j = strjoin(arg_nqn, ".", fname); + if (!j) + return log_oom(); + + if (fd < 0) { + fd = RET_NERRNO(open(node, O_RDONLY|O_CLOEXEC|O_NONBLOCK)); + if (fd < 0) + return log_error_errno(fd, "Failed to open '%s': %m", node); + } + + struct stat st; + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat '%s': %m", node); + if (!S_ISBLK(st.st_mode)) { + r = stat_verify_regular(&st); + if (r < 0) + return log_error_errno(r, "Not a block device or regular file, refusing: %s", node); + } + + /* Let's lock this device continuously while we are operating on it */ + r = lock_generic_with_timeout(fd, LOCK_BSD, LOCK_EX, 10 * USEC_PER_SEC); + if (r < 0) + return log_error_errno(r, "Failed to lock block device: %m"); + + _cleanup_close_ int subsystems_fd = -EBADF; + subsystems_fd = RET_NERRNO(open("/sys/kernel/config/nvmet/subsystems", O_DIRECTORY|O_CLOEXEC|O_RDONLY)); + if (subsystems_fd < 0) + return log_error_errno(subsystems_fd, "Failed to open /sys/kernel/config/nvmet/subsystems: %m"); + + _cleanup_close_ int subsystem_fd = -EBADF; + subsystem_fd = open_mkdir_at(subsystems_fd, j, O_EXCL|O_RDONLY|O_CLOEXEC, 0777); + if (subsystem_fd < 0) + return log_error_errno(subsystem_fd, "Failed to create NVME subsystem '%s': %m", j); + + r = write_string_file_at(subsystem_fd, "attr_allow_any_host", "1", WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_error_errno(r, "Failed to set 'attr_allow_any_host' flag: %m"); + + _cleanup_close_ int namespace_fd = -EBADF; + namespace_fd = open_mkdir_at(subsystem_fd, "namespaces/1", O_EXCL|O_RDONLY|O_CLOEXEC, 0777); + if (namespace_fd < 0) + return log_error_errno(namespace_fd, "Failed to create NVME namespace '1': %m"); + + /* We use /proc/$PID/fd/$FD rather than /proc/self/fd/$FD, because this string is visible to others + * via configfs, and by including the PID it's clear to who the stuff belongs. */ + r = write_string_file_at(namespace_fd, "device_path", FORMAT_PROC_PID_FD_PATH(0, fd), WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_error_errno(r, "Failed to write 'device_path' attribute: %m"); + + r = write_string_file_at(namespace_fd, "enable", "1", WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_error_errno(r, "Failed to write 'enable' attribute: %m"); + + _cleanup_(nvme_subsystem_destroyp) NvmeSubsystem *subsys = NULL; + + subsys = new(NvmeSubsystem, 1); + if (!subsys) + return log_oom(); + + *subsys = (NvmeSubsystem) { + .name = TAKE_PTR(j), + .device_fd = TAKE_FD(fd), + .nvme_all_subsystems_fd = TAKE_FD(subsystems_fd), + .nvme_our_subsystem_fd = TAKE_FD(subsystem_fd), + .device_stat = st, + }; + + subsys->device = strdup(node); + if (!subsys->device) + return log_oom(); + + *ret = TAKE_PTR(subsys); + return 0; +} + +typedef struct NvmePort { + uint16_t portnr; /* used for both the IP and the NVME port numer */ + + int nvme_port_fd; + int nvme_ports_fd; + + int ip_family; +} NvmePort; + +static NvmePort *nvme_port_free(NvmePort *p) { + if (!p) + return NULL; + + safe_close(p->nvme_port_fd); + safe_close(p->nvme_ports_fd); + + return mfree(p); +} + +static int nvme_port_unlink(NvmePort *p) { + int r, ret = 0; + + assert(p); + + if (p->nvme_port_fd >= 0) { + _cleanup_close_ int subsystems_dir_fd = -EBADF; + + subsystems_dir_fd = openat(p->nvme_port_fd, "subsystems", O_DIRECTORY|O_RDONLY|O_CLOEXEC); + if (subsystems_dir_fd < 0) + log_warning_errno(errno, "Failed to open 'subsystems' dir of port %" PRIu16 ", ignoring: %m", p->portnr); + else { + _cleanup_free_ DirectoryEntries *de = NULL; + + r = readdir_all(subsystems_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de); + if (r < 0) + log_warning_errno(r, "Failed to read 'subsystems' dir of port %" PRIu16 ", ignoring: %m", p->portnr); + else + FOREACH_ARRAY(ee, de->entries, de->n_entries) { + const struct dirent *e = *ee; + + if (unlinkat(subsystems_dir_fd, e->d_name, 0) < 0 && errno != ENOENT) + log_warning_errno(errno, "Failed to remove 'subsystems' symlink '%s' of port %" PRIu16 ", ignoring: %m", e->d_name, p->portnr); + } + } + + p->nvme_port_fd = safe_close(p->nvme_port_fd); + } + + if (p->nvme_ports_fd >= 0) { + _cleanup_free_ char *fn = NULL; + if (asprintf(&fn, "%" PRIu16, p->portnr) < 0) + return log_oom(); + + if (unlinkat(p->nvme_ports_fd, fn, AT_REMOVEDIR) < 0) { + if (errno == ENOENT) + ret = 0; + else + ret = log_warning_errno(errno, "Failed to remove port '%" PRIu16 ", ignoring: %m", p->portnr); + } else + ret = 1; + + p->nvme_ports_fd = safe_close(p->nvme_ports_fd); + } + + return ret; +} + +static NvmePort *nvme_port_destroy(NvmePort *p) { + if (!p) + return NULL; + + (void) nvme_port_unlink(p); + + return nvme_port_free(p); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(NvmePort*, nvme_port_destroy); + +static int nvme_port_add_portnr( + int ports_fd, + uint16_t portnr, + int ip_family, + int *ret_fd) { + + int r; + + assert(ports_fd >= 0); + assert(IN_SET(ip_family, AF_INET, AF_INET6)); + assert(ret_fd); + + _cleanup_free_ char *fname = NULL; + if (asprintf(&fname, "%" PRIu16, portnr) < 0) + return log_oom(); + + _cleanup_close_ int port_fd = -EBADF; + port_fd = open_mkdir_at(ports_fd, fname, O_EXCL|O_RDONLY|O_CLOEXEC, 0777); + if (port_fd < 0) { + if (port_fd != -EEXIST) + return log_error_errno(port_fd, "Failed to create port %" PRIu16 ": %m", portnr); + + *ret_fd = -EBADF; + return 0; + } + + r = write_string_file_at(port_fd, "addr_adrfam", af_to_ipv4_ipv6(ip_family), WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_error_errno(r, "Failed to set address family on NVME port %" PRIu16 ": %m", portnr); + + r = write_string_file_at(port_fd, "addr_trtype", "tcp", WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_error_errno(r, "Failed to set transport type on NVME port %" PRIu16 ": %m", portnr); + + r = write_string_file_at(port_fd, "addr_trsvcid", fname, WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_error_errno(r, "Failed to set IP port on NVME port %" PRIu16 ": %m", portnr); + + r = write_string_file_at(port_fd, "addr_traddr", ip_family == AF_INET6 ? "::" : "0.0.0.0", WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_error_errno(r, "Failed to set IP address on NVME port %" PRIu16 ": %m", portnr); + + *ret_fd = TAKE_FD(port_fd); + return 1; +} + +static uint16_t calculate_start_port(const char *name, int ip_family) { + struct siphash state; + uint16_t nr; + + assert(name); + assert(IN_SET(ip_family, AF_INET, AF_INET6)); + + /* Use some fixed key Lennart pulled from /dev/urandom, so that we are deterministic */ + siphash24_init(&state, SD_ID128_MAKE(d1,0b,67,b5,e2,b7,4a,91,8d,6b,27,b6,35,c1,9f,d9).bytes); + siphash24_compress_string(name, &state); + siphash24_compress(&ip_family, sizeof(ip_family), &state); + + nr = 1024U + siphash24_finalize(&state) % (0xFFFFU - 1024U); + SET_FLAG(nr, 1, ip_family == AF_INET6); /* Lowest bit reflects family */ + + return nr; +} + +static uint16_t calculate_next_port(int ip_family) { + uint16_t nr; + + assert(IN_SET(ip_family, AF_INET, AF_INET6)); + + nr = 1024U + random_u64_range(0xFFFFU - 1024U); + SET_FLAG(nr, 1, ip_family == AF_INET6); /* Lowest bit reflects family */ + + return nr; +} + +static int nvme_port_add(const char *name, int ip_family, NvmePort **ret) { + int r; + + assert(name); + assert(IN_SET(ip_family, AF_INET, AF_INET6)); + assert(ret); + + _cleanup_close_ int ports_fd = -EBADF; + ports_fd = RET_NERRNO(open("/sys/kernel/config/nvmet/ports", O_DIRECTORY|O_RDONLY|O_CLOEXEC)); + if (ports_fd < 0) + return log_error_errno(ports_fd, "Failed to open /sys/kernel/config/nvmet/ports: %m"); + + _cleanup_close_ int port_fd = -EBADF; + uint16_t portnr = calculate_start_port(name, ip_family); + for (unsigned attempt = 0;; attempt++) { + r = nvme_port_add_portnr(ports_fd, portnr, ip_family, &port_fd); + if (r < 0) + return r; + if (r > 0) + break; + + if (attempt > 16) + return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find free NVME port after %u attempts.", attempt); + + log_debug_errno(port_fd, "NVME port %" PRIu16 " exists already, randomizing port.", portnr); + + portnr = calculate_next_port(ip_family); + } + + _cleanup_(nvme_port_destroyp) NvmePort *p = new(NvmePort, 1); + if (!p) + return log_oom(); + + *p = (NvmePort) { + .portnr = portnr, + .nvme_ports_fd = TAKE_FD(ports_fd), + .nvme_port_fd = TAKE_FD(port_fd), + .ip_family = ip_family, + }; + + *ret = TAKE_PTR(p); + return 0; +} + +static int nvme_port_link_subsystem(NvmePort *port, NvmeSubsystem *subsys) { + assert(port); + assert(subsys); + + _cleanup_free_ char *target = NULL, *linkname = NULL; + target = path_join("/sys/kernel/config/nvmet/subsystems", subsys->name); + if (!target) + return log_oom(); + + linkname = path_join("subsystems", subsys->name); + if (!linkname) + return log_oom(); + + if (symlinkat(target, port->nvme_port_fd, linkname) < 0) + return log_error_errno(errno, "Failed to link subsystem '%s' to port %" PRIu16 ": %m", subsys->name, port->portnr); + + return 0; +} + +static int nvme_port_unlink_subsystem(NvmePort *port, NvmeSubsystem *subsys) { + assert(port); + assert(subsys); + + _cleanup_free_ char *linkname = NULL; + linkname = path_join("subsystems", subsys->name); + if (!linkname) + return log_oom(); + + if (unlinkat(port->nvme_port_fd, linkname, 0) < 0 && errno != ENOENT) + return log_error_errno(errno, "Failed to unlink subsystem '%s' to port %" PRIu16 ": %m", subsys->name, port->portnr); + + return 0; +} + +static int nvme_subsystem_report(NvmeSubsystem *subsystem, NvmePort *ipv4, NvmePort *ipv6) { + assert(subsystem); + + _cleanup_free_ struct local_address *addresses = NULL; + int n_addresses; + n_addresses = local_addresses(NULL, 0, AF_UNSPEC, &addresses); + if (n_addresses < 0) + return log_error_errno(n_addresses, "Failed to determine local IP addresses: %m"); + + log_notice("NVMe-TCP: %s %s%s%s (%s)", + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + emoji_enabled() ? special_glyph(SPECIAL_GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "", + subsystem->name, subsystem->device); + + FOREACH_ARRAY(a, addresses, n_addresses) { + NvmePort *port = a->family == AF_INET ? ipv4 : ipv6; + + if (!port) + continue; + + log_info(" %s Try for specific device: nvme connect -t tcp -n '%s' -a %s -s %" PRIu16, + special_glyph(a >= addresses + (n_addresses - 1) ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH), + subsystem->name, + IN_ADDR_TO_STRING(a->family, &a->address), + port->portnr); + } + + return 0; +} + +static int nvme_port_report(NvmePort *port) { + if (!port) + return 0; + + _cleanup_free_ struct local_address *addresses = NULL; + int n_addresses; + n_addresses = local_addresses(NULL, 0, port->ip_family, &addresses); + if (n_addresses < 0) + return log_error_errno(n_addresses, "Failed to determine local IP addresses: %m"); + + log_notice("NVMe-TCP: %s %s%sListening on %s (port %" PRIu16 ")", + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + emoji_enabled() ? special_glyph(SPECIAL_GLYPH_WORLD) : "", emoji_enabled() ? " " : "", + af_to_ipv4_ipv6(port->ip_family), + port->portnr); + + FOREACH_ARRAY(a, addresses, n_addresses) + log_info(" %s Try for all devices: nvme connect-all -t tcp -a %s -s %" PRIu16, + special_glyph(a >= addresses + (n_addresses - 1) ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH), + IN_ADDR_TO_STRING(a->family, &a->address), + port->portnr); + + return 0; +} + +typedef struct Context { + Hashmap *subsystems; + NvmePort *ipv4_port, *ipv6_port; + + bool display_refresh_scheduled; +} Context; + +static void device_hash_func(const struct stat *q, struct siphash *state) { + assert(q); + + if (S_ISBLK(q->st_mode) || S_ISCHR(q->st_mode)) { + mode_t m = q->st_mode & S_IFMT; + siphash24_compress(&m, sizeof(m), state); + siphash24_compress(&q->st_rdev, sizeof(q->st_rdev), state); + return; + } + + return inode_hash_func(q, state); +} + +static int device_compare_func(const struct stat *a, const struct stat *b) { + int r; + + assert(a); + assert(b); + + r = CMP(a->st_mode & S_IFMT, b->st_mode & S_IFMT); + if (r != 0) + return r; + + if (S_ISBLK(a->st_mode) || S_ISCHR(a->st_mode)) { + r = CMP(major(a->st_rdev), major(b->st_rdev)); + if (r != 0) + return r; + + r = CMP(minor(a->st_rdev), minor(b->st_rdev)); + if (r != 0) + return r; + + return 0; + } + + return inode_compare_func(a, b); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + nvme_subsystem_hash_ops, + struct stat, + device_hash_func, + device_compare_func, + NvmeSubsystem, + nvme_subsystem_destroy); + +static void context_done(Context *c) { + assert(c); + + c->ipv4_port = nvme_port_destroy(c->ipv4_port); + c->ipv6_port = nvme_port_destroy(c->ipv6_port); + + c->subsystems = hashmap_free(c->subsystems); +} + +static void device_track_back(sd_device *d, sd_device **ret) { + int r; + + assert(d); + assert(ret); + + const char *devname = NULL; + (void) sd_device_get_devname(d, &devname); + + _cleanup_(sd_device_unrefp) sd_device *d_originating = NULL; + r = block_device_get_originating(d, &d_originating); + if (r < 0) + log_device_debug_errno(d, r, "Failed to get originating device for '%s', ignoring: %m", strna(devname)); + + sd_device *d_whole = NULL; + r = block_device_get_whole_disk(d_originating ?: d, &d_whole); /* does not ref returned device */ + if (r < 0) + log_device_debug_errno(d, r, "Failed to get whole device for '%s', ignoring: %m", strna(devname)); + + *ret = d_whole ? sd_device_ref(d_whole) : d_originating ? TAKE_PTR(d_originating) : sd_device_ref(d); +} + +static int device_is_same(sd_device *a, sd_device *b) { + dev_t devnum_a, devnum_b; + int r; + + assert(a); + assert(b); + + r = sd_device_get_devnum(a, &devnum_a); + if (r < 0) + return r; + + r = sd_device_get_devnum(b, &devnum_b); + if (r < 0) + return r; + + return devnum_a == devnum_b; +} + +static bool device_is_allowed(sd_device *d) { + int r; + + assert(d); + + if (arg_all >= 2) /* If --all is specified twice we allow even the root fs to shared */ + return true; + + const char *devname; + r = sd_device_get_devname(d, &devname); + if (r < 0) + return log_device_error_errno(d, r, "Failed to get device name: %m"); + + dev_t root_devnum; + r = get_block_device("/", &root_devnum); + if (r < 0) { + log_warning_errno(r, "Failed to get backing device of the root file system: %m"); + return false; /* Better safe */ + } + if (root_devnum == 0) /* Not backed by a block device? */ + return true; + + _cleanup_(sd_device_unrefp) sd_device *root_device = NULL; + r = sd_device_new_from_devnum(&root_device, 'b', root_devnum); + if (r < 0) { + log_warning_errno(r, "Failed to get root block device, assuming device '%s' is same as root device: %m", devname); + return false; + } + + _cleanup_(sd_device_unrefp) sd_device *whole_root_device = NULL; + device_track_back(root_device, &whole_root_device); + + _cleanup_(sd_device_unrefp) sd_device *whole_d = NULL; + device_track_back(d, &whole_d); + + r = device_is_same(whole_root_device, whole_d); + if (r < 0) { + log_warning_errno(r, "Failed to determine if root device and device '%s' are the same, assuming they are: %m", devname); + return false; /* Better safe */ + } + + return !r; +} + +static int device_added(Context *c, sd_device *device) { + _cleanup_close_ int fd = -EBADF; + int r; + + assert(c); + assert(device); + + const char *sysname; + r = sd_device_get_sysname(device, &sysname); + if (r < 0) + return log_device_error_errno(device, r, "Failed to get device name: %m"); + + log_device_debug(device, "new block device '%s'", sysname); + + if (STARTSWITH_SET(sysname, "loop", "zram")) /* Ignore some devices */ + return 0; + + const char *devname; + r = sd_device_get_devname(device, &devname); + if (r < 0) + return log_device_error_errno(device, r, "Failed to get device node path: %m"); + + struct stat lookup_key = { + .st_mode = S_IFBLK, + }; + + r = sd_device_get_devnum(device, &lookup_key.st_rdev); + if (r < 0) + return log_device_error_errno(device, r, "Failed to get major/minor from device: %m"); + + if (hashmap_contains(c->subsystems, &lookup_key)) { + log_debug("Device '%s' already seen.", devname); + return 0; + } + + if (!device_is_allowed(device)) { + log_device_debug(device, "Not exposing device '%s', as it is backed by root disk.", devname); + return 0; + } + + fd = sd_device_open(device, O_RDONLY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) { + log_device_warning_errno(device, fd, "Failed to open newly acquired device '%s', ignoring device: %m", devname); + return 0; + } + + _cleanup_(nvme_subsystem_destroyp) NvmeSubsystem *s = NULL; + r = nvme_subsystem_add(devname, TAKE_FD(fd), &s); + if (r < 0) + return r; + + if (c->ipv4_port) { + r = nvme_port_link_subsystem(c->ipv4_port, s); + if (r < 0) + return r; + } + + if (c->ipv6_port) { + r = nvme_port_link_subsystem(c->ipv6_port, s); + if (r < 0) + return r; + } + + r = hashmap_ensure_put(&c->subsystems, &nvme_subsystem_hash_ops, &s->device_stat, s); + if (r < 0) + return log_error_errno(r, "Failed to add subsystem to hash table: %m"); + + (void) nvme_subsystem_report(s, c->ipv4_port, c->ipv6_port); + + TAKE_PTR(s); + return 1; +} + +static int device_removed(Context *c, sd_device *device) { + int r; + + assert(device); + + struct stat lookup_key = { + .st_mode = S_IFBLK, + }; + + r = sd_device_get_devnum(device, &lookup_key.st_rdev); + if (r < 0) + return log_device_error_errno(device, r, "Failed to get major/minor from device: %m"); + + NvmeSubsystem *s = hashmap_remove(c->subsystems, &lookup_key); + if (!s) + return 0; + + log_device_debug(device, "removed block device '%s'", s->name); + + if (c->ipv4_port) + (void) nvme_port_unlink_subsystem(c->ipv4_port, s); + if (c->ipv6_port) + (void) nvme_port_unlink_subsystem(c->ipv6_port, s); + + s = nvme_subsystem_destroy(s); + return 1; +} + +static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, void *userdata) { + Context *c = ASSERT_PTR(userdata); + + if (device_for_action(device, SD_DEVICE_REMOVE)) + device_removed(c, device); + else + device_added(c, device); + + return 0; +} + +static int on_display_refresh(sd_event_source *s, uint64_t usec, void *userdata) { + Context *c = ASSERT_PTR(userdata); + + assert(s); + + c->display_refresh_scheduled = false; + + if (isatty(STDERR_FILENO) > 0) + fputs(ANSI_HOME_CLEAR, stderr); + + (void) nvme_port_report(c->ipv4_port); + (void) nvme_port_report(c->ipv6_port); + + NvmeSubsystem *i; + HASHMAP_FOREACH(i, c->subsystems) + (void) nvme_subsystem_report(i, c->ipv4_port, c->ipv6_port); + + return 0; +} + +static int on_address_change(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r, family; + + assert(rtnl); + assert(mm); + + r = sd_rtnl_message_addr_get_family(mm, &family); + if (r < 0) { + log_warning_errno(r, "Failed to get address family from netlink address message, ignoring: %m"); + return 0; + } + + if (!c->display_refresh_scheduled) { + r = sd_event_add_time_relative( + sd_netlink_get_event(rtnl), + /* ret_slot= */ NULL, + CLOCK_MONOTONIC, + 750 * USEC_PER_MSEC, + 0, + on_display_refresh, + c); + if (r < 0) + log_warning_errno(r, "Failed to schedule display refresh, ignoring: %m"); + else + c->display_refresh_scheduled = true; + } + + return 0; +} + +static int run(int argc, char* argv[]) { + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(context_done) Context context = {}; + int r; + + log_show_color(true); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = sd_event_new(&event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + r = sd_event_set_signal_exit(event, true); + if (r < 0) + return log_error_errno(r, "Failed to install exit signal handlers: %m"); + + STRV_FOREACH(i, arg_devices) { + _cleanup_(nvme_subsystem_destroyp) NvmeSubsystem *subsys = NULL; + + r = nvme_subsystem_add(*i, -EBADF, &subsys); + if (r < 0) + return r; + + r = hashmap_ensure_put(&context.subsystems, &nvme_subsystem_hash_ops, &subsys->device_stat, subsys); + if (r == -EEXIST) { + log_warning_errno(r, "Duplicate device '%s' specified, skipping: %m", *i); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to add subsystem to hash table: %m"); + + TAKE_PTR(subsys); + } + + r = nvme_port_add(arg_nqn, AF_INET, &context.ipv4_port); + if (r < 0) + return r; + + nvme_port_report(context.ipv4_port); + + if (socket_ipv6_is_enabled()) { + r = nvme_port_add(arg_nqn, AF_INET6, &context.ipv6_port); + if (r < 0) + return r; + + nvme_port_report(context.ipv6_port); + } + + NvmeSubsystem *i; + HASHMAP_FOREACH(i, context.subsystems) { + if (context.ipv4_port) { + r = nvme_port_link_subsystem(context.ipv4_port, i); + if (r < 0) + return r; + } + + if (context.ipv6_port) { + r = nvme_port_link_subsystem(context.ipv6_port, i); + if (r < 0) + return r; + } + + (void) nvme_subsystem_report(i, context.ipv4_port, context.ipv6_port); + } + + if (arg_all > 0) { + r = sd_device_monitor_new(&monitor); + if (r < 0) + return log_error_errno(r, "Failed to allocate device monitor: %m"); + + r = sd_device_monitor_filter_add_match_subsystem_devtype(monitor, "block", "disk"); + if (r < 0) + return log_error_errno(r, "Failed to configure device monitor match: %m"); + + r = sd_device_monitor_attach_event(monitor, event); + if (r < 0) + return log_error_errno(r, "Failed to attach device monitor to event loop: %m"); + + r = sd_device_monitor_start(monitor, device_monitor_handler, &context); + if (r < 0) + return log_error_errno(r, "Failed to start device monitor: %m"); + + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerator = NULL; + r = sd_device_enumerator_new(&enumerator); + if (r < 0) + return log_error_errno(r, "Failed to allocate enumerator: %m"); + + r = sd_device_enumerator_add_match_subsystem(enumerator, "block", /* match= */ true); + if (r < 0) + return log_error_errno(r, "Failed to match block devices: %m"); + + r = sd_device_enumerator_add_match_property(enumerator, "DEVTYPE", "disk"); + if (r < 0) + return log_error_errno(r, "Failed to match whole block devices: %m"); + + r = sd_device_enumerator_add_nomatch_sysname(enumerator, "loop*"); + if (r < 0) + return log_error_errno(r, "Failed to exclude loop devices: %m"); + + FOREACH_DEVICE(enumerator, device) + device_added(&context, device); + } + + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + r = sd_netlink_attach_event(rtnl, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach netlink socket to event loop: %m"); + + r = sd_netlink_add_match(rtnl, /* ret_slot= */ NULL, RTM_NEWADDR, on_address_change, /* destroy_callback= */ NULL, &context, "storagetm-newaddr"); + if (r < 0) + return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR events: %m"); + + r = sd_netlink_add_match(rtnl, /* ret_slot= */ NULL, RTM_DELADDR, on_address_change, /* destroy_callback= */ NULL, &context, "storagetm-deladdr"); + if (r < 0) + return log_error_errno(r, "Failed to subscribe to RTM_DELADDR events: %m"); + + if (isatty(0) > 0) + log_info("Hit Ctrl-C to exit target mode."); + + _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = + notify_start("READY=1\n" + "STATUS=Exposing disks in target mode...", + NOTIFY_STOPPING); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + log_info("Exiting target mode."); + return r; +} + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/sulogin-shell/sulogin-shell.c b/src/sulogin-shell/sulogin-shell.c index f2de30cc3f..b26663df08 100644 --- a/src/sulogin-shell/sulogin-shell.c +++ b/src/sulogin-shell/sulogin-shell.c @@ -70,7 +70,7 @@ static int fork_wait(const char* const cmdline[]) { pid_t pid; int r; - r = safe_fork("(sulogin)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); + r = safe_fork("(sulogin)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); if (r < 0) return r; if (r == 0) { diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 858868c0c5..8dc515e4d5 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -973,7 +973,7 @@ static int merge(ImageClass image_class, pid_t pid; int r; - r = safe_fork("(sd-merge)", FORK_DEATHSIG|FORK_LOG|FORK_NEW_MOUNTNS, &pid); + r = safe_fork("(sd-merge)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_NEW_MOUNTNS, &pid); if (r < 0) return log_error_errno(r, "Failed to fork off child: %m"); if (r == 0) { diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index 6e2238f2a5..d93bffb759 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -84,7 +84,7 @@ static int load_kexec_kernel(void) { if (arg_dry_run) return 0; - r = safe_fork("(kexec)", FORK_WAIT|FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); + r = safe_fork("(kexec)", FORK_WAIT|FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); if (r < 0) return r; if (r == 0) { diff --git a/src/systemctl/systemctl-sysv-compat.c b/src/systemctl/systemctl-sysv-compat.c index 37fc6347fe..2aa1ec6d83 100644 --- a/src/systemctl/systemctl-sysv-compat.c +++ b/src/systemctl/systemctl-sysv-compat.c @@ -210,7 +210,7 @@ int enable_sysv_units(const char *verb, char **args) { if (!arg_quiet) log_info("Executing: %s", l); - j = safe_fork("(sysv-install)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); + j = safe_fork("(sysv-install)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); if (j < 0) return j; if (j == 0) { diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h index 7edfeb7687..4119c453fb 100644 --- a/src/systemd/sd-netlink.h +++ b/src/systemd/sd-netlink.h @@ -67,6 +67,7 @@ int sd_netlink_add_match(sd_netlink *nl, sd_netlink_slot **ret_slot, uint16_t ma int sd_netlink_attach_event(sd_netlink *nl, sd_event *e, int64_t priority); int sd_netlink_detach_event(sd_netlink *nl); +sd_event* sd_netlink_get_event(sd_netlink *nl); int sd_netlink_attach_filter(sd_netlink *nl, size_t len, const struct sock_filter *filter); /* Message construction */ diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index 15e89336de..e4bdd88284 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -292,7 +292,7 @@ static int download_manifest( r = safe_fork_full("(sd-pull)", (int[]) { -EBADF, pfd[1], STDERR_FILENO }, NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG, &pid); if (r < 0) return r; diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index acf75e51c7..f8f4a154f9 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -793,7 +793,7 @@ static int run_helper( assert(path); assert(cmdline); - r = safe_fork(name, FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork(name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); if (r < 0) return r; if (r == 0) { diff --git a/src/test/test-async.c b/src/test/test-async.c index b5a4f5904b..75bc4d8238 100644 --- a/src/test/test-async.c +++ b/src/test/test-async.c @@ -30,7 +30,7 @@ TEST(asynchronous_close) { assert_se(fcntl(fd, F_GETFD) == -1); assert_se(errno == EBADF); - r = safe_fork("(subreaper)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(subreaper)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT, NULL); assert(r >= 0); if (r == 0) { @@ -63,7 +63,7 @@ TEST(asynchronous_rm_rf) { /* Do this once more, form a subreaper. Which is nice, because we can watch the async child even * though detached */ - r = safe_fork("(subreaper)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(subreaper)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); assert_se(r >= 0); if (r == 0) { diff --git a/src/test/test-capability.c b/src/test/test-capability.c index 2f93fbeede..e8a0569737 100644 --- a/src/test/test-capability.c +++ b/src/test/test-capability.c @@ -254,7 +254,7 @@ static void test_capability_get_ambient(void) { assert_se(capability_get_ambient(&c) >= 0); - r = safe_fork("(getambient)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_WAIT|FORK_LOG, NULL); + r = safe_fork("(getambient)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG, NULL); assert_se(r >= 0); if (r == 0) { diff --git a/src/test/test-data-fd-util.c b/src/test/test-data-fd-util.c index d3bb0e3f5f..aa68132e45 100644 --- a/src/test/test-data-fd-util.c +++ b/src/test/test-data-fd-util.c @@ -115,7 +115,7 @@ TEST(copy_data_fd) { assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, sfd) >= 0); - r = safe_fork("(sd-pipe)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid); + r = safe_fork("(sd-pipe)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &pid); assert_se(r >= 0); if (r == 0) { diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c index a6b5f2f347..dffbad6327 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -475,7 +475,7 @@ TEST(setenv_systemd_exec_pid) { TEST(getenv_steal_erase) { int r; - r = safe_fork("(sd-getenvstealerase)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(sd-getenvstealerase)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); if (r == 0) { _cleanup_strv_free_ char **l = NULL; diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 6156c9caa7..ab0c4e905d 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -602,7 +602,7 @@ static int find_libraries(const char *exec, char ***ret) { r = safe_fork_full("(spawn-ldd)", (int[]) { -EBADF, outpipe[1], errpipe[1] }, NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG, &pid); + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG, &pid); assert_se(r >= 0); if (r == 0) { execlp("ldd", "ldd", exec, NULL); @@ -1299,7 +1299,7 @@ static int prepare_ns(const char *process_name) { r = safe_fork(process_name, FORK_RESET_SIGNALS | FORK_CLOSE_ALL_FDS | - FORK_DEATHSIG | + FORK_DEATHSIG_SIGTERM | FORK_WAIT | FORK_REOPEN_LOG | FORK_LOG | diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index 9c74d88abc..021d4b47c2 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -337,7 +337,7 @@ TEST(close_all_fds) { * with /proc/ overmounted, and once with the combination of both. This should trigger all fallbacks * in the close_range_all() function. */ - r = safe_fork("(caf-plain)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(caf-plain)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); if (r == 0) { test_close_all_fds_inner(); _exit(EXIT_SUCCESS); @@ -347,7 +347,7 @@ TEST(close_all_fds) { if (geteuid() != 0) return (void) log_tests_skipped("Lacking privileges for test with close_range() blocked and /proc/ overmounted"); - r = safe_fork("(caf-noproc)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL); + r = safe_fork("(caf-noproc)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL); if (r == 0) { r = mount_nofollow_verbose(LOG_WARNING, "tmpfs", "/proc", "tmpfs", 0, NULL); if (r < 0) @@ -361,7 +361,7 @@ TEST(close_all_fds) { if (!is_seccomp_available()) return (void) log_tests_skipped("Seccomp not available"); - r = safe_fork("(caf-seccomp)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(caf-seccomp)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); if (r == 0) { r = seccomp_prohibit_close_range(); if (r < 0) @@ -373,7 +373,7 @@ TEST(close_all_fds) { } assert_se(r >= 0); - r = safe_fork("(caf-scnp)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL); + r = safe_fork("(caf-scnp)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL); if (r == 0) { r = seccomp_prohibit_close_range(); if (r < 0) diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index d76a4ced91..ad98a92060 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -969,7 +969,7 @@ TEST(read_full_file_socket) { /* Bind the *client* socket to some randomized name, to verify that this works correctly. */ assert_se(asprintf(&clientname, "@%" PRIx64 "/test-bindname", random_u64()) >= 0); - r = safe_fork("(server)", FORK_DEATHSIG|FORK_LOG, &pid); + r = safe_fork("(server)", FORK_DEATHSIG_SIGTERM|FORK_LOG, &pid); assert_se(r >= 0); if (r == 0) { union sockaddr_union peer = {}; diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c index 9cf0bf42bf..39f71c67d3 100644 --- a/src/test/test-locale-util.c +++ b/src/test/test-locale-util.c @@ -82,7 +82,7 @@ TEST(keymaps) { #define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x)) TEST(dump_special_glyphs) { - assert_cc(SPECIAL_GLYPH_WARNING_SIGN + 1 == _SPECIAL_GLYPH_MAX); + assert_cc(SPECIAL_GLYPH_WORLD + 1 == _SPECIAL_GLYPH_MAX); log_info("is_locale_utf8: %s", yes_no(is_locale_utf8())); @@ -125,6 +125,8 @@ TEST(dump_special_glyphs) { dump_glyph(SPECIAL_GLYPH_SPARKLES); dump_glyph(SPECIAL_GLYPH_LOW_BATTERY); dump_glyph(SPECIAL_GLYPH_WARNING_SIGN); + dump_glyph(SPECIAL_GLYPH_COMPUTER_DISK); + dump_glyph(SPECIAL_GLYPH_WORLD); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-lock-util.c b/src/test/test-lock-util.c index 28fc54a5d6..5edd087582 100644 --- a/src/test/test-lock-util.c +++ b/src/test/test-lock-util.c @@ -35,4 +35,31 @@ TEST(make_lock_file) { assert_se(make_lock_file("lock", LOCK_EX|LOCK_NB, &lock2) == -EBUSY); } +static void test_lock_generic_with_timeout_for_type(LockType type) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, tfd2 = -EBADF; + + tfd = mkdtemp_open(NULL, 0, &t); + assert_se(tfd >= 0); + + tfd2 = fd_reopen(tfd, O_CLOEXEC|O_DIRECTORY); + assert_se(tfd2 >= 0); + + assert_se(lock_generic(tfd, LOCK_BSD, LOCK_EX) >= 0); + assert_se(lock_generic(tfd2, LOCK_BSD, LOCK_EX|LOCK_NB) == -EWOULDBLOCK); + + usec_t start = now(CLOCK_MONOTONIC); + assert_se(lock_generic_with_timeout(tfd2, LOCK_BSD, LOCK_EX, 200 * USEC_PER_MSEC) == -ETIMEDOUT); + assert_se(usec_sub_unsigned(now(CLOCK_MONOTONIC), start) >= 200 * USEC_PER_MSEC); + + assert_se(lock_generic(tfd, LOCK_BSD, LOCK_UN) >= 0); + assert_se(lock_generic_with_timeout(tfd2, LOCK_BSD, LOCK_EX, 200 * USEC_PER_MSEC) == 0); + assert_se(lock_generic(tfd, LOCK_BSD, LOCK_EX|LOCK_NB) == -EWOULDBLOCK); +} + +TEST(lock_generic_with_timeout) { + test_lock_generic_with_timeout_for_type(LOCK_BSD); + test_lock_generic_with_timeout_for_type(LOCK_UNPOSIX); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-mempress.c b/src/test/test-mempress.c index 69928f3de0..26ce4cee79 100644 --- a/src/test/test-mempress.c +++ b/src/test/test-mempress.c @@ -226,7 +226,7 @@ TEST(real_pressure) { assert_se(pipe2(pipe_fd, O_CLOEXEC) >= 0); - r = safe_fork("(eat-memory)", FORK_RESET_SIGNALS|FORK_DEATHSIG, &pid); + r = safe_fork("(eat-memory)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pid); assert_se(r >= 0); if (r == 0) { real_pressure_eat_memory(pipe_fd[0]); diff --git a/src/test/test-mkdir.c b/src/test/test-mkdir.c index 03e5a4997c..4820b3251a 100644 --- a/src/test/test-mkdir.c +++ b/src/test/test-mkdir.c @@ -80,7 +80,7 @@ TEST(mkdir_p_safe) { p = mfree(p); assert_se(p = path_join(tmp, "zero-mode/should-fail-to-create-child")); assert_se(mkdir_parents_safe(tmp, p, 0000, UID_INVALID, GID_INVALID, 0) >= 0); - r = safe_fork("(test-mkdir-no-cap)", FORK_DEATHSIG | FORK_WAIT | FORK_LOG, NULL); + r = safe_fork("(test-mkdir-no-cap)", FORK_DEATHSIG_SIGTERM | FORK_WAIT | FORK_LOG, NULL); if (r == 0) { (void) capability_bounding_set_drop(0, /* right_now = */ true); assert_se(mkdir_p_safe(tmp, p, 0000, UID_INVALID, GID_INVALID, 0) == -EACCES); diff --git a/src/test/test-mount-util.c b/src/test/test-mount-util.c index 1cfc1f3ae5..c3d0acb6af 100644 --- a/src/test/test-mount-util.c +++ b/src/test/test-mount-util.c @@ -280,7 +280,7 @@ TEST(make_mount_switch_root) { r = safe_fork("(switch-root)", FORK_RESET_SIGNALS | FORK_CLOSE_ALL_FDS | - FORK_DEATHSIG | + FORK_DEATHSIG_SIGTERM | FORK_WAIT | FORK_REOPEN_LOG | FORK_LOG | @@ -332,7 +332,7 @@ TEST(umount_recursive) { r = safe_fork("(umount-rec)", FORK_RESET_SIGNALS | FORK_CLOSE_ALL_FDS | - FORK_DEATHSIG | + FORK_DEATHSIG_SIGTERM | FORK_WAIT | FORK_REOPEN_LOG | FORK_LOG | @@ -406,7 +406,7 @@ TEST(fd_make_mount_point) { r = safe_fork("(make_mount-point)", FORK_RESET_SIGNALS | FORK_CLOSE_ALL_FDS | - FORK_DEATHSIG | + FORK_DEATHSIG_SIGTERM | FORK_WAIT | FORK_REOPEN_LOG | FORK_LOG | diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index 22fb1fd278..09ad82d239 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -634,7 +634,7 @@ TEST(safe_fork) { BLOCK_SIGNALS(SIGCHLD); - r = safe_fork("(test-child)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_REOPEN_LOG, &pid); + r = safe_fork("(test-child)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_REOPEN_LOG, &pid); assert_se(r >= 0); if (r == 0) { @@ -701,7 +701,7 @@ TEST(setpriority_closest) { int r; r = safe_fork("(test-setprio)", - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT|FORK_LOG, NULL); + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG, NULL); assert_se(r >= 0); if (r == 0) { @@ -855,7 +855,7 @@ TEST(get_process_threads) { int r; /* Run this test in a child, so that we can guarantee there's exactly one thread around in the child */ - r = safe_fork("(nthreads)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_REOPEN_LOG|FORK_WAIT|FORK_LOG, NULL); + r = safe_fork("(nthreads)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_WAIT|FORK_LOG, NULL); assert_se(r >= 0); if (r == 0) { diff --git a/src/test/test-rm-rf.c b/src/test/test-rm-rf.c index 6a8b7d823f..4c69bd28c9 100644 --- a/src/test/test-rm-rf.c +++ b/src/test/test-rm-rf.c @@ -93,7 +93,7 @@ TEST(rm_rf_chmod) { /* This test only works unpriv (as only then the access mask for the owning user matters), * hence drop privs here */ - r = safe_fork("(setresuid)", FORK_DEATHSIG|FORK_WAIT, NULL); + r = safe_fork("(setresuid)", FORK_DEATHSIG_SIGTERM|FORK_WAIT, NULL); assert_se(r >= 0); if (r == 0) { diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index b5fb437c5b..e9c776a8c5 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -158,7 +158,7 @@ TEST(in_addr_is_multicast) { TEST(getpeercred_getpeergroups) { int r; - r = safe_fork("(getpeercred)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(getpeercred)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); assert_se(r >= 0); if (r == 0) { @@ -228,7 +228,7 @@ TEST(passfd_read) { assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); - r = safe_fork("(passfd_read)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(passfd_read)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); assert_se(r >= 0); if (r == 0) { @@ -270,7 +270,7 @@ TEST(passfd_contents_read) { assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); - r = safe_fork("(passfd_contents_read)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(passfd_contents_read)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); assert_se(r >= 0); if (r == 0) { @@ -322,7 +322,7 @@ TEST(pass_many_fds_contents_read) { assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); - r = safe_fork("(passfd_contents_read)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(passfd_contents_read)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); assert_se(r >= 0); if (r == 0) { @@ -382,7 +382,7 @@ TEST(receive_nopassfd) { assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); - r = safe_fork("(receive_nopassfd)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(receive_nopassfd)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); assert_se(r >= 0); if (r == 0) { @@ -418,7 +418,7 @@ TEST(send_nodata_nofd) { assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); - r = safe_fork("(send_nodata_nofd)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(send_nodata_nofd)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); assert_se(r >= 0); if (r == 0) { @@ -451,7 +451,7 @@ TEST(send_emptydata) { assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); - r = safe_fork("(send_emptydata)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL); + r = safe_fork("(send_emptydata)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); assert_se(r >= 0); if (r == 0) { diff --git a/src/udev/udev-manager.c b/src/udev/udev-manager.c index 697dc2f14f..aca241c0c9 100644 --- a/src/udev/udev-manager.c +++ b/src/udev/udev-manager.c @@ -381,7 +381,7 @@ static int worker_spawn(Manager *manager, Event *event) { if (r < 0) return log_error_errno(r, "Worker: Failed to enable receiving of device: %m"); - r = safe_fork("(udev-worker)", FORK_DEATHSIG, &pid); + r = safe_fork("(udev-worker)", FORK_DEATHSIG_SIGTERM, &pid); if (r < 0) { event->state = EVENT_QUEUED; return log_error_errno(r, "Failed to fork() worker: %m"); diff --git a/src/udev/udev-spawn.c b/src/udev/udev-spawn.c index 9501013d1c..1d0b8548e3 100644 --- a/src/udev/udev-spawn.c +++ b/src/udev/udev-spawn.c @@ -280,7 +280,7 @@ int udev_event_spawn( r = safe_fork_full("(spawn)", (int[]) { -EBADF, outpipe[WRITE_END], errpipe[WRITE_END] }, NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &pid); if (r < 0) return log_device_error_errno(event->dev, r, diff --git a/src/udev/udevadm-lock.c b/src/udev/udevadm-lock.c index 89d7d256d2..bc2d5e76bd 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -10,6 +10,7 @@ #include "device-util.h" #include "fd-util.h" #include "fdset.h" +#include "lock-util.h" #include "main-func.h" #include "parse-util.h" #include "path-util.h" @@ -194,91 +195,31 @@ static int lock_device( if (!S_ISBLK(st.st_mode) || st.st_rdev != devno) return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Path '%s' no longer refers to specified block device %u:%u: %m", path, major(devno), minor(devno)); - if (flock(fd, LOCK_EX|LOCK_NB) < 0) { - - if (errno != EAGAIN) - return log_error_errno(errno, "Failed to lock device '%s': %m", path); + r = lock_generic(fd, LOCK_BSD, LOCK_EX|LOCK_NB); + if (r < 0) { + if (r != -EAGAIN) + return log_error_errno(r, "Failed to lock device '%s': %m", path); if (deadline == 0) return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Device '%s' is currently locked.", path); if (deadline == USEC_INFINITY) { - log_info("Device '%s' is currently locked, waiting%s", path, special_glyph(SPECIAL_GLYPH_ELLIPSIS)); - if (flock(fd, LOCK_EX) < 0) - return log_error_errno(errno, "Failed to lock device '%s': %m", path); - + r = lock_generic(fd, LOCK_BSD, LOCK_EX); } else { - _cleanup_(sigkill_waitp) pid_t flock_pid = 0; - - /* flock() doesn't support a time-out. Let's fake one then. The traditional way to do - * this is via alarm()/setitimer()/timer_create(), but that's racy, given that the - * SIGALRM might already fire between the alarm() and the flock() in which case the - * flock() is never cancelled and we lock up (this is a short time window, but with - * short timeouts on a loaded machine we might run into it, who knows?). Let's - * instead do the lock out-of-process: fork off a child that does the locking, and - * that we'll wait on and kill if it takes too long. */ + usec_t left = usec_sub_unsigned(deadline, now(CLOCK_MONOTONIC)); log_info("Device '%s' is currently locked, waiting %s%s", - path, FORMAT_TIMESPAN(usec_sub_unsigned(deadline, now(CLOCK_MONOTONIC)), 0), + path, FORMAT_TIMESPAN(left, 0), special_glyph(SPECIAL_GLYPH_ELLIPSIS)); - BLOCK_SIGNALS(SIGCHLD); - - r = safe_fork("(timed-flock)", FORK_DEATHSIG|FORK_LOG, &flock_pid); - if (r < 0) - return r; - if (r == 0) { - /* Child */ - - if (flock(fd, LOCK_EX) < 0) { - log_error_errno(errno, "Failed to lock device '%s': %m", path); - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); - } - - for (;;) { - siginfo_t si; - sigset_t ss; - usec_t n; - - assert_se(sigemptyset(&ss) >= 0); - assert_se(sigaddset(&ss, SIGCHLD) >= 0); - - n = now(CLOCK_MONOTONIC); - if (n >= deadline) - return log_error_errno(SYNTHETIC_ERRNO(ETIMEDOUT), "Timeout reached."); - - r = sigtimedwait(&ss, NULL, TIMESPEC_STORE(deadline - n)); - if (r < 0) { - if (errno != EAGAIN) - return log_error_errno(errno, "Failed to wait for SIGCHLD: %m"); - - return log_error_errno(SYNTHETIC_ERRNO(ETIMEDOUT), "Timeout reached."); - } - - assert(r == SIGCHLD); - - zero(si); - - if (waitid(P_PID, flock_pid, &si, WEXITED|WNOHANG|WNOWAIT) < 0) - return log_error_errno(errno, "Failed to wait for child: %m"); - - if (si.si_pid != 0) { - assert(si.si_pid == flock_pid); - - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return log_error_errno(SYNTHETIC_ERRNO(EPROTO), "Unexpected exit status of file lock child."); - - break; - } - - log_debug("Got SIGCHLD for other child, continuing."); - } + r = lock_generic_with_timeout(fd, LOCK_BSD, LOCK_EX, left); + if (r == -ETIMEDOUT) + return log_error_errno(r, "Timeout reached."); } + if (r < 0) + return log_error_errno(r, "Failed to lock device '%s': %m", path); } log_debug("Successfully locked %s (%u:%u)%s", path, major(devno), minor(devno), special_glyph(SPECIAL_GLYPH_ELLIPSIS)); @@ -349,7 +290,7 @@ int lock_main(int argc, char *argv[], void *userdata) { /* Ignore SIGINT and allow the forked process to receive it */ (void) ignore_signals(SIGINT); - r = safe_fork("(lock)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); + r = safe_fork("(lock)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); if (r < 0) return r; if (r == 0) { diff --git a/src/userdb/userdbd-manager.c b/src/userdb/userdbd-manager.c index 73da3fb1e8..62a1fa5f4b 100644 --- a/src/userdb/userdbd-manager.c +++ b/src/userdb/userdbd-manager.c @@ -149,7 +149,7 @@ static int start_one_worker(Manager *m) { "(sd-worker)", /* stdio_fds= */ NULL, &m->listen_fd, 1, - FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_REOPEN_LOG|FORK_LOG|FORK_CLOSE_ALL_FDS, + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_CLOSE_ALL_FDS, &pid); if (r < 0) return log_error_errno(r, "Failed to fork new worker child: %m"); diff --git a/test/TEST-84-STORAGETM/Makefile b/test/TEST-84-STORAGETM/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-84-STORAGETM/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-84-STORAGETM/test.sh b/test/TEST-84-STORAGETM/test.sh new file mode 100755 index 0000000000..8a9730c957 --- /dev/null +++ b/test/TEST-84-STORAGETM/test.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -e + +TEST_DESCRIPTION="test storagetm" + +TEST_NO_NSPAWN=1 + +# shellcheck source=test/test-functions +. "${TEST_BASE_DIR:?}/test-functions" + +test_require_bin nvme + +modprobe -nv nvmet-tcp || exit 0 + +test_append_files() { + inst_binary nvme + + instmods "=nvme" + instmods configfs + + generate_module_dependencies +} + +do_test "$@" diff --git a/test/units/testsuite-84.service b/test/units/testsuite-84.service new file mode 100644 index 0000000000..2c2577019c --- /dev/null +++ b/test/units/testsuite-84.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-84-STORAGETM +After=multi-user.target + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-84.sh b/test/units/testsuite-84.sh new file mode 100755 index 0000000000..954dce2d04 --- /dev/null +++ b/test/units/testsuite-84.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +modprobe -v nvmet-tcp +modprobe -v nvme-tcp + +systemctl start sys-kernel-config.mount + +dd if=/dev/urandom of=/var/tmp/storagetm.test bs=1024 count=10240 + +systemd-run -u teststoragetm.service -p Type=notify /usr/lib/systemd/systemd-storagetm /var/tmp/storagetm.test --nqn=quux + +nvme connect-all -t tcp -a 127.0.0.1 -s 16858 --hostid="$(cat /proc/sys/kernel/random/uuid)" + +dd if=/dev/nvme1n1 bs=1024 | cmp /var/tmp/storagetm.test - + +nvme disconnect --device=nvme1 + +systemctl stop teststoragetm.service + +rm /var/tmp/storagetm.test + +touch /testok diff --git a/units/meson.build b/units/meson.build index 493e294002..51ae9cee5d 100644 --- a/units/meson.build +++ b/units/meson.build @@ -559,6 +559,14 @@ units = [ 'conditions' : ['ENABLE_SYSUSERS'], 'symlinks' : ['sysinit.target.wants/'], }, + { + 'file' : 'systemd-storagetm.service.in', + 'conditions' : ['ENABLE_STORAGETM'], + }, + { + 'file' : 'storage-target-mode.target', + 'conditions' : ['ENABLE_STORAGETM'], + }, { 'file' : 'systemd-time-wait-sync.service.in', 'conditions' : ['ENABLE_TIMESYNCD'], diff --git a/units/storage-target-mode.target b/units/storage-target-mode.target new file mode 100644 index 0000000000..38f78a286e --- /dev/null +++ b/units/storage-target-mode.target @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# 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. + +[Unit] +Description=Storage Target Mode +Documentation=man:systemd.special(7) +Wants=systemd-storagetm.service systemd-udevd.service systemd-udev-trigger.service systemd-networkd.service systemd-network-generator.service systemd-journald.socket systemd-journald-dev-log.socket +Conflicts=rescue.service rescue.target +After=systemd-storagetm.service systemd-udevd.service systemd-udev-trigger.service systemd-networkd.service systemd-network-generator.service systemd-journald.socket systemd-journald-dev-log.socket rescue.service rescue.target +AllowIsolate=yes diff --git a/units/systemd-storagetm.service.in b/units/systemd-storagetm.service.in new file mode 100644 index 0000000000..0fe91eff27 --- /dev/null +++ b/units/systemd-storagetm.service.in @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# 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. + +[Unit] +Description=Storage Target Mode (NVMe-TCP) +Documentation=man:systemd-storagetm.service(8) +ConditionVirtualization=!container +DefaultDependencies=no +Wants=modprobe@nvmet_tcp.service modprobe@thunderbolt_net.service sys-kernel-config.mount +After=modprobe@nvmet_tcp.service modprobe@thunderbolt_net.service sys-kernel-config.mount +Conflicts=shutdown.target +Before=shutdown.target +FailureAction=reboot +SuccessAction=reboot + +[Service] +Type=notify +RemainAfterExit=yes +StandardInput=tty +StandardOutput=tty +ExecStart={{LIBEXECDIR}}/systemd-storagetm --all