]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: Add ExecSearchPath parameter to specify the directory relative to which binarie...
authoralexlzhu <alexlzhu@devvm2387.atn0.facebook.com>
Fri, 9 Jul 2021 00:10:47 +0000 (17:10 -0700)
committerChris Down <chris@chrisdown.name>
Tue, 28 Sep 2021 13:52:27 +0000 (14:52 +0100)
Currently there does not exist a way to specify a path relative to which
all binaries executed by Exec should be found. The only way is to
specify the absolute path.

This change implements the functionality to specify a path relative to which
binaries executed by Exec*= can be found.

Closes #6308

27 files changed:
man/org.freedesktop.systemd1.xml
man/systemd.exec.xml
src/analyze/analyze-verify.c
src/basic/path-util.c
src/basic/path-util.h
src/core/dbus-execute.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/run/run.c
src/shared/bus-unit-util.c
src/test/test-execute.c
src/test/test-path-util.c
test/fuzz/fuzz-unit-file/directives.mount
test/fuzz/fuzz-unit-file/directives.service
test/fuzz/fuzz-unit-file/directives.socket
test/fuzz/fuzz-unit-file/directives.swap
test/test-execute/exec-execsearchpath-environment-path-set.service [new file with mode: 0644]
test/test-execute/exec-execsearchpath-environment.service [new file with mode: 0644]
test/test-execute/exec-execsearchpath-environmentfile-set.service [new file with mode: 0644]
test/test-execute/exec-execsearchpath-environmentfile.service [new file with mode: 0644]
test/test-execute/exec-execsearchpath-passenvironment-set.service [new file with mode: 0644]
test/test-execute/exec-execsearchpath-passenvironment.service [new file with mode: 0644]
test/test-execute/exec-execsearchpath-unit-specifier.service [new file with mode: 0644]
test/test-execute/exec-execsearchpath.service [new file with mode: 0644]

index d063d6d4d9fb43fb8299874f414115c59a86b622..e4972104d95963ea4284e5711678e7c4123395da 100644 (file)
@@ -2737,6 +2737,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly as NoExecPaths = ['...', ...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly as ExecSearchPath = ['...', ...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly t MountFlags = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly b PrivateTmp = ...;
@@ -3260,6 +3262,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <!--property NoExecPaths is not documented!-->
 
+    <!--property ExecSearchPath is not documented!-->
+
     <!--property PrivateTmp is not documented!-->
 
     <!--property PrivateDevices is not documented!-->
@@ -3858,6 +3862,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ExecSearchPath"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
@@ -4567,6 +4573,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly as NoExecPaths = ['...', ...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly as ExecSearchPath = ['...', ...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly t MountFlags = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly b PrivateTmp = ...;
@@ -5118,6 +5126,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <!--property NoExecPaths is not documented!-->
 
+    <!--property ExecSearchPath is not documented!-->
+
     <!--property PrivateTmp is not documented!-->
 
     <!--property PrivateDevices is not documented!-->
@@ -5714,6 +5724,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ExecSearchPath"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
@@ -6320,6 +6332,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly as NoExecPaths = ['...', ...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly as ExecSearchPath = ['...', ...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly t MountFlags = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly b PrivateTmp = ...;
@@ -6799,6 +6813,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <!--property NoExecPaths is not documented!-->
 
+    <!--property ExecSearchPath is not documented!-->
+
     <!--property PrivateTmp is not documented!-->
 
     <!--property PrivateDevices is not documented!-->
@@ -7313,6 +7329,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ExecSearchPath"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
@@ -8040,6 +8058,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly as NoExecPaths = ['...', ...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly as ExecSearchPath = ['...', ...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly t MountFlags = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly b PrivateTmp = ...;
@@ -8505,6 +8525,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <!--property NoExecPaths is not documented!-->
 
+    <!--property ExecSearchPath is not documented!-->
+
     <!--property PrivateTmp is not documented!-->
 
     <!--property PrivateDevices is not documented!-->
@@ -9005,6 +9027,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ExecSearchPath"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
index ddcd0f1c2575eca1c8faece3c4e805984b0861b3..3cef36d3c31f9da120728ad405a419bae438710d 100644 (file)
 
     <variablelist class='unit-directives'>
 
+      <varlistentry>
+        <term><varname>ExecSearchPath=</varname></term>
+
+        <listitem><para>Takes a colon separated list of absolute paths relative to which the executable
+        used by the <varname>Exec*=</varname> (e.g. <varname>ExecStart=</varname>,
+        <varname>ExecStop=</varname>, etc.) properties can be found. <varname>ExecSearchPath=</varname>
+        overrides <varname>$PATH</varname> if <varname>$PATH</varname> is not supplied by the user through
+        <varname>Environment=</varname>, <varname>EnvironmentFile=</varname> or
+        <varname>PassEnvironment=</varname>. Assigning an empty string removes previous assignments
+        and setting <varname>ExecSearchPath=</varname> to a value multiple times will append
+        to the previous setting.
+        </para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>WorkingDirectory=</varname></term>
 
index 2a436c545ef210c4ed7dc3e67f07d9855bf8a456..f3e5d3530d6587b9479ed7a57ef464e761346a28 100644 (file)
@@ -142,7 +142,7 @@ int verify_executable(Unit *u, const ExecCommand *exec, const char *root) {
         if (exec->flags & EXEC_COMMAND_IGNORE_FAILURE)
                 return 0;
 
-        r = find_executable_full(exec->path, root, false, NULL, NULL);
+        r = find_executable_full(exec->path, root, NULL, false, NULL, NULL);
         if (r < 0)
                 return log_unit_error_errno(u, r, "Command %s is not executable: %m", exec->path);
 
index a21981616b5966abe5f441deb21bf0652eb8bf76..eee07b6ea568d77ad0f4e9b570aff94fe0f1f829 100644 (file)
@@ -682,8 +682,8 @@ static int find_executable_impl(const char *name, const char *root, char **ret_f
         return 0;
 }
 
-int find_executable_full(const char *name, const char *root, bool use_path_envvar, char **ret_filename, int *ret_fd) {
-        int last_error, r;
+int find_executable_full(const char *name, const char *root, char **exec_search_path, bool use_path_envvar, char **ret_filename, int *ret_fd) {
+        int last_error = -ENOENT, r = 0;
         const char *p = NULL;
 
         assert(name);
@@ -698,7 +698,27 @@ int find_executable_full(const char *name, const char *root, bool use_path_envva
         if (!p)
                 p = DEFAULT_PATH;
 
-        last_error = -ENOENT;
+        if (exec_search_path) {
+                char **element;
+
+                STRV_FOREACH(element, exec_search_path) {
+                        _cleanup_free_ char *full_path = NULL;
+                        if (!path_is_absolute(*element))
+                                continue;
+                        full_path = path_join(*element, name);
+                        if (!full_path)
+                                return -ENOMEM;
+
+                        r = find_executable_impl(full_path, root, ret_filename, ret_fd);
+                        if (r < 0) {
+                                if (r != -EACCES)
+                                        last_error = r;
+                                continue;
+                        }
+                        return 0;
+                }
+                return last_error;
+        }
 
         /* Resolve a single-component name to a full path */
         for (;;) {
index 0d69145497d50a51d6d44856355a629087e71a6e..518f3340bf2ebf94fbbbd4469ca9b1715060cb87 100644 (file)
@@ -99,9 +99,9 @@ int path_strv_make_absolute_cwd(char **l);
 char** path_strv_resolve(char **l, const char *root);
 char** path_strv_resolve_uniq(char **l, const char *root);
 
-int find_executable_full(const char *name, const char *root, bool use_path_envvar, char **ret_filename, int *ret_fd);
+int find_executable_full(const char *name, const char *root, char **exec_search_path, bool use_path_envvar, char **ret_filename, int *ret_fd);
 static inline int find_executable(const char *name, char **ret_filename) {
-        return find_executable_full(name, /* root= */ NULL, true, ret_filename, NULL);
+        return find_executable_full(name, /* root= */ NULL, NULL, true, ret_filename, NULL);
 }
 
 bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update);
index 13a64809e0c4a7b2d260e055f4ccda99f9fa6846..8f6042708cdcf51f6e274d976053042b1dbd1586 100644 (file)
@@ -1161,6 +1161,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_PROPERTY("InaccessiblePaths", "as", NULL, offsetof(ExecContext, inaccessible_paths), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("ExecPaths", "as", NULL, offsetof(ExecContext, exec_paths), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("NoExecPaths", "as", NULL, offsetof(ExecContext, no_exec_paths), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("ExecSearchPath", "as", NULL, offsetof(ExecContext, exec_search_path), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("MountFlags", "t", bus_property_get_ulong, offsetof(ExecContext, mount_flags), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("PrivateTmp", "b", bus_property_get_bool, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -3140,6 +3141,36 @@ int bus_exec_context_set_transient_property(
 
                 return 1;
 
+        } else if (streq(name, "ExecSearchPath")) {
+                _cleanup_strv_free_ char **l = NULL;
+                char **p;
+
+                r = sd_bus_message_read_strv(message, &l);
+                if (r < 0)
+                        return r;
+
+                STRV_FOREACH(p, l) {
+                        if (!path_is_absolute(*p) || !path_is_normalized(*p) || strchr(*p, ':'))
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s", name);
+                }
+                if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+                        if (strv_isempty(l)) {
+                                c->exec_search_path = strv_free(c->exec_search_path);
+                                unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "ExecSearchPath=");
+                        } else {
+                                _cleanup_free_ char *joined = NULL;
+                                r = strv_extend_strv(&c->exec_search_path, l, true);
+                                if (r < 0)
+                                        return -ENOMEM;
+                                joined = strv_join(c->exec_search_path, ":");
+                                if (!joined)
+                                        return log_oom();
+                                unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "ExecSearchPath=%s", joined);
+                        }
+                }
+
+                return 1;
+
         } else if (STR_IN_SET(name, "RuntimeDirectory", "StateDirectory", "CacheDirectory", "LogsDirectory", "ConfigurationDirectory")) {
                 _cleanup_strv_free_ char **l = NULL;
                 char **p;
index 007aab7b0593872b03cede5946898f376d39970e..e7034ce696040a0d252c02a5207ccdcd40421d02 100644 (file)
@@ -3742,7 +3742,7 @@ static int exec_child(
                 int user_lookup_fd,
                 int *exit_status) {
 
-        _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **replaced_argv = NULL;
+        _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **joined_exec_search_path = NULL, **accum_env = NULL, **replaced_argv = NULL;
         int r, ngids = 0, exec_fd;
         _cleanup_free_ gid_t *supplementary_gids = NULL;
         const char *username = NULL, *groupname = NULL;
@@ -4158,8 +4158,31 @@ static int exec_child(
                 return log_oom();
         }
 
+        /* The PATH variable is set to the default path in params->environment.
+         * However, this is overridden if user specified fields have PATH set.
+         * The intention is to also override PATH if the user does
+         * not specify PATH and the user has specified ExecSearchPath
+         */
+
+        if (!strv_isempty(context->exec_search_path)) {
+                _cleanup_free_ char *joined = NULL;
+
+                joined = strv_join(context->exec_search_path, ":");
+                if (!joined) {
+                        *exit_status = EXIT_MEMORY;
+                        return log_oom();
+                }
+
+                r = strv_env_assign(&joined_exec_search_path, "PATH", joined);
+                if (r < 0) {
+                        *exit_status = EXIT_MEMORY;
+                        return log_oom();
+                }
+        }
+
         accum_env = strv_env_merge(params->environment,
                                    our_env,
+                                   joined_exec_search_path,
                                    pass_env,
                                    context->environment,
                                    files_env);
@@ -4350,7 +4373,7 @@ static int exec_child(
 
         _cleanup_free_ char *executable = NULL;
         _cleanup_close_ int executable_fd = -1;
-        r = find_executable_full(command->path, /* root= */ NULL, false, &executable, &executable_fd);
+        r = find_executable_full(command->path, /* root= */ NULL, context->exec_search_path, false, &executable, &executable_fd);
         if (r < 0) {
                 if (r != -ENOMEM && (command->flags & EXEC_COMMAND_IGNORE_FAILURE)) {
                         log_unit_struct_errno(unit, LOG_INFO, r,
@@ -4926,6 +4949,7 @@ void exec_context_done(ExecContext *c) {
         c->inaccessible_paths = strv_free(c->inaccessible_paths);
         c->exec_paths = strv_free(c->exec_paths);
         c->no_exec_paths = strv_free(c->no_exec_paths);
+        c->exec_search_path = strv_free(c->exec_search_path);
 
         bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
         c->bind_mounts = NULL;
@@ -5597,6 +5621,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
         strv_dump(f, prefix, "InaccessiblePaths", c->inaccessible_paths);
         strv_dump(f, prefix, "ExecPaths", c->exec_paths);
         strv_dump(f, prefix, "NoExecPaths", c->no_exec_paths);
+        strv_dump(f, prefix, "ExecSearchPath", c->exec_search_path);
 
         for (size_t i = 0; i < c->n_bind_mounts; i++)
                 fprintf(f, "%s%s: %s%s:%s:%s\n", prefix,
index fceb8bb6f79e1893f13d2fe70b518dd43c51824c..64a38b2d26a277e0d321f81f2c583be0e53d8dae 100644 (file)
@@ -254,6 +254,7 @@ struct ExecContext {
         char *smack_process_label;
 
         char **read_write_paths, **read_only_paths, **inaccessible_paths, **exec_paths, **no_exec_paths;
+        char **exec_search_path;
         unsigned long mount_flags;
         BindMount *bind_mounts;
         size_t n_bind_mounts;
index 17c43005bcae7bc67641422d5e2b9bfeda2c94cf..7bef95bec758d80b830cbc6840da36037f0c6799 100644 (file)
 {{type}}.InaccessiblePaths,                config_parse_namespace_path_strv,            0,                                  offsetof({{type}}, exec_context.inaccessible_paths)
 {{type}}.ExecPaths,                        config_parse_namespace_path_strv,            0,                                  offsetof({{type}}, exec_context.exec_paths)
 {{type}}.NoExecPaths,                      config_parse_namespace_path_strv,            0,                                  offsetof({{type}}, exec_context.no_exec_paths)
+{{type}}.ExecSearchPath,                   config_parse_colon_separated_paths,          0,                                  offsetof({{type}}, exec_context.exec_search_path)
 {{type}}.BindPaths,                        config_parse_bind_paths,                     0,                                  offsetof({{type}}, exec_context)
 {{type}}.BindReadOnlyPaths,                config_parse_bind_paths,                     0,                                  offsetof({{type}}, exec_context)
 {{type}}.TemporaryFileSystem,              config_parse_temporary_filesystems,          0,                                  offsetof({{type}}, exec_context)
index 4f74f44c7dc80e5c78ae114a6d60ef69cba8327f..f971084c28ee6c555168ccedc948c7568367df25 100644 (file)
@@ -305,6 +305,64 @@ int config_parse_unit_path_printf(
         return config_parse_path(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata);
 }
 
+int config_parse_colon_separated_paths(
+                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) {
+        char ***sv = data;
+        const Unit *u = userdata;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (isempty(rvalue)) {
+                /* Empty assignment resets the list */
+                *sv = strv_free(*sv);
+                return 0;
+        }
+
+        for (const char *p = rvalue;;) {
+                _cleanup_free_ char *word = NULL, *k = NULL;
+
+                r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+                if (r == -ENOMEM)
+                        return log_oom();
+                if (r < 0) {
+                        log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to extract first word, ignoring: %s", rvalue);
+                        return 0;
+                }
+                if (r == 0)
+                        break;
+
+                r = unit_path_printf(u, word, &k);
+                if (r < 0) {
+                        log_syntax(unit, LOG_WARNING, filename, line, r,
+                                   "Failed to resolve unit specifiers in '%s', ignoring: %m", word);
+                        return 0;
+                }
+
+                r = path_simplify_and_warn(k, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue);
+                if (r < 0)
+                        return 0;
+
+                r = strv_consume(sv, TAKE_PTR(k));
+                if (r < 0)
+                        return log_oom();
+        }
+
+        return 0;
+}
+
 int config_parse_unit_path_strv_printf(
                 const char *unit,
                 const char *filename,
@@ -5915,6 +5973,7 @@ void unit_dump_config_items(FILE *f) {
                 { config_parse_string,                "STRING" },
                 { config_parse_path,                  "PATH" },
                 { config_parse_unit_path_printf,      "PATH" },
+                { config_parse_colon_separated_paths, "PATH" },
                 { config_parse_strv,                  "STRING [...]" },
                 { config_parse_exec_nice,             "NICE" },
                 { config_parse_exec_oom_score_adjust, "OOMSCOREADJUST" },
index 0d8863aa6353fe128988b819e98da60e769ceaa7..e84f9ee39107619121f7eff002d296fe3b62a761 100644 (file)
@@ -19,6 +19,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_obsolete_unit_deps);
 CONFIG_PARSER_PROTOTYPE(config_parse_unit_string_printf);
 CONFIG_PARSER_PROTOTYPE(config_parse_unit_strv_printf);
 CONFIG_PARSER_PROTOTYPE(config_parse_unit_path_printf);
+CONFIG_PARSER_PROTOTYPE(config_parse_colon_separated_paths);
 CONFIG_PARSER_PROTOTYPE(config_parse_unit_path_strv_printf);
 CONFIG_PARSER_PROTOTYPE(config_parse_documentation);
 CONFIG_PARSER_PROTOTYPE(config_parse_socket_listen);
index 664153137dfdf7685c6198f2d4965bde50ccf4ef..29ab3f33929a41a775ad547590028434d6cffcd5 100644 (file)
@@ -1714,6 +1714,7 @@ static int run(int argc, char* argv[]) {
         if (!strv_isempty(arg_cmdline) &&
             arg_transport == BUS_TRANSPORT_LOCAL &&
             !strv_find_startswith(arg_property, "RootDirectory=") &&
+            !strv_find_startswith(arg_property, "ExecSearchPath=") &&
             !strv_find_startswith(arg_property, "RootImage=")) {
                 /* Patch in an absolute path to fail early for user convenience, but only when we can do it
                  * (i.e. we will be running from the same file system). This also uses the user's $PATH,
index f9637ac0cce872173a6cd3d75061d484ca7da40f..e33e0c21e025422f8a62ff8a3df3d4298f423d2a 100644 (file)
@@ -969,6 +969,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                               "InaccessiblePaths",
                               "ExecPaths",
                               "NoExecPaths",
+                              "ExecSearchPath",
                               "RuntimeDirectory",
                               "StateDirectory",
                               "CacheDirectory",
index 092c78f2b9d250d4a5566fa482493fca69b69ea5..99beb05d29dbab015ba572c47a44f7b6adb46575 100644 (file)
@@ -8,6 +8,7 @@
 
 #include "capability-util.h"
 #include "cpu-set-util.h"
+#include "copy.h"
 #include "dropin.h"
 #include "errno-list.h"
 #include "fd-util.h"
@@ -28,6 +29,7 @@
 #include "static-destruct.h"
 #include "stat-util.h"
 #include "tests.h"
+#include "tmpfile-util.h"
 #include "unit.h"
 #include "user-util.h"
 #include "util.h"
@@ -273,6 +275,93 @@ static void test_exec_workingdirectory(Manager *m) {
         (void) rm_rf("/tmp/test-exec_workingdirectory", REMOVE_ROOT|REMOVE_PHYSICAL);
 }
 
+static void test_exec_execsearchpath(Manager *m) {
+        assert_se(mkdir_p("/tmp/test-exec_execsearchpath", 0755) >= 0);
+
+        assert_se(copy_file("/bin/ls", "/tmp/test-exec_execsearchpath/ls_temp", 0,  0777, 0, 0, COPY_REPLACE) >= 0);
+
+        test(m, "exec-execsearchpath.service", 0, CLD_EXITED);
+
+        assert_se(rm_rf("/tmp/test-exec_execsearchpath", REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+
+        test(m, "exec-execsearchpath.service", EXIT_EXEC, CLD_EXITED);
+}
+
+static void test_exec_execsearchpath_specifier(Manager *m) {
+        test(m, "exec-execsearchpath-unit-specifier.service", 0, CLD_EXITED);
+}
+
+static void test_exec_execsearchpath_environment(Manager *m) {
+        test(m, "exec-execsearchpath-environment.service", 0, CLD_EXITED);
+        test(m, "exec-execsearchpath-environment-path-set.service", 0, CLD_EXITED);
+}
+
+static void test_exec_execsearchpath_environment_files(Manager *m) {
+        static const char path_not_set[] =
+                "VAR1='word1 word2'\n"
+                "VAR2=word3 \n"
+                "# comment1\n"
+                "\n"
+                "; comment2\n"
+                " ; # comment3\n"
+                "line without an equal\n"
+                "VAR3='$word 5 6'\n"
+                "VAR4='new\nline'\n"
+                "VAR5=password\\with\\backslashes";
+
+        static const char path_set[] =
+                "VAR1='word1 word2'\n"
+                "VAR2=word3 \n"
+                "# comment1\n"
+                "\n"
+                "; comment2\n"
+                " ; # comment3\n"
+                "line without an equal\n"
+                "VAR3='$word 5 6'\n"
+                "VAR4='new\nline'\n"
+                "VAR5=password\\with\\backslashes\n"
+                "PATH=/usr";
+
+        int r;
+
+        r = write_string_file("/tmp/test-exec_execsearchpath_environmentfile.conf", path_not_set, WRITE_STRING_FILE_CREATE);
+
+        assert_se(r == 0);
+
+        test(m, "exec-execsearchpath-environmentfile.service", 0, CLD_EXITED);
+
+        (void) unlink("/tmp/test-exec_environmentfile.conf");
+
+
+        r = write_string_file("/tmp/test-exec_execsearchpath_environmentfile-set.conf", path_set, WRITE_STRING_FILE_CREATE);
+
+        assert_se(r == 0);
+
+        test(m, "exec-execsearchpath-environmentfile-set.service", 0, CLD_EXITED);
+
+        (void) unlink("/tmp/test-exec_environmentfile-set.conf");
+}
+
+static void test_exec_execsearchpath_passenvironment(Manager *m) {
+        assert_se(setenv("VAR1", "word1 word2", 1) == 0);
+        assert_se(setenv("VAR2", "word3", 1) == 0);
+        assert_se(setenv("VAR3", "$word 5 6", 1) == 0);
+        assert_se(setenv("VAR4", "new\nline", 1) == 0);
+        assert_se(setenv("VAR5", "passwordwithbackslashes", 1) == 0);
+
+        test(m, "exec-execsearchpath-passenvironment.service", 0, CLD_EXITED);
+
+        assert_se(setenv("PATH", "/usr", 1) == 0);
+        test(m, "exec-execsearchpath-passenvironment-set.service", 0, CLD_EXITED);
+
+        assert_se(unsetenv("VAR1") == 0);
+        assert_se(unsetenv("VAR2") == 0);
+        assert_se(unsetenv("VAR3") == 0);
+        assert_se(unsetenv("VAR4") == 0);
+        assert_se(unsetenv("VAR5") == 0);
+        assert_se(unsetenv("PATH") == 0);
+}
+
 static void test_exec_personality(Manager *m) {
 #if defined(__x86_64__)
         test(m, "exec-personality-x86-64.service", 0, CLD_EXITED);
@@ -1089,11 +1178,16 @@ int main(int argc, char *argv[]) {
                 entry(test_exec_unsetenvironment),
                 entry(test_exec_user),
                 entry(test_exec_workingdirectory),
+                entry(test_exec_execsearchpath),
+                entry(test_exec_execsearchpath_environment),
+                entry(test_exec_execsearchpath_environment_files),
+                entry(test_exec_execsearchpath_passenvironment),
                 {},
         };
         static const test_entry system_tests[] = {
                 entry(test_exec_dynamicuser),
                 entry(test_exec_specifier),
+                entry(test_exec_execsearchpath_specifier),
                 entry(test_exec_systemcallfilter_system),
                 {},
         };
index 1a0fda5d1af90bae9ba32ef4d3d739c69d3c59b9..0e8648aa6da0b82fe17dd67fee82275d8b921709 100644 (file)
@@ -6,6 +6,7 @@
 #include "alloc-util.h"
 #include "exec-util.h"
 #include "fd-util.h"
+#include "fs-util.h"
 #include "macro.h"
 #include "path-util.h"
 #include "process-util.h"
@@ -14,6 +15,7 @@
 #include "string-util.h"
 #include "strv.h"
 #include "tests.h"
+#include "tmpfile-util.h"
 #include "util.h"
 
 static void test_print_paths(void) {
@@ -201,15 +203,18 @@ static void test_path_equal_root(void) {
 
 static void test_find_executable_full(void) {
         char *p;
+        char* test_file_name;
+        _cleanup_close_ int fd = -1;
+        char fn[] = "/tmp/test-XXXXXX";
 
         log_info("/* %s */", __func__);
 
-        assert_se(find_executable_full("sh", NULL, true, &p, NULL) == 0);
+        assert_se(find_executable_full("sh", NULL, NULL, true, &p, NULL) == 0);
         puts(p);
         assert_se(streq(basename(p), "sh"));
         free(p);
 
-        assert_se(find_executable_full("sh", NULL, false, &p, NULL) == 0);
+        assert_se(find_executable_full("sh", NULL, NULL, false, &p, NULL) == 0);
         puts(p);
         assert_se(streq(basename(p), "sh"));
         free(p);
@@ -221,18 +226,31 @@ static void test_find_executable_full(void) {
 
         assert_se(unsetenv("PATH") == 0);
 
-        assert_se(find_executable_full("sh", NULL, true, &p, NULL) == 0);
+        assert_se(find_executable_full("sh", NULL, NULL, true, &p, NULL) == 0);
         puts(p);
         assert_se(streq(basename(p), "sh"));
         free(p);
 
-        assert_se(find_executable_full("sh", NULL, false, &p, NULL) == 0);
+        assert_se(find_executable_full("sh", NULL, NULL, false, &p, NULL) == 0);
         puts(p);
         assert_se(streq(basename(p), "sh"));
         free(p);
 
         if (oldpath)
                 assert_se(setenv("PATH", oldpath, true) >= 0);
+
+        assert_se((fd = mkostemp_safe(fn)) >= 0);
+        assert_se(fchmod(fd, 0755) >= 0);
+
+        test_file_name = basename(fn);
+
+        assert_se(find_executable_full(test_file_name, NULL, STRV_MAKE("/doesnotexist", "/tmp", "/bin"), false, &p, NULL) == 0);
+        puts(p);
+        assert_se(streq(p, fn));
+        free(p);
+
+        (void) unlink(fn);
+        assert_se(find_executable_full(test_file_name, NULL, STRV_MAKE("/doesnotexist", "/tmp", "/bin"), false, &p, NULL) == -ENOENT);
 }
 
 static void test_find_executable(const char *self) {
@@ -277,7 +295,7 @@ static void test_find_executable_exec_one(const char *path) {
         pid_t pid;
         int r;
 
-        r = find_executable_full(path, NULL, false, &t, &fd);
+        r = find_executable_full(path, NULL, NULL, false, &t, &fd);
 
         log_info_errno(r, "%s: %s → %s: %d/%m", __func__, path, t ?: "-", fd);
 
index 33c44c79c5d54996a2cc964baacc4354b9dba76a..bdb8f95917ef467186f1b5fc7c83bcf490792d4e 100644 (file)
@@ -5,6 +5,7 @@ AllowedMemoryNodes=
 AmbientCapabilities=
 AppArmorProfile=
 BPFProgram=
+ExecSearchPath=
 BindPaths=
 BindReadOnlyPaths=
 BlockIOAccounting=
index d92d90e1181137911111026cbb4cdb29ad41f6f6..aeea1d8731c8017d6dedbbc395631b13415c5f8c 100644 (file)
@@ -31,6 +31,7 @@ AssertUser=
 AssertVirtualization=
 BPFProgram=
 Before=
+ExecSearchPath=
 BindTo=
 BindsTo=
 CollectMode=
index fa28ff10421698830124dc15c033dced103d30f8..04aeb476f11b4acc972c20fcd43553f34cd3922d 100644 (file)
@@ -7,6 +7,7 @@ AmbientCapabilities=
 AppArmorProfile=
 BPFProgram=
 Backlog=
+ExecSearchPath=
 BindIPv6Only=
 BindPaths=
 BindReadOnlyPaths=
index abb3cd54e71b541c6cd5d7d5a6e9fa33c6e7bad2..ff54ebfc1891b560456cd52db900e6ed3f561e28 100644 (file)
@@ -5,6 +5,7 @@ AllowedMemoryNodes=
 AmbientCapabilities=
 AppArmorProfile=
 BPFProgram=
+ExecSearchPath=
 BindPaths=
 BindReadOnlyPaths=
 BlockIOAccounting=
diff --git a/test/test-execute/exec-execsearchpath-environment-path-set.service b/test/test-execute/exec-execsearchpath-environment-path-set.service
new file mode 100644 (file)
index 0000000..dd27de8
--- /dev/null
@@ -0,0 +1,5 @@
+[Service]
+ExecStart=/bin/sh -x -c 'test "$$PATH" = "/usr" && test "$$VAR1" = word3 && test "$$VAR2" = "\\$$word 5 6"'
+Type=oneshot
+ExecSearchPath=/tmp:/bin
+Environment="PATH=/usr" VAR1=word3 "VAR2=$word 5 6"
diff --git a/test/test-execute/exec-execsearchpath-environment.service b/test/test-execute/exec-execsearchpath-environment.service
new file mode 100644 (file)
index 0000000..ba477fb
--- /dev/null
@@ -0,0 +1,5 @@
+[Service]
+ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$PATH" = "/tmp:/bin"'
+Type=oneshot
+ExecSearchPath=/tmp:/bin
+Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
diff --git a/test/test-execute/exec-execsearchpath-environmentfile-set.service b/test/test-execute/exec-execsearchpath-environmentfile-set.service
new file mode 100644 (file)
index 0000000..45877b1
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description=Test for ExecSearchPath with EnvironmentFile where EnvironmentFile sets PATH
+
+[Service]
+ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = /usr'
+Type=oneshot
+EnvironmentFile=/tmp/test-exec_execsearchpath_environmentfile-set.conf
+ExecSearchPath=/tmp:/bin
diff --git a/test/test-execute/exec-execsearchpath-environmentfile.service b/test/test-execute/exec-execsearchpath-environmentfile.service
new file mode 100644 (file)
index 0000000..65a04e7
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description=Test for ExecSearchPath with EnvironmentFile where EnvironmentFile does not set PATH
+
+[Service]
+ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"'
+Type=oneshot
+ExecSearchPath=/tmp:/bin
+EnvironmentFile=/tmp/test-exec_execsearchpath_environmentfile.conf
diff --git a/test/test-execute/exec-execsearchpath-passenvironment-set.service b/test/test-execute/exec-execsearchpath-passenvironment-set.service
new file mode 100644 (file)
index 0000000..62c7742
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description=Test for PassEnvironment with ExecSearchPath with PATH set by user
+
+[Service]
+ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/usr"'
+Type=oneshot
+PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5 PATH
+ExecSearchPath=/tmp:/bin
diff --git a/test/test-execute/exec-execsearchpath-passenvironment.service b/test/test-execute/exec-execsearchpath-passenvironment.service
new file mode 100644 (file)
index 0000000..83eb0d6
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description=Test for PassEnvironment with ExecSearchPath with PATH not set by user
+
+[Service]
+ExecStart=/bin/sh -x -c 'test "$$VAR1" = "word1 word2" && test "$$VAR2" = word3 && test "$$VAR3" = "\\$$word 5 6" && test "$$VAR4" = "new\nline" && test "$$VAR5" = passwordwithbackslashes && test "$$PATH" = "/tmp:/bin"'
+Type=oneshot
+PassEnvironment=VAR1 VAR2 VAR3 VAR4 VAR5
+ExecSearchPath=/tmp:/bin
diff --git a/test/test-execute/exec-execsearchpath-unit-specifier.service b/test/test-execute/exec-execsearchpath-unit-specifier.service
new file mode 100644 (file)
index 0000000..740bab1
--- /dev/null
@@ -0,0 +1,7 @@
+[Unit]
+Description=Test for specifiers with exec search path
+
+[Service]
+Type=oneshot
+ExecSearchPath=/tmp:/bin:/usr/bin:%V
+ExecStart=/bin/sh -x -c 'test %V = /var/tmp && test "$$PATH" = "/tmp:/bin:/usr/bin:/var/tmp"'
diff --git a/test/test-execute/exec-execsearchpath.service b/test/test-execute/exec-execsearchpath.service
new file mode 100644 (file)
index 0000000..67b7170
--- /dev/null
@@ -0,0 +1,4 @@
+[Service]
+ExecStart=ls_temp
+Type=oneshot
+ExecSearchPath=/tmp/test-exec_execsearchpath