1 /* SPDX-License-Identifier: LGPL-2.1+ */
9 #include "alloc-util.h"
13 #include "exec-util.h"
19 #include "path-util.h"
21 #include "string-util.h"
25 static int here
= 0, here2
= 0, here3
= 0;
26 void *ignore_stdout_args
[] = {&here
, &here2
, &here3
};
28 /* noop handlers, just check that arguments are passed correctly */
29 static int ignore_stdout_func(int fd
, void *arg
) {
36 static int ignore_stdout_func2(int fd
, void *arg
) {
38 assert(arg
== &here2
);
43 static int ignore_stdout_func3(int fd
, void *arg
) {
45 assert(arg
== &here3
);
51 static const gather_stdout_callback_t ignore_stdout
[] = {
57 static void test_execute_directory(bool gather_stdout
) {
58 char template_lo
[] = "/tmp/test-exec-util.lo.XXXXXXX";
59 char template_hi
[] = "/tmp/test-exec-util.hi.XXXXXXX";
60 const char * dirs
[] = {template_hi
, template_lo
, NULL
};
61 const char *name
, *name2
, *name3
,
62 *overridden
, *override
,
64 *masked2
, *mask2
, /* the mask is non-executable */
65 *masked2e
, *mask2e
; /* the mask is executable */
67 log_info("/* %s (%s) */", __func__
, gather_stdout
? "gathering stdout" : "asynchronous");
69 assert_se(mkdtemp(template_lo
));
70 assert_se(mkdtemp(template_hi
));
72 name
= strjoina(template_lo
, "/script");
73 name2
= strjoina(template_hi
, "/script2");
74 name3
= strjoina(template_lo
, "/useless");
75 overridden
= strjoina(template_lo
, "/overridden");
76 override
= strjoina(template_hi
, "/overridden");
77 masked
= strjoina(template_lo
, "/masked");
78 mask
= strjoina(template_hi
, "/masked");
79 masked2
= strjoina(template_lo
, "/masked2");
80 mask2
= strjoina(template_hi
, "/masked2");
81 masked2e
= strjoina(template_lo
, "/masked2e");
82 mask2e
= strjoina(template_hi
, "/masked2e");
84 assert_se(write_string_file(name
,
85 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works",
86 WRITE_STRING_FILE_CREATE
) == 0);
87 assert_se(write_string_file(name2
,
88 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works2",
89 WRITE_STRING_FILE_CREATE
) == 0);
90 assert_se(write_string_file(overridden
,
91 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
92 WRITE_STRING_FILE_CREATE
) == 0);
93 assert_se(write_string_file(override
,
94 "#!/bin/sh\necho 'Executing '$0",
95 WRITE_STRING_FILE_CREATE
) == 0);
96 assert_se(write_string_file(masked
,
97 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
98 WRITE_STRING_FILE_CREATE
) == 0);
99 assert_se(write_string_file(masked2
,
100 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
101 WRITE_STRING_FILE_CREATE
) == 0);
102 assert_se(write_string_file(masked2e
,
103 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
104 WRITE_STRING_FILE_CREATE
) == 0);
105 assert_se(symlink("/dev/null", mask
) == 0);
106 assert_se(touch(mask2
) == 0);
107 assert_se(touch(mask2e
) == 0);
108 assert_se(touch(name3
) >= 0);
110 assert_se(chmod(name
, 0755) == 0);
111 assert_se(chmod(name2
, 0755) == 0);
112 assert_se(chmod(overridden
, 0755) == 0);
113 assert_se(chmod(override
, 0755) == 0);
114 assert_se(chmod(masked
, 0755) == 0);
115 assert_se(chmod(masked2
, 0755) == 0);
116 assert_se(chmod(masked2e
, 0755) == 0);
117 assert_se(chmod(mask2e
, 0755) == 0);
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(template_lo
) == 0);
125 assert_se(access("it_works", F_OK
) >= 0);
126 assert_se(access("failed", F_OK
) < 0);
128 assert_se(chdir(template_hi
) == 0);
129 assert_se(access("it_works2", F_OK
) >= 0);
130 assert_se(access("failed", F_OK
) < 0);
132 (void) rm_rf(template_lo
, REMOVE_ROOT
|REMOVE_PHYSICAL
);
133 (void) rm_rf(template_hi
, REMOVE_ROOT
|REMOVE_PHYSICAL
);
136 static void test_execution_order(void) {
137 char template_lo
[] = "/tmp/test-exec-util-lo.XXXXXXX";
138 char template_hi
[] = "/tmp/test-exec-util-hi.XXXXXXX";
139 const char *dirs
[] = {template_hi
, template_lo
, 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(template_lo
));
145 assert_se(mkdtemp(template_hi
));
147 output
= strjoina(template_hi
, "/output");
149 log_info("/* %s >>%s */", __func__
, output
);
151 /* write files in "random" order */
152 name2
= strjoina(template_lo
, "/90-bar");
153 name
= strjoina(template_hi
, "/80-foo");
154 name3
= strjoina(template_lo
, "/last");
155 overridden
= strjoina(template_lo
, "/30-override");
156 override
= strjoina(template_hi
, "/30-override");
157 masked
= strjoina(template_lo
, "/10-masked");
158 mask
= strjoina(template_hi
, "/10-masked");
160 t
= strjoina("#!/bin/sh\necho $(basename $0) >>", output
);
161 assert_se(write_string_file(name
, t
, WRITE_STRING_FILE_CREATE
) == 0);
163 t
= strjoina("#!/bin/sh\necho $(basename $0) >>", output
);
164 assert_se(write_string_file(name2
, t
, WRITE_STRING_FILE_CREATE
) == 0);
166 t
= strjoina("#!/bin/sh\necho $(basename $0) >>", output
);
167 assert_se(write_string_file(name3
, t
, WRITE_STRING_FILE_CREATE
) == 0);
169 t
= strjoina("#!/bin/sh\necho OVERRIDDEN >>", output
);
170 assert_se(write_string_file(overridden
, t
, WRITE_STRING_FILE_CREATE
) == 0);
172 t
= strjoina("#!/bin/sh\necho $(basename $0) >>", output
);
173 assert_se(write_string_file(override
, t
, WRITE_STRING_FILE_CREATE
) == 0);
175 t
= strjoina("#!/bin/sh\necho MASKED >>", output
);
176 assert_se(write_string_file(masked
, t
, WRITE_STRING_FILE_CREATE
) == 0);
178 assert_se(symlink("/dev/null", mask
) == 0);
180 assert_se(chmod(name
, 0755) == 0);
181 assert_se(chmod(name2
, 0755) == 0);
182 assert_se(chmod(name3
, 0755) == 0);
183 assert_se(chmod(overridden
, 0755) == 0);
184 assert_se(chmod(override
, 0755) == 0);
185 assert_se(chmod(masked
, 0755) == 0);
187 execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
, ignore_stdout
, ignore_stdout_args
, NULL
, NULL
, EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
189 assert_se(read_full_file(output
, &contents
, NULL
) >= 0);
190 assert_se(streq(contents
, "30-override\n80-foo\n90-bar\nlast\n"));
192 (void) rm_rf(template_lo
, REMOVE_ROOT
|REMOVE_PHYSICAL
);
193 (void) rm_rf(template_hi
, REMOVE_ROOT
|REMOVE_PHYSICAL
);
196 static int gather_stdout_one(int fd
, void *arg
) {
201 assert_se(read(fd
, buf
, sizeof buf
) >= 0);
204 assert_se(t
= strndup(buf
, sizeof buf
));
205 assert_se(strv_push(s
, t
) >= 0);
209 static int gather_stdout_two(int fd
, void *arg
) {
210 char ***s
= arg
, **t
;
213 assert_se(write(fd
, *t
, strlen(*t
)) == (ssize_t
) strlen(*t
));
218 static int gather_stdout_three(int fd
, void *arg
) {
222 assert_se(read(fd
, buf
, sizeof buf
- 1) > 0);
224 assert_se(*s
= strndup(buf
, sizeof buf
));
229 const gather_stdout_callback_t gather_stdout
[] = {
235 static void test_stdout_gathering(void) {
236 char template[] = "/tmp/test-exec-util.XXXXXXX";
237 const char *dirs
[] = {template, NULL
};
238 const char *name
, *name2
, *name3
;
241 char **tmp
= NULL
; /* this is only used in the forked process, no cleanup here */
242 _cleanup_free_
char *output
= NULL
;
244 void* args
[] = {&tmp
, &tmp
, &output
};
246 assert_se(mkdtemp(template));
248 log_info("/* %s */", __func__
);
251 name
= strjoina(template, "/10-foo");
252 name2
= strjoina(template, "/20-bar");
253 name3
= strjoina(template, "/30-last");
255 assert_se(write_string_file(name
,
256 "#!/bin/sh\necho a\necho b\necho c\n",
257 WRITE_STRING_FILE_CREATE
) == 0);
258 assert_se(write_string_file(name2
,
259 "#!/bin/sh\necho d\n",
260 WRITE_STRING_FILE_CREATE
) == 0);
261 assert_se(write_string_file(name3
,
262 "#!/bin/sh\nsleep 1",
263 WRITE_STRING_FILE_CREATE
) == 0);
265 assert_se(chmod(name
, 0755) == 0);
266 assert_se(chmod(name2
, 0755) == 0);
267 assert_se(chmod(name3
, 0755) == 0);
269 r
= execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
, gather_stdout
, args
, NULL
, NULL
, EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
272 log_info("got: %s", output
);
274 assert_se(streq(output
, "a\nb\nc\nd\n"));
277 static void test_environment_gathering(void) {
278 char template[] = "/tmp/test-exec-util.XXXXXXX", **p
;
279 const char *dirs
[] = {template, NULL
};
280 const char *name
, *name2
, *name3
, *old
;
283 char **tmp
= NULL
; /* this is only used in the forked process, no cleanup here */
284 _cleanup_strv_free_
char **env
= NULL
;
286 void* const args
[] = { &tmp
, &tmp
, &env
};
288 assert_se(mkdtemp(template));
290 log_info("/* %s */", __func__
);
293 name
= strjoina(template, "/10-foo");
294 name2
= strjoina(template, "/20-bar");
295 name3
= strjoina(template, "/30-last");
297 assert_se(write_string_file(name
,
300 WRITE_STRING_FILE_CREATE
) == 0);
301 assert_se(write_string_file(name2
,
303 "echo A=22:$A\n\n\n", /* substitution from previous generator */
304 WRITE_STRING_FILE_CREATE
) == 0);
305 assert_se(write_string_file(name3
,
310 "echo C=001\n" /* variable overwriting */
311 /* various invalid entries */
318 /* test variable assignment without newline */
319 "echo PATH=$PATH:/no/such/file", /* no newline */
320 WRITE_STRING_FILE_CREATE
) == 0);
322 assert_se(chmod(name
, 0755) == 0);
323 assert_se(chmod(name2
, 0755) == 0);
324 assert_se(chmod(name3
, 0755) == 0);
326 /* When booting in containers or without initramfs there might not be
327 * any PATH in the environment and if there is no PATH /bin/sh built-in
328 * PATH may leak and override systemd's DEFAULT_PATH which is not
329 * good. Force our own PATH in environment, to prevent expansion of sh
331 old
= getenv("PATH");
332 r
= setenv("PATH", "no-sh-built-in-path", 1);
335 r
= execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
, gather_environment
, args
, NULL
, NULL
, EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
339 log_info("got env: \"%s\"", *p
);
341 assert_se(streq(strv_env_get(env
, "A"), "22:23:24"));
342 assert_se(streq(strv_env_get(env
, "B"), "12"));
343 assert_se(streq(strv_env_get(env
, "C"), "001"));
344 assert_se(streq(strv_env_get(env
, "PATH"), "no-sh-built-in-path:/no/such/file"));
346 /* now retest with "default" path passed in, as created by
347 * manager_default_environment */
348 env
= strv_free(env
);
349 env
= strv_new("PATH=" DEFAULT_PATH
);
352 r
= execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
, gather_environment
, args
, NULL
, env
, EXEC_DIR_PARALLEL
| EXEC_DIR_IGNORE_ERRORS
);
356 log_info("got env: \"%s\"", *p
);
358 assert_se(streq(strv_env_get(env
, "A"), "22:23:24"));
359 assert_se(streq(strv_env_get(env
, "B"), "12"));
360 assert_se(streq(strv_env_get(env
, "C"), "001"));
361 assert_se(streq(strv_env_get(env
, "PATH"), DEFAULT_PATH
":/no/such/file"));
363 /* reset environ PATH */
365 (void) setenv("PATH", old
, 1);
367 (void) unsetenv("PATH");
370 static void test_error_catching(void) {
371 char template[] = "/tmp/test-exec-util.XXXXXXX";
372 const char *dirs
[] = {template, NULL
};
373 const char *name
, *name2
, *name3
;
376 assert_se(mkdtemp(template));
378 log_info("/* %s */", __func__
);
381 name
= strjoina(template, "/10-foo");
382 name2
= strjoina(template, "/20-bar");
383 name3
= strjoina(template, "/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 r
= execute_directories(dirs
, DEFAULT_TIMEOUT_USEC
, NULL
, NULL
, NULL
, NULL
, EXEC_DIR_NONE
);
401 /* we should exit with the error code of the first script that failed */
405 static void test_exec_command_flags_from_strv(void) {
406 ExecCommandFlags flags
= 0;
407 char **valid_strv
= STRV_MAKE("no-env-expand", "no-setuid", "ignore-failure");
408 char **invalid_strv
= STRV_MAKE("no-env-expand", "no-setuid", "nonexistent-option", "ignore-failure");
411 r
= exec_command_flags_from_strv(valid_strv
, &flags
);
414 assert_se(FLAGS_SET(flags
, EXEC_COMMAND_NO_ENV_EXPAND
));
415 assert_se(FLAGS_SET(flags
, EXEC_COMMAND_NO_SETUID
));
416 assert_se(FLAGS_SET(flags
, EXEC_COMMAND_IGNORE_FAILURE
));
417 assert_se(!FLAGS_SET(flags
, EXEC_COMMAND_AMBIENT_MAGIC
));
418 assert_se(!FLAGS_SET(flags
, EXEC_COMMAND_FULLY_PRIVILEGED
));
420 r
= exec_command_flags_from_strv(invalid_strv
, &flags
);
422 assert_se(r
== -EINVAL
);
425 static void test_exec_command_flags_to_strv(void) {
426 _cleanup_strv_free_
char **opts
= NULL
, **empty_opts
= NULL
, **invalid_opts
= NULL
;
427 ExecCommandFlags flags
= 0;
430 flags
|= (EXEC_COMMAND_AMBIENT_MAGIC
|EXEC_COMMAND_NO_ENV_EXPAND
|EXEC_COMMAND_IGNORE_FAILURE
);
432 r
= exec_command_flags_to_strv(flags
, &opts
);
435 assert_se(strv_equal(opts
, STRV_MAKE("ignore-failure", "ambient", "no-env-expand")));
437 r
= exec_command_flags_to_strv(0, &empty_opts
);
440 assert_se(strv_equal(empty_opts
, STRV_MAKE_EMPTY
));
442 flags
= _EXEC_COMMAND_FLAGS_INVALID
;
444 r
= exec_command_flags_to_strv(flags
, &invalid_opts
);
446 assert_se(r
== -EINVAL
);
449 int main(int argc
, char *argv
[]) {
450 test_setup_logging(LOG_DEBUG
);
452 test_execute_directory(true);
453 test_execute_directory(false);
454 test_execution_order();
455 test_stdout_gathering();
456 test_environment_gathering();
457 test_error_catching();
458 test_exec_command_flags_from_strv();
459 test_exec_command_flags_to_strv();