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("test", dirs
, DEFAULT_TIMEOUT_USEC
, ignore_stdout
, ignore_stdout_args
, NULL
, NULL
, EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
122 execute_directories("test", 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(__func__
,
193 dirs
, DEFAULT_TIMEOUT_USEC
, ignore_stdout
, ignore_stdout_args
, NULL
, NULL
,
194 EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
196 assert_se(read_full_file(output
, &contents
, NULL
) >= 0);
197 ASSERT_STREQ(contents
, "30-override\n80-foo\n90-bar\nlast\n");
200 static int gather_stdout_one(int fd
, void *arg
) {
205 assert_se(read(fd
, buf
, sizeof buf
) >= 0);
208 assert_se(t
= strndup(buf
, sizeof buf
));
209 assert_se(strv_push(s
, t
) >= 0);
213 static int gather_stdout_two(int fd
, void *arg
) {
217 assert_se(write(fd
, *t
, strlen(*t
)) == (ssize_t
) strlen(*t
));
222 static int gather_stdout_three(int fd
, void *arg
) {
226 assert_se(read(fd
, buf
, sizeof buf
- 1) > 0);
228 assert_se(*s
= strndup(buf
, sizeof buf
));
233 const gather_stdout_callback_t gather_stdouts
[] = {
239 TEST(stdout_gathering
) {
240 _cleanup_(rm_rf_physical_and_freep
) char *tmpdir
= NULL
;
241 const char *name
, *name2
, *name3
;
244 char **tmp
= NULL
; /* this is only used in the forked process, no cleanup here */
245 _cleanup_free_
char *output
= NULL
;
247 void* args
[] = {&tmp
, &tmp
, &output
};
249 assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir
) >= 0);
251 const char *dirs
[] = { tmpdir
, NULL
};
254 name
= strjoina(tmpdir
, "/10-foo");
255 name2
= strjoina(tmpdir
, "/20-bar");
256 name3
= strjoina(tmpdir
, "/30-last");
258 assert_se(write_string_file(name
,
259 "#!/bin/sh\necho a\necho b\necho c\n",
260 WRITE_STRING_FILE_CREATE
) == 0);
261 assert_se(write_string_file(name2
,
262 "#!/bin/sh\necho d\n",
263 WRITE_STRING_FILE_CREATE
) == 0);
264 assert_se(write_string_file(name3
,
265 "#!/bin/sh\nsleep 1",
266 WRITE_STRING_FILE_CREATE
) == 0);
268 assert_se(chmod(name
, 0755) == 0);
269 assert_se(chmod(name2
, 0755) == 0);
270 assert_se(chmod(name3
, 0755) == 0);
272 if (access(name
, X_OK
) < 0 && ERRNO_IS_PRIVILEGE(errno
))
275 r
= execute_directories(__func__
,
276 dirs
, DEFAULT_TIMEOUT_USEC
, gather_stdouts
, args
, NULL
, NULL
,
277 EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
280 log_info("got: %s", output
);
282 ASSERT_STREQ(output
, "a\nb\nc\nd\n");
285 TEST(environment_gathering
) {
286 _cleanup_(rm_rf_physical_and_freep
) char *tmpdir
= NULL
;
287 const char *name
, *name2
, *name3
, *old
;
290 char **tmp
= NULL
; /* this is only used in the forked process, no cleanup here */
291 _cleanup_strv_free_
char **env
= NULL
;
293 void* const args
[] = { &tmp
, &tmp
, &env
};
295 assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir
) >= 0);
297 const char *dirs
[] = { tmpdir
, NULL
};
300 name
= strjoina(tmpdir
, "/10-foo");
301 name2
= strjoina(tmpdir
, "/20-bar");
302 name3
= strjoina(tmpdir
, "/30-last");
304 assert_se(write_string_file(name
,
307 WRITE_STRING_FILE_CREATE
) == 0);
308 assert_se(write_string_file(name2
,
310 "echo A=22:$A\n\n\n", /* substitution from previous generator */
311 WRITE_STRING_FILE_CREATE
) == 0);
312 assert_se(write_string_file(name3
,
317 "echo C=001\n" /* variable overwriting */
318 /* various invalid entries */
325 /* test variable assignment without newline */
326 "echo PATH=$PATH:/no/such/file", /* no newline */
327 WRITE_STRING_FILE_CREATE
) == 0);
329 assert_se(chmod(name
, 0755) == 0);
330 assert_se(chmod(name2
, 0755) == 0);
331 assert_se(chmod(name3
, 0755) == 0);
333 /* When booting in containers or without initrd there might not be any PATH in the environment and if
334 * there is no PATH /bin/sh built-in PATH may leak and override systemd's default path which is not
335 * good. Force our own PATH in environment, to prevent expansion of sh built-in $PATH */
336 old
= getenv("PATH");
337 r
= setenv("PATH", "no-sh-built-in-path", 1);
340 if (access(name
, X_OK
) < 0 && ERRNO_IS_PRIVILEGE(errno
))
343 r
= execute_directories(__func__
,
344 dirs
, DEFAULT_TIMEOUT_USEC
, gather_environment
, args
, NULL
, NULL
,
345 EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
349 log_info("got env: \"%s\"", *p
);
351 ASSERT_STREQ(strv_env_get(env
, "A"), "22:23:24");
352 ASSERT_STREQ(strv_env_get(env
, "B"), "12");
353 ASSERT_STREQ(strv_env_get(env
, "C"), "001");
354 ASSERT_STREQ(strv_env_get(env
, "PATH"), "no-sh-built-in-path:/no/such/file");
356 /* Now retest with some "default" path passed. */
357 env
= strv_free(env
);
358 env
= strv_new("PATH=" DEFAULT_PATH_WITHOUT_SBIN
);
361 r
= execute_directories(__func__
,
362 dirs
, DEFAULT_TIMEOUT_USEC
, gather_environment
, args
, NULL
, env
,
363 EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
367 log_info("got env: \"%s\"", *p
);
369 ASSERT_STREQ(strv_env_get(env
, "A"), "22:23:24");
370 ASSERT_STREQ(strv_env_get(env
, "B"), "12");
371 ASSERT_STREQ(strv_env_get(env
, "C"), "001");
372 ASSERT_STREQ(strv_env_get(env
, "PATH"), DEFAULT_PATH_WITHOUT_SBIN
":/no/such/file");
374 /* reset environ PATH */
375 assert_se(set_unset_env("PATH", old
, true) == 0);
378 TEST(error_catching
) {
379 _cleanup_(rm_rf_physical_and_freep
) char *tmpdir
= NULL
;
380 const char *name
, *name2
, *name3
;
383 assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir
) >= 0);
385 const char *dirs
[] = { tmpdir
, NULL
};
388 name
= strjoina(tmpdir
, "/10-foo");
389 name2
= strjoina(tmpdir
, "/20-bar");
390 name3
= strjoina(tmpdir
, "/30-last");
392 assert_se(write_string_file(name
,
393 "#!/bin/sh\necho a\necho b\necho c\n",
394 WRITE_STRING_FILE_CREATE
) == 0);
395 assert_se(write_string_file(name2
,
396 "#!/bin/sh\nexit 42\n",
397 WRITE_STRING_FILE_CREATE
) == 0);
398 assert_se(write_string_file(name3
,
399 "#!/bin/sh\nexit 12",
400 WRITE_STRING_FILE_CREATE
) == 0);
402 assert_se(chmod(name
, 0755) == 0);
403 assert_se(chmod(name2
, 0755) == 0);
404 assert_se(chmod(name3
, 0755) == 0);
406 if (access(name
, X_OK
) < 0 && ERRNO_IS_PRIVILEGE(errno
))
409 r
= execute_directories(__func__
,
410 dirs
, DEFAULT_TIMEOUT_USEC
,
411 /* callbacks = */ NULL
, /* callback_args = */ NULL
,
412 /* argv = */ NULL
, /* envp = */ NULL
, /* flags = */ 0);
414 /* we should exit with the error code of the first script that failed */
418 TEST(exec_command_flags_from_strv
) {
419 ExecCommandFlags flags
= 0;
420 char **valid_strv
= STRV_MAKE("no-env-expand", "no-setuid", "ignore-failure");
421 char **invalid_strv
= STRV_MAKE("no-env-expand", "no-setuid", "nonexistent-option", "ignore-failure");
424 r
= exec_command_flags_from_strv(valid_strv
, &flags
);
427 assert_se(FLAGS_SET(flags
, EXEC_COMMAND_NO_ENV_EXPAND
));
428 assert_se(FLAGS_SET(flags
, EXEC_COMMAND_NO_SETUID
));
429 assert_se(FLAGS_SET(flags
, EXEC_COMMAND_IGNORE_FAILURE
));
430 assert_se(!FLAGS_SET(flags
, EXEC_COMMAND_FULLY_PRIVILEGED
));
432 r
= exec_command_flags_from_strv(invalid_strv
, &flags
);
434 assert_se(r
== -EINVAL
);
437 TEST(exec_command_flags_to_strv
) {
438 _cleanup_strv_free_
char **opts
= NULL
;
440 ASSERT_OK(exec_command_flags_to_strv(EXEC_COMMAND_NO_ENV_EXPAND
|EXEC_COMMAND_IGNORE_FAILURE
, &opts
));
441 assert_se(strv_equal(opts
, STRV_MAKE("ignore-failure", "no-env-expand")));
443 opts
= strv_free(opts
);
445 ASSERT_OK(exec_command_flags_to_strv(0, &opts
));
446 assert_se(strv_isempty(opts
));
448 opts
= strv_free(opts
);
450 ASSERT_ERROR(exec_command_flags_to_strv(1U << 20, &opts
), EINVAL
);
453 DEFINE_TEST_MAIN(LOG_DEBUG
);