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