]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Add ExtraFileDescriptor property to StartTransientUnit dbus API 34556/head
authorRyan Wilson <ryantimwilson@meta.com>
Mon, 30 Sep 2024 16:58:34 +0000 (09:58 -0700)
committerRyan Wilson <ryantimwilson@meta.com>
Mon, 7 Oct 2024 16:01:48 +0000 (09:01 -0700)
This adds the ExtraFileDescriptor property to StartTransient dbus API
with format "a(hs)" - array of (file descriptor, name) pairs. The FD
will be passed to the unit via sd_notify like Socket and OpenFile.

systemctl show also shows ExtraFileDescriptorName for these transient
units. We only show the name passed to dbus as the FD numbers will
change once passed over the unix socket and are duplicated, so its
confusing to display the numbers.

We do not add this functionality for systemd-run or general systemd
service units as it is not useful for general systemd services.
Arguably, it could be useful for systemd-run in bash scripts but we
prefer to be cautious and not expose the API yet.

Fixes: #34396
man/org.freedesktop.systemd1.xml
src/core/dbus-service.c
src/core/exec-invoke.c
src/core/execute-serialize.c
src/core/execute.c
src/core/execute.h
src/core/fuzz-execute-serialize.c
src/core/service.c
src/core/service.h
src/systemctl/systemctl-show.c
test/units/TEST-23-UNIT-FILE.ExtraFileDescriptors.sh [new file with mode: 0755]

index dd144a6cba07e037776f01a4cfb6ed94f99c3a93..3b7cad5b47f4c01217424a805ffa5b56d382c136 100644 (file)
@@ -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 {
 
     <variablelist class="dbus-property" generated="True" extra-ref="OpenFile"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ExtraFileDescriptorNames"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="ReloadSignal"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="ExecMainStartTimestamp"/>
@@ -4843,6 +4847,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       <varname>StateDirectory</varname>, <varname>CacheDirectory</varname> and <varname>LogsDirectory</varname>,
       which will create a symlink of the given name to the respective directory. The messages take an unused
       <varname>flags</varname> parameter, reserved for future backward-compatible changes.</para>
+
+      <para><varname>ExtraFileDescriptorNames</varname> contains file descriptor names passed to the service via
+      the <varname>ExtraFileDescriptors</varname> property in the <function>StartTransientUnit()</function>
+      method. See <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+      for more details on how to retrieve these file descriptors. Unlike the <varname>ExtraFileDescriptors</varname>
+      input property, <varname>ExtraFileDescriptorNames</varname> only contains names and not the file descriptors.</para>
     </refsect2>
   </refsect1>
 
@@ -12209,6 +12219,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>LiveMountResult</varname>,
       <varname>PrivateTmpEx</varname>,
       <varname>ImportCredentialEx</varname>,
+      <varname>ExtraFileDescriptorNames</varname>,
       <varname>BindLogSockets</varname>, and
       <varname>PrivateUsersEx</varname> were added in version 257.</para>
     </refsect2>
index 43a8fb0617535e465c212e4b8c1ecd75a3268819..a0244c186079974d26ae1fbaf3b84b41be021dc8 100644 (file)
@@ -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;
 }
 
index becc2b92ef8d94fbfa661d08b61c21d27b25dbb3..2bd43a95ddb29862f1c2ef89c2340b3159700664 100644 (file)
@@ -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)
index b3035c002679578fdeaf6c44fd3bec466619de1b..13e7078b1a9556df8131065d5d2181ae40bd13d9 100644 (file)
@@ -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;
 
index 30fcffcc5bdf03bde5b95463516a7d597e7e198a..0c2c278d69c4bc200911d568d1b90a97ca9a0fce 100644 (file)
@@ -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);
 
index 01a196748b5c8ae24dd6000682d604d2310122df..29c08d48b9d219eb6bd10f9d8376549d0a935a63 100644 (file)
@@ -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;
index 5b2dc952add57fc2b32326bae321c205318bdfd0..05abee5a4e69a62db24bfb5589ba3101c202de5e 100644 (file)
@@ -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);
index e5db7a085b5f03761a6689221c995e0f3476f566..7162016313c18678b1cc66643b3a596189fd75db 100644 (file)
@@ -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;
-
-                if (fstat(i->fd, &st) < 0) {
-                        log_debug_errno(errno, "Failed to stat fdstore entry: %m");
-                        continue;
-                }
-
-                flags = fcntl(i->fd, F_GETFL);
-                if (flags < 0) {
-                        log_debug_errno(errno, "Failed to get fdstore entry flags: %m");
-                        continue;
-                }
+        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);
+}
 
-                (void) fd_get_path(i->fd, &path);
+static void service_dump_extra_fds(Service *s, FILE *f, const char *prefix) {
+        assert(s);
+        assert(f);
+        assert(prefix);
 
-                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);
index 6a0c49299223a20cee05b5100b9eced145576b23..97c217209e2c4dd50e85292d240fb9a263ae3aa7 100644 (file)
@@ -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);
index 0f954b89b3ff51e891466919d7e48cd8d4450e08..6f3fd766227ccb9226878bedc408cf6cdbb603a0 100644 (file)
@@ -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 (executable)
index 0000000..cf5c240
--- /dev/null
@@ -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}</tmp/test-extra-fd/1.txt
+exec {TEST_FD2}</tmp/test-extra-fd/2.txt
+
+TEST_UNIT="test-23-extra-fd.service"
+
+busctl call \
+    org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+    org.freedesktop.systemd1.Manager StartTransientUnit \
+    "ssa(sv)a(sa(sv))" "$TEST_UNIT" replace 4 \
+      ExecStart "a(sasb)" 1 \
+        /usr/lib/systemd/tests/testdata/units/TEST-23-UNIT-FILE-openfile-child.sh \
+        5 /usr/lib/systemd/tests/testdata/units/TEST-23-UNIT-FILE-openfile-child.sh 2 "test:other" "Hello" "Extra" \
+        true \
+      RemainAfterExit "b" true \
+      Type "s" oneshot \
+      ExtraFileDescriptors "a(hs)" 2 \
+        "$TEST_FD1" test \
+        "$TEST_FD2" other \
+    0
+
+cmp -b <(systemctl show -p ExtraFileDescriptorNames "$TEST_UNIT") <<EOF
+ExtraFileDescriptorNames=test other
+EOF
+
+# shellcheck disable=SC2016
+timeout 10s bash -xec 'while [[ "$(systemctl show -P SubState test-23-extra-fd.service)" != "exited" ]]; do sleep .5; done'
+
+assert_eq "$(systemctl show -P Result "$TEST_UNIT")" "success"
+assert_eq "$(systemctl show -P ExecMainStatus "$TEST_UNIT")" "0"
+
+systemctl stop "$TEST_UNIT"
+
+systemctl log-level info