]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/test/test-exec-util.c
core: add ExecStartXYZEx= with dbus support for executable prefixes
[thirdparty/systemd.git] / src / test / test-exec-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <errno.h>
4 #include <string.h>
5 #include <sys/stat.h>
6 #include <sys/wait.h>
7 #include <unistd.h>
8
9 #include "alloc-util.h"
10 #include "copy.h"
11 #include "def.h"
12 #include "env-util.h"
13 #include "exec-util.h"
14 #include "fd-util.h"
15 #include "fileio.h"
16 #include "fs-util.h"
17 #include "log.h"
18 #include "macro.h"
19 #include "path-util.h"
20 #include "rm-rf.h"
21 #include "string-util.h"
22 #include "strv.h"
23 #include "tests.h"
24
25 static int here = 0, here2 = 0, here3 = 0;
26 void *ignore_stdout_args[] = {&here, &here2, &here3};
27
28 /* noop handlers, just check that arguments are passed correctly */
29 static int ignore_stdout_func(int fd, void *arg) {
30 assert(fd >= 0);
31 assert(arg == &here);
32 safe_close(fd);
33
34 return 0;
35 }
36 static int ignore_stdout_func2(int fd, void *arg) {
37 assert(fd >= 0);
38 assert(arg == &here2);
39 safe_close(fd);
40
41 return 0;
42 }
43 static int ignore_stdout_func3(int fd, void *arg) {
44 assert(fd >= 0);
45 assert(arg == &here3);
46 safe_close(fd);
47
48 return 0;
49 }
50
51 static const gather_stdout_callback_t ignore_stdout[] = {
52 ignore_stdout_func,
53 ignore_stdout_func2,
54 ignore_stdout_func3,
55 };
56
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,
63 *masked, *mask,
64 *masked2, *mask2, /* the mask is non-executable */
65 *masked2e, *mask2e; /* the mask is executable */
66
67 log_info("/* %s (%s) */", __func__, gather_stdout ? "gathering stdout" : "asynchronous");
68
69 assert_se(mkdtemp(template_lo));
70 assert_se(mkdtemp(template_hi));
71
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");
83
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);
109
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);
118
119 if (gather_stdout)
120 execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
121 else
122 execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
123
124 assert_se(chdir(template_lo) == 0);
125 assert_se(access("it_works", F_OK) >= 0);
126 assert_se(access("failed", F_OK) < 0);
127
128 assert_se(chdir(template_hi) == 0);
129 assert_se(access("it_works2", F_OK) >= 0);
130 assert_se(access("failed", F_OK) < 0);
131
132 (void) rm_rf(template_lo, REMOVE_ROOT|REMOVE_PHYSICAL);
133 (void) rm_rf(template_hi, REMOVE_ROOT|REMOVE_PHYSICAL);
134 }
135
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;
143
144 assert_se(mkdtemp(template_lo));
145 assert_se(mkdtemp(template_hi));
146
147 output = strjoina(template_hi, "/output");
148
149 log_info("/* %s >>%s */", __func__, output);
150
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");
159
160 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
161 assert_se(write_string_file(name, t, WRITE_STRING_FILE_CREATE) == 0);
162
163 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
164 assert_se(write_string_file(name2, t, WRITE_STRING_FILE_CREATE) == 0);
165
166 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
167 assert_se(write_string_file(name3, t, WRITE_STRING_FILE_CREATE) == 0);
168
169 t = strjoina("#!/bin/sh\necho OVERRIDDEN >>", output);
170 assert_se(write_string_file(overridden, t, WRITE_STRING_FILE_CREATE) == 0);
171
172 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
173 assert_se(write_string_file(override, t, WRITE_STRING_FILE_CREATE) == 0);
174
175 t = strjoina("#!/bin/sh\necho MASKED >>", output);
176 assert_se(write_string_file(masked, t, WRITE_STRING_FILE_CREATE) == 0);
177
178 assert_se(symlink("/dev/null", mask) == 0);
179
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);
186
187 execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
188
189 assert_se(read_full_file(output, &contents, NULL) >= 0);
190 assert_se(streq(contents, "30-override\n80-foo\n90-bar\nlast\n"));
191
192 (void) rm_rf(template_lo, REMOVE_ROOT|REMOVE_PHYSICAL);
193 (void) rm_rf(template_hi, REMOVE_ROOT|REMOVE_PHYSICAL);
194 }
195
196 static int gather_stdout_one(int fd, void *arg) {
197 char ***s = arg, *t;
198 char buf[128] = {};
199
200 assert_se(s);
201 assert_se(read(fd, buf, sizeof buf) >= 0);
202 safe_close(fd);
203
204 assert_se(t = strndup(buf, sizeof buf));
205 assert_se(strv_push(s, t) >= 0);
206
207 return 0;
208 }
209 static int gather_stdout_two(int fd, void *arg) {
210 char ***s = arg, **t;
211
212 STRV_FOREACH(t, *s)
213 assert_se(write(fd, *t, strlen(*t)) == (ssize_t) strlen(*t));
214 safe_close(fd);
215
216 return 0;
217 }
218 static int gather_stdout_three(int fd, void *arg) {
219 char **s = arg;
220 char buf[128] = {};
221
222 assert_se(read(fd, buf, sizeof buf - 1) > 0);
223 safe_close(fd);
224 assert_se(*s = strndup(buf, sizeof buf));
225
226 return 0;
227 }
228
229 const gather_stdout_callback_t gather_stdout[] = {
230 gather_stdout_one,
231 gather_stdout_two,
232 gather_stdout_three,
233 };
234
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;
239 int r;
240
241 char **tmp = NULL; /* this is only used in the forked process, no cleanup here */
242 _cleanup_free_ char *output = NULL;
243
244 void* args[] = {&tmp, &tmp, &output};
245
246 assert_se(mkdtemp(template));
247
248 log_info("/* %s */", __func__);
249
250 /* write files */
251 name = strjoina(template, "/10-foo");
252 name2 = strjoina(template, "/20-bar");
253 name3 = strjoina(template, "/30-last");
254
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);
264
265 assert_se(chmod(name, 0755) == 0);
266 assert_se(chmod(name2, 0755) == 0);
267 assert_se(chmod(name3, 0755) == 0);
268
269 r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_stdout, args, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
270 assert_se(r >= 0);
271
272 log_info("got: %s", output);
273
274 assert_se(streq(output, "a\nb\nc\nd\n"));
275 }
276
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;
281 int r;
282
283 char **tmp = NULL; /* this is only used in the forked process, no cleanup here */
284 _cleanup_strv_free_ char **env = NULL;
285
286 void* const args[] = { &tmp, &tmp, &env };
287
288 assert_se(mkdtemp(template));
289
290 log_info("/* %s */", __func__);
291
292 /* write files */
293 name = strjoina(template, "/10-foo");
294 name2 = strjoina(template, "/20-bar");
295 name3 = strjoina(template, "/30-last");
296
297 assert_se(write_string_file(name,
298 "#!/bin/sh\n"
299 "echo A=23\n",
300 WRITE_STRING_FILE_CREATE) == 0);
301 assert_se(write_string_file(name2,
302 "#!/bin/sh\n"
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,
306 "#!/bin/sh\n"
307 "echo A=$A:24\n"
308 "echo B=12\n"
309 "echo C=000\n"
310 "echo C=001\n" /* variable overwriting */
311 /* various invalid entries */
312 "echo unset A\n"
313 "echo unset A=\n"
314 "echo unset A=B\n"
315 "echo unset \n"
316 "echo A B=C\n"
317 "echo A\n"
318 /* test variable assignment without newline */
319 "echo PATH=$PATH:/no/such/file", /* no newline */
320 WRITE_STRING_FILE_CREATE) == 0);
321
322 assert_se(chmod(name, 0755) == 0);
323 assert_se(chmod(name2, 0755) == 0);
324 assert_se(chmod(name3, 0755) == 0);
325
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
330 * built-in $PATH */
331 old = getenv("PATH");
332 r = setenv("PATH", "no-sh-built-in-path", 1);
333 assert_se(r >= 0);
334
335 r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
336 assert_se(r >= 0);
337
338 STRV_FOREACH(p, env)
339 log_info("got env: \"%s\"", *p);
340
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"));
345
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);
350 assert_se(env);
351
352 r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL, env, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
353 assert_se(r >= 0);
354
355 STRV_FOREACH(p, env)
356 log_info("got env: \"%s\"", *p);
357
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"));
362
363 /* reset environ PATH */
364 if (old)
365 (void) setenv("PATH", old, 1);
366 else
367 (void) unsetenv("PATH");
368 }
369
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;
374 int r;
375
376 assert_se(mkdtemp(template));
377
378 log_info("/* %s */", __func__);
379
380 /* write files */
381 name = strjoina(template, "/10-foo");
382 name2 = strjoina(template, "/20-bar");
383 name3 = strjoina(template, "/30-last");
384
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);
394
395 assert_se(chmod(name, 0755) == 0);
396 assert_se(chmod(name2, 0755) == 0);
397 assert_se(chmod(name3, 0755) == 0);
398
399 r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, NULL, NULL, EXEC_DIR_NONE);
400
401 /* we should exit with the error code of the first script that failed */
402 assert_se(r == 42);
403 }
404
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");
409 int r;
410
411 r = exec_command_flags_from_strv(valid_strv, &flags);
412
413 assert_se(r == 0);
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));
419
420 r = exec_command_flags_from_strv(invalid_strv, &flags);
421
422 assert_se(r == -EINVAL);
423 }
424
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;
428 int r;
429
430 flags |= (EXEC_COMMAND_AMBIENT_MAGIC|EXEC_COMMAND_NO_ENV_EXPAND|EXEC_COMMAND_IGNORE_FAILURE);
431
432 r = exec_command_flags_to_strv(flags, &opts);
433
434 assert_se(r == 0);
435 assert_se(strv_equal(opts, STRV_MAKE("ignore-failure", "ambient", "no-env-expand")));
436
437 r = exec_command_flags_to_strv(0, &empty_opts);
438
439 assert_se(r == 0);
440 assert_se(strv_equal(empty_opts, STRV_MAKE_EMPTY));
441
442 flags = _EXEC_COMMAND_FLAGS_INVALID;
443
444 r = exec_command_flags_to_strv(flags, &invalid_opts);
445
446 assert_se(r == -EINVAL);
447 }
448
449 int main(int argc, char *argv[]) {
450 test_setup_logging(LOG_DEBUG);
451
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();
460
461 return 0;
462 }