1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "alloc-util.h"
10 #include "exec-util.h"
15 #include "path-util.h"
17 #include "string-util.h"
20 #include "tmpfile-util.h"
22 static int here
= 0, here2
= 0, here3
= 0;
23 static void *ignore_stdout_args
[] = { &here
, &here2
, &here3
};
25 /* noop handlers, just check that arguments are passed correctly */
26 static int ignore_stdout_func(int fd
, void *arg
) {
28 assert_se(arg
== &here
);
33 static int ignore_stdout_func2(int fd
, void *arg
) {
35 assert_se(arg
== &here2
);
40 static int ignore_stdout_func3(int fd
, void *arg
) {
42 assert_se(arg
== &here3
);
48 static const gather_stdout_callback_t ignore_stdout
[] = {
54 static void test_execute_directory_one(bool gather_stdout
) {
55 _cleanup_(rm_rf_physical_and_freep
) char *tmp_lo
= NULL
, *tmp_hi
= NULL
;
56 const char *name
, *name2
, *name3
,
57 *overridden
, *override
,
59 *masked2
, *mask2
, /* the mask is non-executable */
60 *masked2e
, *mask2e
; /* the mask is executable */
62 log_info("/* %s (%s) */", __func__
, gather_stdout
? "gathering stdout" : "asynchronous");
64 assert_se(mkdtemp_malloc("/tmp/test-exec-util.lo.XXXXXXX", &tmp_lo
) >= 0);
65 assert_se(mkdtemp_malloc("/tmp/test-exec-util.hi.XXXXXXX", &tmp_hi
) >= 0);
67 const char * dirs
[] = { tmp_hi
, tmp_lo
, NULL
};
69 name
= strjoina(tmp_lo
, "/script");
70 name2
= strjoina(tmp_hi
, "/script2");
71 name3
= strjoina(tmp_lo
, "/useless");
72 overridden
= strjoina(tmp_lo
, "/overridden");
73 override
= strjoina(tmp_hi
, "/overridden");
74 masked
= strjoina(tmp_lo
, "/masked");
75 mask
= strjoina(tmp_hi
, "/masked");
76 masked2
= strjoina(tmp_lo
, "/masked2");
77 mask2
= strjoina(tmp_hi
, "/masked2");
78 masked2e
= strjoina(tmp_lo
, "/masked2e");
79 mask2e
= strjoina(tmp_hi
, "/masked2e");
81 assert_se(write_string_file(name
,
82 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works",
83 WRITE_STRING_FILE_CREATE
) == 0);
84 assert_se(write_string_file(name2
,
85 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works2",
86 WRITE_STRING_FILE_CREATE
) == 0);
87 assert_se(write_string_file(overridden
,
88 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
89 WRITE_STRING_FILE_CREATE
) == 0);
90 assert_se(write_string_file(override
,
91 "#!/bin/sh\necho 'Executing '$0",
92 WRITE_STRING_FILE_CREATE
) == 0);
93 assert_se(write_string_file(masked
,
94 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
95 WRITE_STRING_FILE_CREATE
) == 0);
96 assert_se(write_string_file(masked2
,
97 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
98 WRITE_STRING_FILE_CREATE
) == 0);
99 assert_se(write_string_file(masked2e
,
100 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
101 WRITE_STRING_FILE_CREATE
) == 0);
102 assert_se(symlink("/dev/null", mask
) == 0);
103 assert_se(touch(mask2
) == 0);
104 assert_se(touch(mask2e
) == 0);
105 assert_se(touch(name3
) >= 0);
107 assert_se(chmod(name
, 0755) == 0);
108 assert_se(chmod(name2
, 0755) == 0);
109 assert_se(chmod(overridden
, 0755) == 0);
110 assert_se(chmod(override
, 0755) == 0);
111 assert_se(chmod(masked
, 0755) == 0);
112 assert_se(chmod(masked2
, 0755) == 0);
113 assert_se(chmod(masked2e
, 0755) == 0);
114 assert_se(chmod(mask2e
, 0755) == 0);
116 if (access(name
, X_OK
) < 0 && ERRNO_IS_PRIVILEGE(errno
))
120 execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
, ignore_stdout
, ignore_stdout_args
, NULL
, NULL
, EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
122 execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
, NULL
, NULL
, NULL
, NULL
, EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
124 assert_se(chdir(tmp_lo
) == 0);
125 assert_se(access("it_works", F_OK
) >= 0);
126 assert_se(access("failed", F_OK
) < 0);
128 assert_se(chdir(tmp_hi
) == 0);
129 assert_se(access("it_works2", F_OK
) >= 0);
130 assert_se(access("failed", F_OK
) < 0);
133 TEST(execute_directory
) {
134 test_execute_directory_one(true);
135 test_execute_directory_one(false);
138 TEST(execution_order
) {
139 _cleanup_(rm_rf_physical_and_freep
) char *tmp_lo
= NULL
, *tmp_hi
= NULL
;
140 const char *name
, *name2
, *name3
, *overridden
, *override
, *masked
, *mask
;
141 const char *output
, *t
;
142 _cleanup_free_
char *contents
= NULL
;
144 assert_se(mkdtemp_malloc("/tmp/test-exec-util-lo.XXXXXXX", &tmp_lo
) >= 0);
145 assert_se(mkdtemp_malloc("/tmp/test-exec-util-hi.XXXXXXX", &tmp_hi
) >= 0);
147 const char *dirs
[] = { tmp_hi
, tmp_lo
, NULL
};
149 output
= strjoina(tmp_hi
, "/output");
151 log_info("/* %s >>%s */", __func__
, output
);
153 /* write files in "random" order */
154 name2
= strjoina(tmp_lo
, "/90-bar");
155 name
= strjoina(tmp_hi
, "/80-foo");
156 name3
= strjoina(tmp_lo
, "/last");
157 overridden
= strjoina(tmp_lo
, "/30-override");
158 override
= strjoina(tmp_hi
, "/30-override");
159 masked
= strjoina(tmp_lo
, "/10-masked");
160 mask
= strjoina(tmp_hi
, "/10-masked");
162 t
= strjoina("#!/bin/sh\necho $(basename $0) >>", output
);
163 assert_se(write_string_file(name
, t
, WRITE_STRING_FILE_CREATE
) == 0);
165 t
= strjoina("#!/bin/sh\necho $(basename $0) >>", output
);
166 assert_se(write_string_file(name2
, t
, WRITE_STRING_FILE_CREATE
) == 0);
168 t
= strjoina("#!/bin/sh\necho $(basename $0) >>", output
);
169 assert_se(write_string_file(name3
, t
, WRITE_STRING_FILE_CREATE
) == 0);
171 t
= strjoina("#!/bin/sh\necho OVERRIDDEN >>", output
);
172 assert_se(write_string_file(overridden
, t
, WRITE_STRING_FILE_CREATE
) == 0);
174 t
= strjoina("#!/bin/sh\necho $(basename $0) >>", output
);
175 assert_se(write_string_file(override
, t
, WRITE_STRING_FILE_CREATE
) == 0);
177 t
= strjoina("#!/bin/sh\necho MASKED >>", output
);
178 assert_se(write_string_file(masked
, t
, WRITE_STRING_FILE_CREATE
) == 0);
180 assert_se(symlink("/dev/null", mask
) == 0);
182 assert_se(chmod(name
, 0755) == 0);
183 assert_se(chmod(name2
, 0755) == 0);
184 assert_se(chmod(name3
, 0755) == 0);
185 assert_se(chmod(overridden
, 0755) == 0);
186 assert_se(chmod(override
, 0755) == 0);
187 assert_se(chmod(masked
, 0755) == 0);
189 if (access(name
, X_OK
) < 0 && ERRNO_IS_PRIVILEGE(errno
))
192 execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
, ignore_stdout
, ignore_stdout_args
, NULL
, NULL
, EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
194 assert_se(read_full_file(output
, &contents
, NULL
) >= 0);
195 ASSERT_STREQ(contents
, "30-override\n80-foo\n90-bar\nlast\n");
198 static int gather_stdout_one(int fd
, void *arg
) {
203 assert_se(read(fd
, buf
, sizeof buf
) >= 0);
206 assert_se(t
= strndup(buf
, sizeof buf
));
207 assert_se(strv_push(s
, t
) >= 0);
211 static int gather_stdout_two(int fd
, void *arg
) {
215 assert_se(write(fd
, *t
, strlen(*t
)) == (ssize_t
) strlen(*t
));
220 static int gather_stdout_three(int fd
, void *arg
) {
224 assert_se(read(fd
, buf
, sizeof buf
- 1) > 0);
226 assert_se(*s
= strndup(buf
, sizeof buf
));
231 const gather_stdout_callback_t gather_stdouts
[] = {
237 TEST(stdout_gathering
) {
238 _cleanup_(rm_rf_physical_and_freep
) char *tmpdir
= NULL
;
239 const char *name
, *name2
, *name3
;
242 char **tmp
= NULL
; /* this is only used in the forked process, no cleanup here */
243 _cleanup_free_
char *output
= NULL
;
245 void* args
[] = {&tmp
, &tmp
, &output
};
247 assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir
) >= 0);
249 const char *dirs
[] = { tmpdir
, NULL
};
252 name
= strjoina(tmpdir
, "/10-foo");
253 name2
= strjoina(tmpdir
, "/20-bar");
254 name3
= strjoina(tmpdir
, "/30-last");
256 assert_se(write_string_file(name
,
257 "#!/bin/sh\necho a\necho b\necho c\n",
258 WRITE_STRING_FILE_CREATE
) == 0);
259 assert_se(write_string_file(name2
,
260 "#!/bin/sh\necho d\n",
261 WRITE_STRING_FILE_CREATE
) == 0);
262 assert_se(write_string_file(name3
,
263 "#!/bin/sh\nsleep 1",
264 WRITE_STRING_FILE_CREATE
) == 0);
266 assert_se(chmod(name
, 0755) == 0);
267 assert_se(chmod(name2
, 0755) == 0);
268 assert_se(chmod(name3
, 0755) == 0);
270 if (access(name
, X_OK
) < 0 && ERRNO_IS_PRIVILEGE(errno
))
273 r
= execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
, gather_stdouts
, args
, NULL
, NULL
,
274 EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
277 log_info("got: %s", output
);
279 ASSERT_STREQ(output
, "a\nb\nc\nd\n");
282 TEST(environment_gathering
) {
283 _cleanup_(rm_rf_physical_and_freep
) char *tmpdir
= NULL
;
284 const char *name
, *name2
, *name3
, *old
;
287 char **tmp
= NULL
; /* this is only used in the forked process, no cleanup here */
288 _cleanup_strv_free_
char **env
= NULL
;
290 void* const args
[] = { &tmp
, &tmp
, &env
};
292 assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir
) >= 0);
294 const char *dirs
[] = { tmpdir
, NULL
};
297 name
= strjoina(tmpdir
, "/10-foo");
298 name2
= strjoina(tmpdir
, "/20-bar");
299 name3
= strjoina(tmpdir
, "/30-last");
301 assert_se(write_string_file(name
,
304 WRITE_STRING_FILE_CREATE
) == 0);
305 assert_se(write_string_file(name2
,
307 "echo A=22:$A\n\n\n", /* substitution from previous generator */
308 WRITE_STRING_FILE_CREATE
) == 0);
309 assert_se(write_string_file(name3
,
314 "echo C=001\n" /* variable overwriting */
315 /* various invalid entries */
322 /* test variable assignment without newline */
323 "echo PATH=$PATH:/no/such/file", /* no newline */
324 WRITE_STRING_FILE_CREATE
) == 0);
326 assert_se(chmod(name
, 0755) == 0);
327 assert_se(chmod(name2
, 0755) == 0);
328 assert_se(chmod(name3
, 0755) == 0);
330 /* When booting in containers or without initrd there might not be any PATH in the environment and if
331 * there is no PATH /bin/sh built-in PATH may leak and override systemd's default path which is not
332 * good. Force our own PATH in environment, to prevent expansion of sh built-in $PATH */
333 old
= getenv("PATH");
334 r
= setenv("PATH", "no-sh-built-in-path", 1);
337 if (access(name
, X_OK
) < 0 && ERRNO_IS_PRIVILEGE(errno
))
340 r
= execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
, gather_environment
, args
, NULL
, NULL
, EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
344 log_info("got env: \"%s\"", *p
);
346 ASSERT_STREQ(strv_env_get(env
, "A"), "22:23:24");
347 ASSERT_STREQ(strv_env_get(env
, "B"), "12");
348 ASSERT_STREQ(strv_env_get(env
, "C"), "001");
349 ASSERT_STREQ(strv_env_get(env
, "PATH"), "no-sh-built-in-path:/no/such/file");
351 /* Now retest with some "default" path passed. */
352 env
= strv_free(env
);
353 env
= strv_new("PATH=" DEFAULT_PATH_WITHOUT_SBIN
);
356 r
= execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
, gather_environment
, args
, NULL
, env
, EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
360 log_info("got env: \"%s\"", *p
);
362 ASSERT_STREQ(strv_env_get(env
, "A"), "22:23:24");
363 ASSERT_STREQ(strv_env_get(env
, "B"), "12");
364 ASSERT_STREQ(strv_env_get(env
, "C"), "001");
365 ASSERT_STREQ(strv_env_get(env
, "PATH"), DEFAULT_PATH_WITHOUT_SBIN
":/no/such/file");
367 /* reset environ PATH */
368 assert_se(set_unset_env("PATH", old
, true) == 0);
371 TEST(error_catching
) {
372 _cleanup_(rm_rf_physical_and_freep
) char *tmpdir
= NULL
;
373 const char *name
, *name2
, *name3
;
376 assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir
) >= 0);
378 const char *dirs
[] = { tmpdir
, NULL
};
381 name
= strjoina(tmpdir
, "/10-foo");
382 name2
= strjoina(tmpdir
, "/20-bar");
383 name3
= strjoina(tmpdir
, "/30-last");
385 assert_se(write_string_file(name
,
386 "#!/bin/sh\necho a\necho b\necho c\n",
387 WRITE_STRING_FILE_CREATE
) == 0);
388 assert_se(write_string_file(name2
,
389 "#!/bin/sh\nexit 42\n",
390 WRITE_STRING_FILE_CREATE
) == 0);
391 assert_se(write_string_file(name3
,
392 "#!/bin/sh\nexit 12",
393 WRITE_STRING_FILE_CREATE
) == 0);
395 assert_se(chmod(name
, 0755) == 0);
396 assert_se(chmod(name2
, 0755) == 0);
397 assert_se(chmod(name3
, 0755) == 0);
399 if (access(name
, X_OK
) < 0 && ERRNO_IS_PRIVILEGE(errno
))
402 r
= execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
,
403 /* callbacks = */ NULL
, /* callback_args = */ NULL
,
404 /* argv = */ NULL
, /* envp = */ NULL
, /* flags = */ 0);
406 /* we should exit with the error code of the first script that failed */
410 TEST(exec_command_flags_from_strv
) {
411 ExecCommandFlags flags
= 0;
412 char **valid_strv
= STRV_MAKE("no-env-expand", "no-setuid", "ignore-failure");
413 char **invalid_strv
= STRV_MAKE("no-env-expand", "no-setuid", "nonexistent-option", "ignore-failure");
416 r
= exec_command_flags_from_strv(valid_strv
, &flags
);
419 assert_se(FLAGS_SET(flags
, EXEC_COMMAND_NO_ENV_EXPAND
));
420 assert_se(FLAGS_SET(flags
, EXEC_COMMAND_NO_SETUID
));
421 assert_se(FLAGS_SET(flags
, EXEC_COMMAND_IGNORE_FAILURE
));
422 assert_se(!FLAGS_SET(flags
, EXEC_COMMAND_FULLY_PRIVILEGED
));
424 r
= exec_command_flags_from_strv(invalid_strv
, &flags
);
426 assert_se(r
== -EINVAL
);
429 TEST(exec_command_flags_to_strv
) {
430 _cleanup_strv_free_
char **opts
= NULL
;
432 ASSERT_OK(exec_command_flags_to_strv(EXEC_COMMAND_NO_ENV_EXPAND
|EXEC_COMMAND_IGNORE_FAILURE
, &opts
));
433 assert_se(strv_equal(opts
, STRV_MAKE("ignore-failure", "no-env-expand")));
435 opts
= strv_free(opts
);
437 ASSERT_OK(exec_command_flags_to_strv(0, &opts
));
438 assert_se(strv_isempty(opts
));
440 opts
= strv_free(opts
);
442 ASSERT_ERROR(exec_command_flags_to_strv(1U << 20, &opts
), EINVAL
);
445 DEFINE_TEST_MAIN(LOG_DEBUG
);