diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index dd144a6cba..3b7cad5b47 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -2790,6 +2790,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(sst) OpenFile = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly as ExtraFileDescriptorNames = ['...', ...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i ReloadSignal = ...;
readonly t ExecMainStartTimestamp = ...;
readonly t ExecMainStartTimestampMonotonic = ...;
@@ -4098,6 +4100,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+
@@ -4843,6 +4847,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
StateDirectory, CacheDirectory and LogsDirectory,
which will create a symlink of the given name to the respective directory. The messages take an unused
flags parameter, reserved for future backward-compatible changes.
+
+ ExtraFileDescriptorNames contains file descriptor names passed to the service via
+ the ExtraFileDescriptors property in the StartTransientUnit()
+ method. See sd_listen_fds3
+ for more details on how to retrieve these file descriptors. Unlike the ExtraFileDescriptors
+ input property, ExtraFileDescriptorNames only contains names and not the file descriptors.
@@ -12209,6 +12219,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
LiveMountResult,
PrivateTmpEx,
ImportCredentialEx,
+ ExtraFileDescriptorNames,
BindLogSockets, and
PrivateUsersEx were added in version 257.
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c
index 43a8fb0617..a0244c1860 100644
--- a/src/core/dbus-service.c
+++ b/src/core/dbus-service.c
@@ -69,6 +69,34 @@ static int property_get_open_files(
return sd_bus_message_close_container(reply);
}
+static int property_get_extra_file_descriptors(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ ServiceExtraFD **extra_fds = ASSERT_PTR(userdata);
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ r = sd_bus_message_open_container(reply, 'a', "s");
+ if (r < 0)
+ return r;
+
+ LIST_FOREACH(extra_fd, efd, *extra_fds) {
+ r = sd_bus_message_append_basic(reply, 's', efd->fdname);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
static int property_get_exit_status_set(
sd_bus *bus,
const char *path,
@@ -339,6 +367,7 @@ const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_PROPERTY("NRestarts", "u", bus_property_get_unsigned, offsetof(Service, n_restarts), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("OOMPolicy", "s", bus_property_get_oom_policy, offsetof(Service, oom_policy), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OpenFile", "a(sst)", property_get_open_files, offsetof(Service, open_files), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("ExtraFileDescriptorNames", "as", property_get_extra_file_descriptors, offsetof(Service, extra_fds), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ReloadSignal", "i", bus_property_get_int, offsetof(Service, reload_signal), SD_BUS_VTABLE_PROPERTY_CONST),
BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
@@ -718,6 +747,58 @@ static int bus_service_set_transient_property(
if (streq(name, "ReloadSignal"))
return bus_set_transient_reload_signal(u, name, &s->reload_signal, message, flags, error);
+ if (streq(name, "ExtraFileDescriptors")) {
+ int fd;
+ const char *fdname;
+
+ r = sd_bus_message_enter_container(message, 'a', "(hs)");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ _cleanup_(service_extra_fd_freep) ServiceExtraFD *efd = NULL;
+
+ r = sd_bus_message_read(message, "(hs)", &fd, &fdname);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ /* Disallow empty string for ExtraFileDescriptors.
+ * Unlike OpenFile, StandardInput and friends, there isn't a good sane
+ * default for an arbitrary FD. */
+ if (fd < 0 || isempty(fdname) || !fdname_is_valid(fdname))
+ return -EINVAL;
+
+ if (UNIT_WRITE_FLAGS_NOOP(flags))
+ continue;
+
+ efd = new(ServiceExtraFD, 1);
+ if (!efd)
+ return -ENOMEM;
+
+ *efd = (ServiceExtraFD) {
+ .fd = -EBADF,
+ .fdname = strdup(fdname),
+ };
+
+ if (!efd->fdname)
+ return -ENOMEM;
+
+ efd->fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (efd->fd < 0)
+ return -errno;
+
+ LIST_APPEND(extra_fd, s->extra_fds, TAKE_PTR(efd));
+ }
+
+ r = sd_bus_message_exit_container(message);
+ if (r < 0)
+ return r;
+
+ return 1;
+ }
+
return 0;
}
diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c
index becc2b92ef..2bd43a95dd 100644
--- a/src/core/exec-invoke.c
+++ b/src/core/exec-invoke.c
@@ -3912,7 +3912,7 @@ static int exec_context_named_iofds(
for (size_t i = 0; i < 3; i++)
stdio_fdname[i] = exec_context_fdname(c, i);
- n_fds = p->n_storage_fds + p->n_socket_fds;
+ n_fds = p->n_storage_fds + p->n_socket_fds + p->n_extra_fds;
for (size_t i = 0; i < n_fds && targets > 0; i++)
if (named_iofds[STDIN_FILENO] < 0 &&
@@ -4096,7 +4096,7 @@ int exec_invoke(
int ngids_after_pam = 0;
int socket_fd = -EBADF, named_iofds[3] = EBADF_TRIPLET;
- size_t n_storage_fds, n_socket_fds;
+ size_t n_storage_fds, n_socket_fds, n_extra_fds;
assert(command);
assert(context);
@@ -4133,12 +4133,13 @@ int exec_invoke(
return log_exec_error_errno(context, params, SYNTHETIC_ERRNO(EINVAL), "Got no socket.");
socket_fd = params->fds[0];
- n_storage_fds = n_socket_fds = 0;
+ n_storage_fds = n_socket_fds = n_extra_fds = 0;
} else {
n_socket_fds = params->n_socket_fds;
n_storage_fds = params->n_storage_fds;
+ n_extra_fds = params->n_extra_fds;
}
- n_fds = n_socket_fds + n_storage_fds;
+ n_fds = n_socket_fds + n_storage_fds + n_extra_fds;
r = exec_context_named_iofds(context, params, named_iofds);
if (r < 0)
diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c
index b3035c0026..13e7078b1a 100644
--- a/src/core/execute-serialize.c
+++ b/src/core/execute-serialize.c
@@ -1282,7 +1282,13 @@ static int exec_parameters_serialize(const ExecParameters *p, const ExecContext
return r;
}
- r = serialize_fd_many(f, fds, "exec-parameters-fds", p->fds, p->n_socket_fds + p->n_storage_fds);
+ if (p->n_extra_fds > 0) {
+ r = serialize_item_format(f, "exec-parameters-n-extra-fds", "%zu", p->n_extra_fds);
+ if (r < 0)
+ return r;
+ }
+
+ r = serialize_fd_many(f, fds, "exec-parameters-fds", p->fds, p->n_socket_fds + p->n_storage_fds + p->n_extra_fds);
if (r < 0)
return r;
}
@@ -1478,27 +1484,37 @@ static int exec_parameters_deserialize(ExecParameters *p, FILE *f, FDSet *fds) {
if (p->n_storage_fds > (size_t) nr_open)
return -EINVAL; /* too many, someone is playing games with us */
+ } else if ((val = startswith(l, "exec-parameters-n-extra-fds="))) {
+ if (p->fds)
+ return -EINVAL; /* Already received */
+
+ r = safe_atozu(val, &p->n_extra_fds);
+ if (r < 0)
+ return r;
+
+ if (p->n_extra_fds > (size_t) nr_open)
+ return -EINVAL; /* too many, someone is playing games with us */
} else if ((val = startswith(l, "exec-parameters-fds="))) {
- if (p->n_socket_fds + p->n_storage_fds == 0)
+ if (p->n_socket_fds + p->n_storage_fds + p->n_extra_fds == 0)
return log_warning_errno(
SYNTHETIC_ERRNO(EINVAL),
"Got exec-parameters-fds= without "
- "prior exec-parameters-n-socket-fds= or exec-parameters-n-storage-fds=");
- if (p->n_socket_fds + p->n_storage_fds > (size_t) nr_open)
+ "prior exec-parameters-n-socket-fds= or exec-parameters-n-storage-fds= or exec-parameters-n-extra-fds=");
+ if (p->n_socket_fds + p->n_storage_fds + p->n_extra_fds > (size_t) nr_open)
return -EINVAL; /* too many, someone is playing games with us */
if (p->fds)
return -EINVAL; /* duplicated */
- p->fds = new(int, p->n_socket_fds + p->n_storage_fds);
+ p->fds = new(int, p->n_socket_fds + p->n_storage_fds + p->n_extra_fds);
if (!p->fds)
return log_oom_debug();
/* Ensure we don't leave any FD uninitialized on error, it makes the fuzzer sad */
- FOREACH_ARRAY(i, p->fds, p->n_socket_fds + p->n_storage_fds)
+ FOREACH_ARRAY(i, p->fds, p->n_socket_fds + p->n_storage_fds + p->n_extra_fds)
*i = -EBADF;
- r = deserialize_fd_many(fds, val, p->n_socket_fds + p->n_storage_fds, p->fds);
+ r = deserialize_fd_many(fds, val, p->n_socket_fds + p->n_storage_fds + p->n_extra_fds, p->fds);
if (r < 0)
continue;
diff --git a/src/core/execute.c b/src/core/execute.c
index 30fcffcc5b..0c2c278d69 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -391,7 +391,7 @@ int exec_spawn(
assert(context);
assert(params);
assert(!params->fds || FLAGS_SET(params->flags, EXEC_PASS_FDS));
- assert(params->fds || (params->n_socket_fds + params->n_storage_fds == 0));
+ assert(params->fds || (params->n_socket_fds + params->n_storage_fds + params->n_extra_fds == 0));
assert(!params->files_env); /* We fill this field, ensure it comes NULL-initialized to us */
assert(ret);
@@ -2632,7 +2632,7 @@ void exec_params_deep_clear(ExecParameters *p) {
* to be fully cleaned up to make sanitizers and analyzers happy, as opposed as the shallow clean
* function above. */
- close_many_unset(p->fds, p->n_socket_fds + p->n_storage_fds);
+ close_many_unset(p->fds, p->n_socket_fds + p->n_storage_fds + p->n_extra_fds);
p->cgroup_path = mfree(p->cgroup_path);
diff --git a/src/core/execute.h b/src/core/execute.h
index 01a196748b..29c08d48b9 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -421,6 +421,7 @@ struct ExecParameters {
char **fd_names;
size_t n_socket_fds;
size_t n_storage_fds;
+ size_t n_extra_fds;
ExecFlags flags;
bool selinux_context_net:1;
diff --git a/src/core/fuzz-execute-serialize.c b/src/core/fuzz-execute-serialize.c
index 5b2dc952ad..05abee5a4e 100644
--- a/src/core/fuzz-execute-serialize.c
+++ b/src/core/fuzz-execute-serialize.c
@@ -58,8 +58,8 @@ static void exec_fuzz_one(FILE *f, FDSet *fdset) {
params.user_lookup_fd = -EBADF;
params.bpf_restrict_fs_map_fd = -EBADF;
if (!params.fds)
- params.n_socket_fds = params.n_storage_fds = 0;
- for (size_t i = 0; params.fds && i < params.n_socket_fds + params.n_storage_fds; i++)
+ params.n_socket_fds = params.n_storage_fds = params.n_extra_fds = 0;
+ for (size_t i = 0; params.fds && i < params.n_socket_fds + params.n_storage_fds + params.n_extra_fds; i++)
params.fds[i] = -EBADF;
exec_command_done_array(&command, /* n= */ 1);
diff --git a/src/core/service.c b/src/core/service.c
index e5db7a085b..7162016313 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -454,6 +454,21 @@ static void service_release_fd_store(Service *s) {
assert(s->n_fd_store == 0);
}
+ServiceExtraFD* service_extra_fd_free(ServiceExtraFD *efd) {
+ if (!efd)
+ return NULL;
+
+ efd->fd = asynchronous_close(efd->fd);
+ free(efd->fdname);
+ return mfree(efd);
+}
+
+static void service_release_extra_fds(Service *s) {
+ assert(s);
+
+ LIST_CLEAR(extra_fd, s->extra_fds, service_extra_fd_free);
+}
+
static void service_release_stdio_fd(Service *s) {
assert(s);
@@ -510,6 +525,7 @@ static void service_done(Unit *u) {
service_release_socket_fd(s);
service_release_stdio_fd(s);
service_release_fd_store(s);
+ service_release_extra_fds(s);
s->mount_request = sd_bus_message_unref(s->mount_request);
}
@@ -903,40 +919,59 @@ static int service_load(Unit *u) {
return service_verify(s);
}
+static int service_dump_fd(int fd, const char *fdname, const char *header, FILE *f, const char *prefix) {
+ _cleanup_free_ char *path = NULL;
+ struct stat st;
+ int flags;
+
+ if (fstat(fd, &st) < 0)
+ return log_debug_errno(errno, "Failed to stat fdstore entry: %m");
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return log_debug_errno(errno, "Failed to get fdstore entry flags: %m");
+
+ (void) fd_get_path(fd, &path);
+
+ fprintf(f,
+ "%s%s '%s' (type=%s; dev=" DEVNUM_FORMAT_STR "; inode=%" PRIu64 "; rdev=" DEVNUM_FORMAT_STR "; path=%s; access=%s)\n",
+ prefix,
+ header,
+ fdname,
+ strna(inode_type_to_string(st.st_mode)),
+ DEVNUM_FORMAT_VAL(st.st_dev),
+ (uint64_t) st.st_ino,
+ DEVNUM_FORMAT_VAL(st.st_rdev),
+ strna(path),
+ strna(accmode_to_string(flags)));
+
+ return 0;
+}
+
static void service_dump_fdstore(Service *s, FILE *f, const char *prefix) {
assert(s);
assert(f);
assert(prefix);
- LIST_FOREACH(fd_store, i, s->fd_store) {
- _cleanup_free_ char *path = NULL;
- struct stat st;
- int flags;
+ LIST_FOREACH(fd_store, i, s->fd_store)
+ (void) service_dump_fd(i->fd,
+ i->fdname,
+ i == s->fd_store ? "File Descriptor Store Entry:" : " ",
+ f,
+ prefix);
+}
- if (fstat(i->fd, &st) < 0) {
- log_debug_errno(errno, "Failed to stat fdstore entry: %m");
- continue;
- }
+static void service_dump_extra_fds(Service *s, FILE *f, const char *prefix) {
+ assert(s);
+ assert(f);
+ assert(prefix);
- flags = fcntl(i->fd, F_GETFL);
- if (flags < 0) {
- log_debug_errno(errno, "Failed to get fdstore entry flags: %m");
- continue;
- }
-
- (void) fd_get_path(i->fd, &path);
-
- fprintf(f,
- "%s%s '%s' (type=%s; dev=" DEVNUM_FORMAT_STR "; inode=%" PRIu64 "; rdev=" DEVNUM_FORMAT_STR "; path=%s; access=%s)\n",
- prefix, i == s->fd_store ? "File Descriptor Store Entry:" : " ",
- i->fdname,
- strna(inode_type_to_string(st.st_mode)),
- DEVNUM_FORMAT_VAL(st.st_dev),
- (uint64_t) st.st_ino,
- DEVNUM_FORMAT_VAL(st.st_rdev),
- strna(path),
- strna(accmode_to_string(flags)));
- }
+ LIST_FOREACH(extra_fd, i, s->extra_fds)
+ (void) service_dump_fd(i->fd,
+ i->fdname,
+ i == s->extra_fds ? "Extra File Descriptor Entry:" : " ",
+ f,
+ prefix);
}
static void service_dump(Unit *u, FILE *f, const char *prefix) {
@@ -1093,6 +1128,8 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
fprintf(f, "%sOpen File: %s\n", prefix, ofs);
}
+ service_dump_extra_fds(s, f, prefix);
+
cgroup_context_dump(UNIT(s), f, prefix);
}
@@ -1423,11 +1460,12 @@ static int service_collect_fds(
int **fds,
char ***fd_names,
size_t *n_socket_fds,
- size_t *n_storage_fds) {
+ size_t *n_storage_fds,
+ size_t *n_extra_fds) {
_cleanup_strv_free_ char **rfd_names = NULL;
_cleanup_free_ int *rfds = NULL;
- size_t rn_socket_fds = 0, rn_storage_fds = 0;
+ size_t rn_socket_fds = 0, rn_storage_fds = 0, rn_extra_fds = 0;
int r;
assert(s);
@@ -1435,6 +1473,7 @@ static int service_collect_fds(
assert(fd_names);
assert(n_socket_fds);
assert(n_storage_fds);
+ assert(n_extra_fds);
if (s->socket_fd >= 0) {
Socket *sock = ASSERT_PTR(SOCKET(UNIT_DEREF(s->accept_socket)));
@@ -1512,10 +1551,44 @@ static int service_collect_fds(
rfd_names[n_fds] = NULL;
}
+ LIST_FOREACH(extra_fd, extra_fd, s->extra_fds)
+ rn_extra_fds++;
+
+ if (rn_extra_fds > 0) {
+ size_t n_fds;
+ char **nl;
+ int *t;
+
+ t = reallocarray(rfds, rn_socket_fds + rn_storage_fds + rn_extra_fds, sizeof(int));
+ if (!t)
+ return -ENOMEM;
+
+ rfds = t;
+
+ nl = reallocarray(rfd_names, rn_socket_fds + rn_storage_fds + rn_extra_fds + 1, sizeof(char *));
+ if (!nl)
+ return -ENOMEM;
+
+ rfd_names = nl;
+ n_fds = rn_socket_fds + rn_storage_fds;
+
+ LIST_FOREACH(extra_fd, extra_fd, s->extra_fds) {
+ rfds[n_fds] = extra_fd->fd;
+ rfd_names[n_fds] = strdup(extra_fd->fdname);
+ if (!rfd_names[n_fds])
+ return -ENOMEM;
+
+ n_fds++;
+ }
+
+ rfd_names[n_fds] = NULL;
+ }
+
*fds = TAKE_PTR(rfds);
*fd_names = TAKE_PTR(rfd_names);
*n_socket_fds = rn_socket_fds;
*n_storage_fds = rn_storage_fds;
+ *n_extra_fds = rn_extra_fds;
return 0;
}
@@ -1714,7 +1787,8 @@ static int service_spawn_internal(
&exec_params.fds,
&exec_params.fd_names,
&exec_params.n_socket_fds,
- &exec_params.n_storage_fds);
+ &exec_params.n_storage_fds,
+ &exec_params.n_extra_fds);
if (r < 0)
return r;
@@ -1722,7 +1796,7 @@ static int service_spawn_internal(
exec_params.flags |= EXEC_PASS_FDS;
- log_unit_debug(UNIT(s), "Passing %zu fds to service", exec_params.n_socket_fds + exec_params.n_storage_fds);
+ log_unit_debug(UNIT(s), "Passing %zu fds to service", exec_params.n_socket_fds + exec_params.n_storage_fds + exec_params.n_extra_fds);
}
if (!FLAGS_SET(exec_params.flags, EXEC_IS_CONTROL) && s->type == SERVICE_EXEC) {
@@ -5288,6 +5362,7 @@ static void service_release_resources(Unit *u) {
service_release_socket_fd(s);
service_release_stdio_fd(s);
+ service_release_extra_fds(s);
if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES)
service_release_fd_store(s);
diff --git a/src/core/service.h b/src/core/service.h
index 6a0c492992..97c217209e 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -3,6 +3,7 @@
typedef struct Service Service;
typedef struct ServiceFDStore ServiceFDStore;
+typedef struct ServiceExtraFD ServiceExtraFD;
#include "exit-status.h"
#include "kill.h"
@@ -111,6 +112,13 @@ struct ServiceFDStore {
LIST_FIELDS(ServiceFDStore, fd_store);
};
+struct ServiceExtraFD {
+ int fd;
+ char *fdname;
+
+ LIST_FIELDS(ServiceExtraFD, extra_fd);
+};
+
struct Service {
Unit meta;
@@ -231,6 +239,9 @@ struct Service {
LIST_HEAD(OpenFile, open_files);
+ /* If service spawned from transient unit, extra file descriptors can be passed via dbus API */
+ LIST_HEAD(ServiceExtraFD, extra_fds);
+
int reload_signal;
usec_t reload_begin_usec;
@@ -295,3 +306,6 @@ DEFINE_CAST(SERVICE, Service);
/* Only exported for unit tests */
int service_deserialize_exec_command(Unit *u, const char *key, const char *value);
+
+ServiceExtraFD* service_extra_fd_free(ServiceExtraFD *efd);
+DEFINE_TRIVIAL_CLEANUP_FUNC(ServiceExtraFD*, service_extra_fd_free);
diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c
index 0f954b89b3..6f3fd76622 100644
--- a/src/systemctl/systemctl-show.c
+++ b/src/systemctl/systemctl-show.c
@@ -2017,6 +2017,21 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
if (r < 0)
return bus_log_parse_error(r);
+ return 1;
+ } else if (streq(name, "ExtraFileDescriptorNames")) {
+ _cleanup_strv_free_ char **extra_fd_names = NULL;
+ _cleanup_free_ char *joined = NULL;
+
+ r = sd_bus_message_read_strv(m, &extra_fd_names);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ joined = strv_join(extra_fd_names, " ");
+ if (!joined)
+ return log_oom();
+
+ bus_print_property_value(name, expected_value, flags, joined);
+
return 1;
}
diff --git a/test/units/TEST-23-UNIT-FILE.ExtraFileDescriptors.sh b/test/units/TEST-23-UNIT-FILE.ExtraFileDescriptors.sh
new file mode 100755
index 0000000000..cf5c240908
--- /dev/null
+++ b/test/units/TEST-23-UNIT-FILE.ExtraFileDescriptors.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/util.sh
+. "$(dirname "$0")"/util.sh
+
+at_exit() {
+ set +e
+
+ rm -rf /tmp/test-extra-fd/
+}
+
+trap at_exit EXIT
+
+mkdir /tmp/test-extra-fd
+echo "Hello" > /tmp/test-extra-fd/1.txt
+echo "Extra" > /tmp/test-extra-fd/2.txt
+
+systemd-analyze log-level debug
+
+# Open files and assign FD to variables
+exec {TEST_FD1}