]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add OpenFile setting
authorRichard Phibel <rphibel@googlemail.com>
Mon, 7 Nov 2022 16:13:15 +0000 (17:13 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 10 Jan 2023 14:16:26 +0000 (15:16 +0100)
27 files changed:
man/org.freedesktop.systemd1.xml
man/systemd.service.xml
src/core/dbus-service.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.in
src/core/load-fragment.c
src/core/load-fragment.h
src/core/service.c
src/core/service.h
src/shared/bus-unit-util.c
src/shared/meson.build
src/shared/open-file.c [new file with mode: 0644]
src/shared/open-file.h [new file with mode: 0644]
src/systemctl/systemctl-show.c
src/test/meson.build
src/test/test-load-fragment.c
src/test/test-open-file.c [new file with mode: 0644]
test/TEST-77-OPENFILE/Makefile [new symlink]
test/TEST-77-OPENFILE/test.sh [new file with mode: 0755]
test/units/testsuite-77-netcat.service [new file with mode: 0644]
test/units/testsuite-77-netcat.sh [new file with mode: 0755]
test/units/testsuite-77-run.sh [new file with mode: 0755]
test/units/testsuite-77-socket.service [new file with mode: 0644]
test/units/testsuite-77-socket.sh [new file with mode: 0755]
test/units/testsuite-77.service [new file with mode: 0644]
test/units/testsuite-77.sh [new file with mode: 0755]

index 32ead7f272d1e7197ef07a8fd2f1c69fcae32019..5154638c337e4e4b34e3a984c493281edf546f2d 100644 (file)
@@ -2576,6 +2576,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       readonly u NRestarts = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s OOMPolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly a(sst) OpenFile = [...];
       readonly t ExecMainStartTimestamp = ...;
       readonly t ExecMainStartTimestampMonotonic = ...;
       readonly t ExecMainExitTimestamp = ...;
@@ -3173,6 +3175,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <!--property OOMPolicy is not documented!-->
 
+    <!--property OpenFile is not documented!-->
+
     <!--property ExecCondition is not documented!-->
 
     <!--property ExecConditionEx is not documented!-->
@@ -3729,6 +3733,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="OOMPolicy"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="OpenFile"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="ExecMainStartTimestamp"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="ExecMainStartTimestampMonotonic"/>
index 1c9e59f7229d2931cc96e0b517a6ffa9480dfa03..e327f688f437dcd3e54c386fad5a1f252847e208 100644 (file)
         kills, this setting determines the state of the unit after <command>systemd-oomd</command> kills a
         cgroup associated with it.</para></listitem>
       </varlistentry>
+      <varlistentry>
+        <term><varname>OpenFile=</varname></term>
+        <listitem><para>Takes an argument of the form <literal>path<optional><replaceable>:fd-name:options</replaceable></optional></literal>,
+        where:
+        <itemizedlist>
+            <listitem><simpara><literal>path</literal> is a path to a file or an <constant>AF_UNIX</constant> socket in the file system;</simpara></listitem>
+            <listitem><simpara><literal>fd-name</literal> is a name that will be associated with the file descriptor;
+            the name may contain any ASCII character, but must exclude control characters and ":", and must be at most 255 characters in length;
+            it is optional and, if not provided, defaults to the file name;</simpara></listitem>
+            <listitem><simpara><literal>options</literal> is a comma-separated list of access options;
+            possible values are
+            <literal>read-only</literal>,
+            <literal>append</literal>,
+            <literal>truncate</literal>,
+            <literal>graceful</literal>;
+            if not specified, files will be opened in <constant>rw</constant> mode;
+            if <literal>graceful</literal> is specified, errors during file/socket opening are ignored.
+            Specifying the same option several times is treated as an error.</simpara></listitem>
+        </itemizedlist>
+        The file or socket is opened by the service manager and the file descriptor is passed to the service.
+        If the path is a socket, we call <function>connect()</function> on it.
+        See <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+        for more details on how to retrieve these file descriptors.</para>
+
+        <para>This setting is useful to allow services to access files/sockets that they can't access themselves
+        (due to running in a separate mount namespace, not having privileges, ...).</para>
+
+        <para>This setting can be specified multiple times, in which case all the specified paths are opened and the file descriptors passed to the service.
+        If the empty string is assigned, the entire list of open files defined prior to this is reset.</para></listitem>
+      </varlistentry>
+
     </variablelist>
 
     <para id='shared-unit-options'>Check
index 6e4bc0bd1a30c56f4af3b1c339c22866f44a5ace..0d437afe6b6fe55f7cd66d2ff8970364c15d0df7 100644 (file)
@@ -17,6 +17,7 @@
 #include "fileio.h"
 #include "locale-util.h"
 #include "mount-util.h"
+#include "open-file.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "selinux-access.h"
@@ -36,6 +37,34 @@ static BUS_DEFINE_PROPERTY_GET(property_get_timeout_abort_usec, "t", Service, se
 static BUS_DEFINE_PROPERTY_GET(property_get_watchdog_usec, "t", Service, service_get_watchdog_usec);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_timeout_failure_mode, service_timeout_failure_mode, ServiceTimeoutFailureMode);
 
+static int property_get_open_files(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        OpenFile **open_files = ASSERT_PTR(userdata);
+        int r;
+
+        assert(bus);
+        assert(reply);
+
+        r = sd_bus_message_open_container(reply, 'a', "(sst)");
+        if (r < 0)
+                return r;
+
+        LIST_FOREACH(open_files, of, *open_files) {
+                r = sd_bus_message_append(reply, "(sst)", of->path, of->fdname, of->flags);
+                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,
@@ -228,6 +257,7 @@ const sd_bus_vtable bus_service_vtable[] = {
         SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         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),
 
         BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecCondition", offsetof(Service, exec_command[SERVICE_EXEC_CONDITION]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
@@ -532,6 +562,56 @@ static int bus_service_set_transient_property(
         if (streq(name, "StandardErrorFileDescriptor"))
                 return bus_set_transient_std_fd(u, name, &s->stderr_fd, &s->exec_context.stdio_as_fds, message, flags, error);
 
+        if (streq(name, "OpenFile")) {
+                const char *path, *fdname;
+                uint64_t offlags;
+
+                r = sd_bus_message_enter_container(message, 'a', "(sst)");
+                if (r < 0)
+                        return r;
+
+                while ((r = sd_bus_message_read(message, "(sst)", &path, &fdname, &offlags)) > 0) {
+                        _cleanup_(open_file_freep) OpenFile *of = NULL;
+                        _cleanup_free_ char *ofs = NULL;
+
+                        of = new(OpenFile, 1);
+                        if (!of)
+                                return -ENOMEM;
+
+                        *of = (OpenFile) {
+                                .path = strdup(path),
+                                .fdname = strdup(fdname),
+                                .flags = offlags,
+                        };
+
+                        if (!of->path || !of->fdname)
+                                return -ENOMEM;
+
+                        r = open_file_validate(of);
+                        if (r < 0)
+                                return r;
+
+                        if (UNIT_WRITE_FLAGS_NOOP(flags))
+                                continue;
+
+                        r = open_file_to_string(of, &ofs);
+                        if (r < 0)
+                                return sd_bus_error_set_errnof(
+                                                error, r, "Failed to convert OpenFile= value to string: %m");
+
+                        LIST_APPEND(open_files, s->open_files, TAKE_PTR(of));
+                        unit_write_settingf(u, flags | UNIT_ESCAPE_SPECIFIERS, name, "OpenFile=%s", ofs);
+                }
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_exit_container(message);
+                if (r < 0)
+                        return r;
+
+                return 1;
+        }
+
         return 0;
 }
 
index 439f491d024682e6cfd0619c8a47c64b4afd4a56..4c440895c19ecf961016a19bdc9f5255edfb6fc4 100644 (file)
@@ -151,11 +151,14 @@ static int shift_fds(int fds[], size_t n_fds) {
         return 0;
 }
 
-static int flags_fds(const int fds[], size_t n_socket_fds, size_t n_storage_fds, bool nonblock) {
-        size_t n_fds;
+static int flags_fds(
+                const int fds[],
+                size_t n_socket_fds,
+                size_t n_fds,
+                bool nonblock) {
+
         int r;
 
-        n_fds = n_socket_fds + n_storage_fds;
         if (n_fds <= 0)
                 return 0;
 
@@ -1805,6 +1808,7 @@ static int build_environment(
                 const ExecContext *c,
                 const ExecParameters *p,
                 size_t n_fds,
+                char **fdnames,
                 const char *home,
                 const char *username,
                 const char *shell,
@@ -1837,7 +1841,7 @@ static int build_environment(
                         return -ENOMEM;
                 our_env[n_env++] = x;
 
-                joined = strv_join(p->fd_names, ":");
+                joined = strv_join(fdnames, ":");
                 if (!joined)
                         return -ENOMEM;
 
@@ -4105,6 +4109,123 @@ static int add_shifted_fd(int *fds, size_t fds_size, size_t *n_fds, int fd, int
         return 1;
 }
 
+static int connect_unix_harder(Unit *u, const OpenFile *of, int ofd) {
+        union sockaddr_union addr = {
+                .un.sun_family = AF_UNIX,
+        };
+        socklen_t sa_len;
+        static const int socket_types[] = { SOCK_DGRAM, SOCK_STREAM, SOCK_SEQPACKET };
+        int r;
+
+        assert(u);
+        assert(of);
+        assert(ofd >= 0);
+
+        r = sockaddr_un_set_path(&addr.un, FORMAT_PROC_FD_PATH(ofd));
+        if (r < 0)
+                return log_unit_error_errno(u, r, "Failed to set sockaddr for %s: %m", of->path);
+
+        sa_len = r;
+
+        for (size_t i = 0; i < ELEMENTSOF(socket_types); i++) {
+                _cleanup_close_ int fd = -EBADF;
+
+                fd = socket(AF_UNIX, socket_types[i] | SOCK_CLOEXEC, 0);
+                if (fd < 0)
+                        return log_unit_error_errno(u, errno, "Failed to create socket for %s: %m", of->path);
+
+                r = RET_NERRNO(connect(fd, &addr.sa, sa_len));
+                if (r == -EPROTOTYPE)
+                        continue;
+                if (r < 0)
+                        return log_unit_error_errno(u, r, "Failed to connect socket for %s: %m", of->path);
+
+                return TAKE_FD(fd);
+        }
+
+        return log_unit_error_errno(u, SYNTHETIC_ERRNO(EPROTOTYPE), "Failed to connect socket for \"%s\".", of->path);
+}
+
+static int get_open_file_fd(Unit *u, const OpenFile *of) {
+        struct stat st;
+        _cleanup_close_ int fd = -EBADF, ofd = -EBADF;
+
+        assert(u);
+        assert(of);
+
+        ofd = open(of->path, O_PATH | O_CLOEXEC);
+        if (ofd < 0)
+                return log_error_errno(errno, "Could not open \"%s\": %m", of->path);
+        if (fstat(ofd, &st) < 0)
+                return log_error_errno(errno, "Failed to stat %s: %m", of->path);
+
+        if (S_ISSOCK(st.st_mode)) {
+                fd = connect_unix_harder(u, of, ofd);
+                if (fd < 0)
+                        return fd;
+
+                if (FLAGS_SET(of->flags, OPENFILE_READ_ONLY) && shutdown(fd, SHUT_WR) < 0)
+                        return log_error_errno(errno, "Failed to shutdown send for socket %s: %m", of->path);
+
+                log_unit_debug(u, "socket %s opened (fd=%d)", of->path, fd);
+        } else {
+                int flags = FLAGS_SET(of->flags, OPENFILE_READ_ONLY) ? O_RDONLY : O_RDWR;
+                if (FLAGS_SET(of->flags, OPENFILE_APPEND))
+                        flags |= O_APPEND;
+                else if (FLAGS_SET(of->flags, OPENFILE_TRUNCATE))
+                        flags |= O_TRUNC;
+
+                fd = fd_reopen(ofd, flags | O_CLOEXEC);
+                if (fd < 0)
+                        return log_unit_error_errno(u, fd, "Failed to open file %s: %m", of->path);
+
+                log_unit_debug(u, "file %s opened (fd=%d)", of->path, fd);
+        }
+
+        return TAKE_FD(fd);
+}
+
+static int collect_open_file_fds(
+                Unit *u,
+                OpenFile* open_files,
+                int **fds,
+                char ***fdnames,
+                size_t *n_fds) {
+        int r;
+
+        assert(u);
+        assert(fds);
+        assert(fdnames);
+        assert(n_fds);
+
+        LIST_FOREACH(open_files, of, open_files) {
+                _cleanup_close_ int fd = -EBADF;
+
+                fd = get_open_file_fd(u, of);
+                if (fd < 0) {
+                        if (FLAGS_SET(of->flags, OPENFILE_GRACEFUL)) {
+                                log_unit_debug_errno(u, fd, "Failed to get OpenFile= file descriptor for %s, ignoring: %m", of->path);
+                                continue;
+                        }
+
+                        return fd;
+                }
+
+                if (!GREEDY_REALLOC(*fds, *n_fds + 1))
+                        return -ENOMEM;
+
+                r = strv_extend(fdnames, of->fdname);
+                if (r < 0)
+                        return r;
+
+                (*fds)[*n_fds] = TAKE_FD(fd);
+
+                (*n_fds)++;
+        }
+
+        return 0;
+}
+
 static int exec_child(
                 Unit *unit,
                 const ExecCommand *command,
@@ -4114,7 +4235,7 @@ static int exec_child(
                 DynamicCreds *dcreds,
                 int socket_fd,
                 const int named_iofds[static 3],
-                int *fds,
+                int *params_fds,
                 size_t n_socket_fds,
                 size_t n_storage_fds,
                 char **files_env,
@@ -4154,6 +4275,8 @@ static int exec_child(
         int secure_bits;
         _cleanup_free_ gid_t *gids_after_pam = NULL;
         int ngids_after_pam = 0;
+        _cleanup_free_ int *fds = NULL;
+        _cleanup_strv_free_ char **fdnames = NULL;
 
         assert(unit);
         assert(command);
@@ -4196,6 +4319,24 @@ static int exec_child(
         /* In case anything used libc syslog(), close this here, too */
         closelog();
 
+        fds = newdup(int, params_fds, n_fds);
+        if (!fds) {
+                *exit_status = EXIT_MEMORY;
+                return log_oom();
+        }
+
+        fdnames = strv_copy((char**) params->fd_names);
+        if (!fdnames) {
+                *exit_status = EXIT_MEMORY;
+                return log_oom();
+        }
+
+        r = collect_open_file_fds(unit, params->open_files, &fds, &fdnames, &n_fds);
+        if (r < 0) {
+                *exit_status = EXIT_FDS;
+                return log_unit_error_errno(unit, r, "Failed to get OpenFile= file descriptors: %m");
+        }
+
         int keep_fds[n_fds + 3];
         memcpy_safe(keep_fds, fds, n_fds * sizeof(int));
         n_keep_fds = n_fds;
@@ -4551,6 +4692,7 @@ static int exec_child(
                         context,
                         params,
                         n_fds,
+                        fdnames,
                         home,
                         username,
                         shell,
@@ -4843,7 +4985,7 @@ static int exec_child(
         if (r >= 0)
                 r = shift_fds(fds, n_fds);
         if (r >= 0)
-                r = flags_fds(fds, n_socket_fds, n_storage_fds, context->non_blocking);
+                r = flags_fds(fds, n_socket_fds, n_fds, context->non_blocking);
         if (r < 0) {
                 *exit_status = EXIT_FDS;
                 return log_unit_error_errno(unit, r, "Failed to adjust passed file descriptors: %m");
index 24cd4640d7d200d8a5023fd09eb1c20e4edca508..62ad6d2eb2aacf453420762843944b89ce860f7a 100644 (file)
@@ -23,6 +23,7 @@ typedef struct Manager Manager;
 #include "namespace.h"
 #include "nsflags.h"
 #include "numa-util.h"
+#include "open-file.h"
 #include "path-util.h"
 #include "set.h"
 #include "time-util.h"
@@ -427,6 +428,8 @@ struct ExecParameters {
         int exec_fd;
 
         const char *notify_socket;
+
+        LIST_HEAD(OpenFile, open_files);
 };
 
 #include "unit.h"
index 2850da5cc10c50f79ead2f77ec2f02885179a39c..81eb586cfee86f1e2f14cba259f006dcf309f546 100644 (file)
@@ -426,6 +426,7 @@ Service.BusPolicy,                       config_parse_warn_compat,
 Service.USBFunctionDescriptors,          config_parse_unit_path_printf,               0,                                  offsetof(Service, usb_function_descriptors)
 Service.USBFunctionStrings,              config_parse_unit_path_printf,               0,                                  offsetof(Service, usb_function_strings)
 Service.OOMPolicy,                       config_parse_oom_policy,                     0,                                  offsetof(Service, oom_policy)
+Service.OpenFile,                        config_parse_open_file,                      0,                                  offsetof(Service, open_files)
 {{ EXEC_CONTEXT_CONFIG_ITEMS('Service') }}
 {{ CGROUP_CONTEXT_CONFIG_ITEMS('Service') }}
 {{ KILL_CONTEXT_CONFIG_ITEMS('Service') }}
index e115aa62706b94bbc4b4fd2f7724d988965f528d..6c55bc5187c1527fb36850567ee29ff088cb4181 100644 (file)
@@ -49,6 +49,7 @@
 #include "missing_ioprio.h"
 #include "mountpoint-util.h"
 #include "nulstr-util.h"
+#include "open-file.h"
 #include "parse-helpers.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -6532,3 +6533,39 @@ int config_parse_log_filter_patterns(
 
         return 0;
 }
+
+int config_parse_open_file(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_(open_file_freep) OpenFile *of = NULL;
+        OpenFile **head = ASSERT_PTR(data);
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        if (isempty(rvalue)) {
+                open_file_free_many(head);
+                return 0;
+        }
+
+        r = open_file_parse(rvalue, &of);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse OpenFile= setting, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        LIST_APPEND(open_files, *head, TAKE_PTR(of));
+
+        return 0;
+}
index 74b36336950a279a291bbf6680d2c340558eabd0..11d43dda923957ca34b89e84eecde8582f6232d0 100644 (file)
@@ -151,6 +151,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_restrict_network_interfaces);
 CONFIG_PARSER_PROTOTYPE(config_parse_watchdog_sec);
 CONFIG_PARSER_PROTOTYPE(config_parse_tty_size);
 CONFIG_PARSER_PROTOTYPE(config_parse_log_filter_patterns);
+CONFIG_PARSER_PROTOTYPE(config_parse_open_file);
 
 /* gperf prototypes */
 const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
index 4993fdf5e7e1cb849343200b0a8e0e6bbba5b07b..0efcb9a081718f5685afc8622e54bcb63d3fc1b6 100644 (file)
@@ -26,6 +26,7 @@
 #include "load-fragment.h"
 #include "log.h"
 #include "manager.h"
+#include "open-file.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
@@ -360,6 +361,8 @@ static void service_done(Unit *u) {
 
         assert(s);
 
+        open_file_free_many(&s->open_files);
+
         s->pid_file = mfree(s->pid_file);
         s->status_text = mfree(s->status_text);
 
@@ -925,6 +928,21 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
                         prefix, s->n_fd_store_max,
                         prefix, s->n_fd_store);
 
+        if (s->open_files)
+                LIST_FOREACH(open_files, of, s->open_files) {
+                        _cleanup_free_ char *ofs = NULL;
+                        int r;
+
+                        r = open_file_to_string(of, &ofs);
+                        if (r < 0) {
+                                log_debug_errno(r,
+                                                "Failed to convert OpenFile= setting to string, ignoring: %m");
+                                continue;
+                        }
+
+                        fprintf(f, "%sOpen File: %s\n", prefix, ofs);
+                }
+
         cgroup_context_dump(UNIT(s), f, prefix);
 }
 
@@ -1528,6 +1546,8 @@ static int service_spawn_internal(
                 if (r < 0)
                         return r;
 
+                exec_params.open_files = s->open_files;
+
                 log_unit_debug(UNIT(s), "Passing %zu fds to service", exec_params.n_socket_fds + exec_params.n_storage_fds);
         }
 
index 91e02e6d7ee27850b8f3a3a93f0045fac717f30c..763398e31503d96a45ea9fe829b89ce1eae66f2d 100644 (file)
@@ -6,6 +6,7 @@ typedef struct ServiceFDStore ServiceFDStore;
 
 #include "exit-status.h"
 #include "kill.h"
+#include "open-file.h"
 #include "path.h"
 #include "ratelimit.h"
 #include "socket.h"
@@ -215,6 +216,8 @@ struct Service {
         bool flush_n_restarts;
 
         OOMPolicy oom_policy;
+
+        LIST_HEAD(OpenFile, open_files);
 };
 
 static inline usec_t service_timeout_abort_usec(Service *s) {
index 7154c9b4c0804a1f9d1f357e5f194781568966d0..0fa2dea2d4e153165cd357e28ebb17c3ee05f51e 100644 (file)
@@ -29,6 +29,7 @@
 #include "mountpoint-util.h"
 #include "nsflags.h"
 #include "numa-util.h"
+#include "open-file.h"
 #include "parse-helpers.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -410,6 +411,23 @@ static int bus_append_exec_command(sd_bus_message *m, const char *field, const c
         return 1;
 }
 
+static int bus_append_open_file(sd_bus_message *m, const char *field, const char *eq) {
+        _cleanup_(open_file_freep) OpenFile *of = NULL;
+        int r;
+
+        assert(m);
+
+        r = open_file_parse(eq, &of);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse OpenFile= setting: %m");
+
+        r = sd_bus_message_append(m, "(sv)", field, "a(sst)", (size_t) 1, of->path, of->fdname, of->flags);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        return 1;
+}
+
 static int bus_append_ip_address_access(sd_bus_message *m, int family, const union in_addr_union *prefix, unsigned char prefixlen) {
         int r;
 
@@ -2300,6 +2318,9 @@ static int bus_append_service_property(sd_bus_message *m, const char *field, con
                 return 1;
         }
 
+        if (streq(field, "OpenFile"))
+                return bus_append_open_file(m, field, eq);
+
         return 0;
 }
 
index 0d5e9f2dbbbe19d22acc06437ef31f0a9d85a4e1..bc1cfa5b4b5e560f3d3d407406760e727b1509c2 100644 (file)
@@ -243,6 +243,8 @@ shared_sources = files(
         'nsflags.h',
         'numa-util.c',
         'numa-util.h',
+        'open-file.c',
+        'open-file.h',
         'openssl-util.c',
         'openssl-util.h',
         'output-mode.c',
diff --git a/src/shared/open-file.c b/src/shared/open-file.c
new file mode 100644 (file)
index 0000000..dedf067
--- /dev/null
@@ -0,0 +1,150 @@
+
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+
+#include "escape.h"
+#include "extract-word.h"
+#include "fd-util.h"
+#include "open-file.h"
+#include "path-util.h"
+#include "string-table.h"
+#include "string-util.h"
+
+int open_file_parse(const char *v, OpenFile **ret) {
+        _cleanup_free_ char *options = NULL;
+        _cleanup_(open_file_freep) OpenFile *of = NULL;
+        int r;
+
+        assert(v);
+        assert(ret);
+
+        of = new0(OpenFile, 1);
+        if (!of)
+                return -ENOMEM;
+
+        r = extract_many_words(&v, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_CUNESCAPE, &of->path, &of->fdname, &options, NULL);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EINVAL;
+
+        /* Enforce that at most 3 colon-separated words are present */
+        if (!isempty(v))
+                return -EINVAL;
+
+        for (const char *p = options;;) {
+                OpenFileFlag flag;
+                _cleanup_free_ char *word = NULL;
+
+                r = extract_first_word(&p, &word, ",", 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                flag = open_file_flags_from_string(word);
+                if (flag < 0)
+                        return flag;
+
+                if ((flag & of->flags) != 0)
+                        return -EINVAL;
+
+                of->flags |= flag;
+        }
+
+        if (isempty(of->fdname)) {
+                free(of->fdname);
+                r = path_extract_filename(of->path, &of->fdname);
+                if (r < 0)
+                        return r;
+        }
+
+        r = open_file_validate(of);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(of);
+
+        return 0;
+}
+
+int open_file_validate(const OpenFile *of) {
+        assert(of);
+
+        if (!path_is_valid(of->path) || !path_is_absolute(of->path))
+                return -EINVAL;
+
+        if (!fdname_is_valid(of->fdname))
+                return -EINVAL;
+
+        if ((FLAGS_SET(of->flags, OPENFILE_READ_ONLY) + FLAGS_SET(of->flags, OPENFILE_APPEND) +
+             FLAGS_SET(of->flags, OPENFILE_TRUNCATE)) > 1)
+                return -EINVAL;
+
+        if ((of->flags & ~_OPENFILE_MASK_PUBLIC) != 0)
+                return -EINVAL;
+
+        return 0;
+}
+
+int open_file_to_string(const OpenFile *of, char **ret) {
+        _cleanup_free_ char *options = NULL, *fname = NULL, *s = NULL;
+        bool has_fdname = false;
+        int r;
+
+        assert(of);
+        assert(ret);
+
+        s = shell_escape(of->path, ":");
+        if (!s)
+                return -ENOMEM;
+
+        r = path_extract_filename(of->path, &fname);
+        if (r < 0)
+                return r;
+
+        has_fdname = !streq(fname, of->fdname);
+        if (has_fdname)
+                if (!strextend(&s, ":", of->fdname))
+                        return -ENOMEM;
+
+        for (OpenFileFlag flag = OPENFILE_READ_ONLY; flag < _OPENFILE_MAX; flag <<= 1)
+                if (FLAGS_SET(of->flags, flag) && !strextend_with_separator(&options, ",", open_file_flags_to_string(flag)))
+                        return -ENOMEM;
+
+        if (options)
+                if (!(has_fdname ? strextend(&s, ":", options) : strextend(&s, "::", options)))
+                        return -ENOMEM;
+
+        *ret = TAKE_PTR(s);
+
+        return 0;
+}
+
+OpenFile *open_file_free(OpenFile *of) {
+        if (!of)
+                return NULL;
+
+        free(of->path);
+        free(of->fdname);
+        return mfree(of);
+}
+
+void open_file_free_many(OpenFile **head) {
+        OpenFile *of;
+
+        while ((of = *head)) {
+                LIST_REMOVE(open_files, *head, of);
+                of = open_file_free(of);
+        }
+}
+
+static const char * const open_file_flags_table[_OPENFILE_MAX] = {
+        [OPENFILE_READ_ONLY] = "read-only",
+        [OPENFILE_APPEND]    = "append",
+        [OPENFILE_TRUNCATE]  = "truncate",
+        [OPENFILE_GRACEFUL]  = "graceful",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(open_file_flags, OpenFileFlag);
diff --git a/src/shared/open-file.h b/src/shared/open-file.h
new file mode 100644 (file)
index 0000000..bb63ec8
--- /dev/null
@@ -0,0 +1,36 @@
+
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "list.h"
+
+typedef enum OpenFileFlag {
+        OPENFILE_READ_ONLY = 1 << 0,
+        OPENFILE_APPEND    = 1 << 1,
+        OPENFILE_TRUNCATE  = 1 << 2,
+        OPENFILE_GRACEFUL  = 1 << 3,
+        _OPENFILE_MAX,
+        _OPENFILE_INVALID  = -EINVAL,
+        _OPENFILE_MASK_PUBLIC = OPENFILE_READ_ONLY | OPENFILE_APPEND | OPENFILE_TRUNCATE | OPENFILE_GRACEFUL,
+} OpenFileFlag;
+
+typedef struct OpenFile {
+        char *path;
+        char *fdname;
+        OpenFileFlag flags;
+        LIST_FIELDS(struct OpenFile, open_files);
+} OpenFile;
+
+int open_file_parse(const char *v, OpenFile **ret);
+
+int open_file_validate(const OpenFile *of);
+
+int open_file_to_string(const OpenFile *of, char **ret);
+
+OpenFile *open_file_free(OpenFile *of);
+DEFINE_TRIVIAL_CLEANUP_FUNC(OpenFile*, open_file_free);
+
+void open_file_free_many(OpenFile **head);
+
+const char *open_file_flags_to_string(OpenFileFlag t) _const_;
+OpenFileFlag open_file_flags_from_string(const char *t) _pure_;
index f78cf307cac515fdac0d81ae44b5170f88bf8474..4166b361ddab8bcaf536aff1828f467765423954 100644 (file)
@@ -23,6 +23,7 @@
 #include "locale-util.h"
 #include "memory-util.h"
 #include "numa-util.h"
+#include "open-file.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "pretty-print.h"
@@ -1857,6 +1858,38 @@ 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 (contents[0] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "OpenFile")) {
+                        char *path, *fdname;
+                        uint64_t offlags;
+
+                        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sst)");
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+
+                        while ((r = sd_bus_message_read(m, "(sst)", &path, &fdname, &offlags)) > 0) {
+                                _cleanup_free_ char *ofs = NULL;
+
+                                r = open_file_to_string(
+                                        &(OpenFile){
+                                                .path = path,
+                                                .fdname = fdname,
+                                                .flags = offlags,
+                                        },
+                                        &ofs);
+                                if (r < 0)
+                                        return log_error_errno(
+                                                        r, "Failed to convert OpenFile= value to string: %m");
+
+                                bus_print_property_value(name, expected_value, flags, ofs);
+                        }
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+
+                        r = sd_bus_message_exit_container(m);
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+
                         return 1;
                 }
 
index f9ee919019557c57f87f8f5b937e059e77cee6aa..3aa98e1ffdffa01a4a7ff7d72ba5f25dd745f799 100644 (file)
@@ -695,6 +695,8 @@ tests += [
         [files('test-hmac.c')],
 
         [files('test-sha256.c')],
+
+        [files('test-open-file.c')],
 ]
 
 ############################################################
index 8d3c58a800edd61fe24989a02373ec445e673326..446437313224b4dbb6ec7edfe4c5d92dba3a70d4 100644 (file)
@@ -22,6 +22,7 @@
 #include "load-fragment.h"
 #include "macro.h"
 #include "memory-util.h"
+#include "open-file.h"
 #include "pcre2-util.h"
 #include "rm-rf.h"
 #include "specifier.h"
@@ -1048,6 +1049,50 @@ TEST(config_parse_log_filter_patterns) {
         }
 }
 
+TEST(config_parse_open_file) {
+        _cleanup_(manager_freep) Manager *m = NULL;
+        _cleanup_(unit_freep) Unit *u = NULL;
+        _cleanup_(open_file_freep) OpenFile *of = NULL;
+        int r;
+
+        r = manager_new(LOOKUP_SCOPE_USER, MANAGER_TEST_RUN_MINIMAL, &m);
+        if (manager_errno_skip_test(r)) {
+                log_notice_errno(r, "Skipping test: manager_new: %m");
+                return;
+        }
+
+        assert_se(r >= 0);
+        assert_se(manager_startup(m, NULL, NULL, NULL) >= 0);
+
+        assert_se(u = unit_new(m, sizeof(Service)));
+        assert_se(unit_add_name(u, "foobar.service") == 0);
+
+        r = config_parse_open_file(NULL, "fake", 1, "section", 1,
+                                   "OpenFile", 0, "/proc/1/ns/mnt:host-mount-namespace:read-only",
+                                   &of, u);
+        assert_se(r >= 0);
+        assert_se(of);
+        assert_se(streq(of->path, "/proc/1/ns/mnt"));
+        assert_se(streq(of->fdname, "host-mount-namespace"));
+        assert_se(of->flags == OPENFILE_READ_ONLY);
+
+        of = open_file_free(of);
+        r = config_parse_open_file(NULL, "fake", 1, "section", 1,
+                                   "OpenFile", 0, "/proc/1/ns/mnt::read-only",
+                                   &of, u);
+        assert_se(r >= 0);
+        assert_se(of);
+        assert_se(streq(of->path, "/proc/1/ns/mnt"));
+        assert_se(streq(of->fdname, "mnt"));
+        assert_se(of->flags == OPENFILE_READ_ONLY);
+
+        r = config_parse_open_file(NULL, "fake", 1, "section", 1,
+                                   "OpenFile", 0, "",
+                                   &of, u);
+        assert_se(r >= 0);
+        assert_se(!of);
+}
+
 static int intro(void) {
         if (enter_cgroup_subroot(NULL) == -ENOMEDIUM)
                 return log_tests_skipped("cgroupfs not available");
diff --git a/src/test/test-open-file.c b/src/test/test-open-file.c
new file mode 100644 (file)
index 0000000..1b938ec
--- /dev/null
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "open-file.h"
+#include "string-util.h"
+#include "tests.h"
+
+TEST(open_file_parse) {
+        _cleanup_(open_file_freep) OpenFile *of = NULL;
+        int r;
+
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only", &of);
+
+        assert_se(r >= 0);
+        assert_se(streq(of->path, "/proc/1/ns/mnt"));
+        assert_se(streq(of->fdname, "host-mount-namespace"));
+        assert_se(of->flags == OPENFILE_READ_ONLY);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt", &of);
+
+        assert_se(r >= 0);
+        assert_se(streq(of->path, "/proc/1/ns/mnt"));
+        assert_se(streq(of->fdname, "mnt"));
+        assert_se(of->flags == 0);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace", &of);
+
+        assert_se(r >= 0);
+        assert_se(streq(of->path, "/proc/1/ns/mnt"));
+        assert_se(streq(of->fdname, "host-mount-namespace"));
+        assert_se(of->flags == 0);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt::read-only", &of);
+
+        assert_se(r >= 0);
+        assert_se(streq(of->path, "/proc/1/ns/mnt"));
+        assert_se(streq(of->fdname, "mnt"));
+        assert_se(of->flags == OPENFILE_READ_ONLY);
+
+        of = open_file_free(of);
+        r = open_file_parse("../file.dat:file:read-only", &of);
+
+        assert_se(r == -EINVAL);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:rw", &of);
+
+        assert_se(r == -EINVAL);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:append", &of);
+
+        assert_se(r >= 0);
+        assert_se(streq(of->path, "/proc/1/ns/mnt"));
+        assert_se(streq(of->fdname, "host-mount-namespace"));
+        assert_se(of->flags == OPENFILE_APPEND);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:truncate", &of);
+
+        assert_se(r >= 0);
+        assert_se(streq(of->path, "/proc/1/ns/mnt"));
+        assert_se(streq(of->fdname, "host-mount-namespace"));
+        assert_se(of->flags == OPENFILE_TRUNCATE);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,append", &of);
+
+        assert_se(r == -EINVAL);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,truncate", &of);
+
+        assert_se(r == -EINVAL);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:append,truncate", &of);
+
+        assert_se(r == -EINVAL);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,read-only", &of);
+
+        assert_se(r == -EINVAL);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:graceful", &of);
+
+        assert_se(r >= 0);
+        assert_se(streq(of->path, "/proc/1/ns/mnt"));
+        assert_se(streq(of->fdname, "host-mount-namespace"));
+        assert_se(of->flags == OPENFILE_GRACEFUL);
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,graceful", &of);
+
+        assert_se(r >= 0);
+        assert_se(streq(of->path, "/proc/1/ns/mnt"));
+        assert_se(streq(of->fdname, "host-mount-namespace"));
+        assert_se(of->flags == (OPENFILE_READ_ONLY | OPENFILE_GRACEFUL));
+
+        of = open_file_free(of);
+        r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only:other", &of);
+
+        assert_se(r == -EINVAL);
+}
+
+TEST(open_file_to_string) {
+        _cleanup_free_ char *s = NULL;
+        _cleanup_(open_file_freep) OpenFile *of = NULL;
+        int r;
+
+        assert_se(of = new (OpenFile, 1));
+        *of = (OpenFile){ .path = strdup("/proc/1/ns/mnt"),
+                          .fdname = strdup("host-mount-namespace"),
+                          .flags = OPENFILE_READ_ONLY };
+
+        r = open_file_to_string(of, &s);
+
+        assert_se(r >= 0);
+        assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:read-only"));
+
+        s = mfree(s);
+        of->flags = OPENFILE_APPEND;
+
+        r = open_file_to_string(of, &s);
+
+        assert_se(r >= 0);
+        assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:append"));
+
+        s = mfree(s);
+        of->flags = OPENFILE_TRUNCATE;
+
+        r = open_file_to_string(of, &s);
+
+        assert_se(r >= 0);
+        assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:truncate"));
+
+        s = mfree(s);
+        of->flags = OPENFILE_GRACEFUL;
+
+        r = open_file_to_string(of, &s);
+
+        assert_se(r >= 0);
+        assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:graceful"));
+
+        s = mfree(s);
+        of->flags = OPENFILE_READ_ONLY | OPENFILE_GRACEFUL;
+
+        r = open_file_to_string(of, &s);
+
+        assert_se(r >= 0);
+        assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:read-only,graceful"));
+
+        s = mfree(s);
+        of->flags = 0;
+
+        r = open_file_to_string(of, &s);
+
+        assert_se(r >= 0);
+        assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace"));
+
+        s = mfree(s);
+        assert_se(free_and_strdup(&of->fdname, "mnt"));
+        of->flags = OPENFILE_READ_ONLY;
+
+        r = open_file_to_string(of, &s);
+
+        assert_se(r >= 0);
+        assert_se(streq(s, "/proc/1/ns/mnt::read-only"));
+
+        s = mfree(s);
+        assert_se(free_and_strdup(&of->path, "/path:with:colon"));
+        assert_se(free_and_strdup(&of->fdname, "path:with:colon"));
+        of->flags = 0;
+
+        r = open_file_to_string(of, &s);
+
+        assert_se(r >= 0);
+        assert_se(streq(s, "/path\\:with\\:colon"));
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
diff --git a/test/TEST-77-OPENFILE/Makefile b/test/TEST-77-OPENFILE/Makefile
new file mode 120000 (symlink)
index 0000000..e9f93b1
--- /dev/null
@@ -0,0 +1 @@
+../TEST-01-BASIC/Makefile
\ No newline at end of file
diff --git a/test/TEST-77-OPENFILE/test.sh b/test/TEST-77-OPENFILE/test.sh
new file mode 100755 (executable)
index 0000000..e434999
--- /dev/null
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -e
+
+TEST_DESCRIPTION="Openfile tests"
+
+# shellcheck source=test/test-functions
+. "${TEST_BASE_DIR:?}/test-functions"
+
+test_append_files() {
+    local workspace="${1:?}"
+    echo "Open" > "$workspace/test-77-open.dat"
+    echo "File" > "$workspace/test-77-file.dat"
+}
+
+do_test "$@"
diff --git a/test/units/testsuite-77-netcat.service b/test/units/testsuite-77-netcat.service
new file mode 100644 (file)
index 0000000..8ae399a
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-77-OPENFILE
+
+[Service]
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=simple
diff --git a/test/units/testsuite-77-netcat.sh b/test/units/testsuite-77-netcat.sh
new file mode 100755 (executable)
index 0000000..73b4c87
--- /dev/null
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+echo "Socket" | nc -lkU /tmp/test.sock
diff --git a/test/units/testsuite-77-run.sh b/test/units/testsuite-77-run.sh
new file mode 100755 (executable)
index 0000000..086044a
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+export SYSTEMD_LOG_LEVEL=debug
+
+assert_eq "$LISTEN_FDS" "1"
+assert_eq "$LISTEN_FDNAMES" "new-file"
+read -r -u 3 text
+assert_eq "$text" "New"
diff --git a/test/units/testsuite-77-socket.service b/test/units/testsuite-77-socket.service
new file mode 100644 (file)
index 0000000..9b6cfc6
--- /dev/null
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-77-OPENFILE
+
+[Service]
+OpenFile=/tmp/test.sock:socket:read-only
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-77-socket.sh b/test/units/testsuite-77-socket.sh
new file mode 100755 (executable)
index 0000000..0f88a6d
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+export SYSTEMD_LOG_LEVEL=debug
+
+assert_eq "$LISTEN_FDS" "1"
+assert_eq "$LISTEN_FDNAMES" "socket"
+read -r -u 3 text
+assert_eq "$text" "Socket"
diff --git a/test/units/testsuite-77.service b/test/units/testsuite-77.service
new file mode 100644 (file)
index 0000000..6ed8add
--- /dev/null
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-77-OPENFILE
+
+[Service]
+OpenFile=/test-77-open.dat:open:read-only
+OpenFile=/test-77-file.dat
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-77.sh b/test/units/testsuite-77.sh
new file mode 100755 (executable)
index 0000000..2675f05
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+export SYSTEMD_LOG_LEVEL=debug
+
+assert_eq "$LISTEN_FDS" "2"
+assert_eq "$LISTEN_FDNAMES" "open:test-77-file.dat"
+read -r -u 3 text
+assert_eq "$text" "Open"
+read -r -u 4 text
+assert_eq "$text" "File"
+
+# Test for socket
+systemctl start testsuite-77-netcat.service
+systemctl start testsuite-77-socket.service
+
+# Tests for D-Bus
+diff <(systemctl show -p OpenFile testsuite-77) - <<EOF
+OpenFile=/test-77-open.dat:open:read-only
+OpenFile=/test-77-file.dat
+EOF
+echo "New" > /test-77-new-file.dat
+systemd-run --wait -p OpenFile=/test-77-new-file.dat:new-file:read-only "$(dirname "$0")"/testsuite-77-run.sh
+
+assert_rc 202 systemd-run --wait -p OpenFile=/test-77-new-file.dat:new-file:read-only -p OpenFile=/test-77-mssing-file.dat:missing-file:read-only "$(dirname "$0")"/testsuite-77-run.sh
+
+assert_rc 0 systemd-run --wait -p OpenFile=/test-77-new-file.dat:new-file:read-only -p OpenFile=/test-77-mssing-file.dat:missing-file:read-only,graceful "$(dirname "$0")"/testsuite-77-run.sh
+
+# End
+touch /testok