]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/test/test-exec-util.c
ukify: fix parsing uname version with '+'
[thirdparty/systemd.git] / src / test / test-exec-util.c
... / ...
CommitLineData
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <stdlib.h>
4#include <sys/stat.h>
5#include <unistd.h>
6
7#include "alloc-util.h"
8#include "constants.h"
9#include "env-util.h"
10#include "exec-util.h"
11#include "fd-util.h"
12#include "fileio.h"
13#include "fs-util.h"
14#include "log.h"
15#include "path-util.h"
16#include "rm-rf.h"
17#include "string-util.h"
18#include "strv.h"
19#include "tests.h"
20#include "tmpfile-util.h"
21
22static int here = 0, here2 = 0, here3 = 0;
23static void *ignore_stdout_args[] = { &here, &here2, &here3 };
24
25/* noop handlers, just check that arguments are passed correctly */
26static int ignore_stdout_func(int fd, void *arg) {
27 assert_se(fd >= 0);
28 assert_se(arg == &here);
29 safe_close(fd);
30
31 return 0;
32}
33static int ignore_stdout_func2(int fd, void *arg) {
34 assert_se(fd >= 0);
35 assert_se(arg == &here2);
36 safe_close(fd);
37
38 return 0;
39}
40static int ignore_stdout_func3(int fd, void *arg) {
41 assert_se(fd >= 0);
42 assert_se(arg == &here3);
43 safe_close(fd);
44
45 return 0;
46}
47
48static const gather_stdout_callback_t ignore_stdout[] = {
49 ignore_stdout_func,
50 ignore_stdout_func2,
51 ignore_stdout_func3,
52};
53
54static 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,
58 *masked, *mask,
59 *masked2, *mask2, /* the mask is non-executable */
60 *masked2e, *mask2e; /* the mask is executable */
61
62 log_info("/* %s (%s) */", __func__, gather_stdout ? "gathering stdout" : "asynchronous");
63
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);
66
67 const char * dirs[] = { tmp_hi, tmp_lo, NULL };
68
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");
80
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);
106
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);
115
116 if (access(name, X_OK) < 0 && ERRNO_IS_PRIVILEGE(errno))
117 return;
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(tmp_lo) == 0);
125 assert_se(access("it_works", F_OK) >= 0);
126 assert_se(access("failed", F_OK) < 0);
127
128 assert_se(chdir(tmp_hi) == 0);
129 assert_se(access("it_works2", F_OK) >= 0);
130 assert_se(access("failed", F_OK) < 0);
131}
132
133TEST(execute_directory) {
134 test_execute_directory_one(true);
135 test_execute_directory_one(false);
136}
137
138TEST(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;
143
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);
146
147 const char *dirs[] = { tmp_hi, tmp_lo, NULL };
148
149 output = strjoina(tmp_hi, "/output");
150
151 log_info("/* %s >>%s */", __func__, output);
152
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");
161
162 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
163 assert_se(write_string_file(name, t, WRITE_STRING_FILE_CREATE) == 0);
164
165 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
166 assert_se(write_string_file(name2, t, WRITE_STRING_FILE_CREATE) == 0);
167
168 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
169 assert_se(write_string_file(name3, t, WRITE_STRING_FILE_CREATE) == 0);
170
171 t = strjoina("#!/bin/sh\necho OVERRIDDEN >>", output);
172 assert_se(write_string_file(overridden, t, WRITE_STRING_FILE_CREATE) == 0);
173
174 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
175 assert_se(write_string_file(override, t, WRITE_STRING_FILE_CREATE) == 0);
176
177 t = strjoina("#!/bin/sh\necho MASKED >>", output);
178 assert_se(write_string_file(masked, t, WRITE_STRING_FILE_CREATE) == 0);
179
180 assert_se(symlink("/dev/null", mask) == 0);
181
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);
188
189 if (access(name, X_OK) < 0 && ERRNO_IS_PRIVILEGE(errno))
190 return;
191
192 execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
193
194 assert_se(read_full_file(output, &contents, NULL) >= 0);
195 ASSERT_STREQ(contents, "30-override\n80-foo\n90-bar\nlast\n");
196}
197
198static int gather_stdout_one(int fd, void *arg) {
199 char ***s = arg, *t;
200 char buf[128] = {};
201
202 assert_se(s);
203 assert_se(read(fd, buf, sizeof buf) >= 0);
204 safe_close(fd);
205
206 assert_se(t = strndup(buf, sizeof buf));
207 assert_se(strv_push(s, t) >= 0);
208
209 return 0;
210}
211static int gather_stdout_two(int fd, void *arg) {
212 char ***s = arg;
213
214 STRV_FOREACH(t, *s)
215 assert_se(write(fd, *t, strlen(*t)) == (ssize_t) strlen(*t));
216 safe_close(fd);
217
218 return 0;
219}
220static int gather_stdout_three(int fd, void *arg) {
221 char **s = arg;
222 char buf[128] = {};
223
224 assert_se(read(fd, buf, sizeof buf - 1) > 0);
225 safe_close(fd);
226 assert_se(*s = strndup(buf, sizeof buf));
227
228 return 0;
229}
230
231const gather_stdout_callback_t gather_stdouts[] = {
232 gather_stdout_one,
233 gather_stdout_two,
234 gather_stdout_three,
235};
236
237TEST(stdout_gathering) {
238 _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
239 const char *name, *name2, *name3;
240 int r;
241
242 char **tmp = NULL; /* this is only used in the forked process, no cleanup here */
243 _cleanup_free_ char *output = NULL;
244
245 void* args[] = {&tmp, &tmp, &output};
246
247 assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir) >= 0);
248
249 const char *dirs[] = { tmpdir, NULL };
250
251 /* write files */
252 name = strjoina(tmpdir, "/10-foo");
253 name2 = strjoina(tmpdir, "/20-bar");
254 name3 = strjoina(tmpdir, "/30-last");
255
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);
265
266 assert_se(chmod(name, 0755) == 0);
267 assert_se(chmod(name2, 0755) == 0);
268 assert_se(chmod(name3, 0755) == 0);
269
270 if (access(name, X_OK) < 0 && ERRNO_IS_PRIVILEGE(errno))
271 return;
272
273 r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_stdouts, args, NULL, NULL,
274 EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
275 assert_se(r >= 0);
276
277 log_info("got: %s", output);
278
279 ASSERT_STREQ(output, "a\nb\nc\nd\n");
280}
281
282TEST(environment_gathering) {
283 _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
284 const char *name, *name2, *name3, *old;
285 int r;
286
287 char **tmp = NULL; /* this is only used in the forked process, no cleanup here */
288 _cleanup_strv_free_ char **env = NULL;
289
290 void* const args[] = { &tmp, &tmp, &env };
291
292 assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir) >= 0);
293
294 const char *dirs[] = { tmpdir, NULL };
295
296 /* write files */
297 name = strjoina(tmpdir, "/10-foo");
298 name2 = strjoina(tmpdir, "/20-bar");
299 name3 = strjoina(tmpdir, "/30-last");
300
301 assert_se(write_string_file(name,
302 "#!/bin/sh\n"
303 "echo A=23\n",
304 WRITE_STRING_FILE_CREATE) == 0);
305 assert_se(write_string_file(name2,
306 "#!/bin/sh\n"
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,
310 "#!/bin/sh\n"
311 "echo A=$A:24\n"
312 "echo B=12\n"
313 "echo C=000\n"
314 "echo C=001\n" /* variable overwriting */
315 /* various invalid entries */
316 "echo unset A\n"
317 "echo unset A=\n"
318 "echo unset A=B\n"
319 "echo unset \n"
320 "echo A B=C\n"
321 "echo A\n"
322 /* test variable assignment without newline */
323 "echo PATH=$PATH:/no/such/file", /* no newline */
324 WRITE_STRING_FILE_CREATE) == 0);
325
326 assert_se(chmod(name, 0755) == 0);
327 assert_se(chmod(name2, 0755) == 0);
328 assert_se(chmod(name3, 0755) == 0);
329
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);
335 assert_se(r >= 0);
336
337 if (access(name, X_OK) < 0 && ERRNO_IS_PRIVILEGE(errno))
338 return;
339
340 r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
341 assert_se(r >= 0);
342
343 STRV_FOREACH(p, env)
344 log_info("got env: \"%s\"", *p);
345
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");
350
351 /* Now retest with some "default" path passed. */
352 env = strv_free(env);
353 env = strv_new("PATH=" DEFAULT_PATH_WITHOUT_SBIN);
354 assert_se(env);
355
356 r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL, env, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
357 assert_se(r >= 0);
358
359 STRV_FOREACH(p, env)
360 log_info("got env: \"%s\"", *p);
361
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");
366
367 /* reset environ PATH */
368 assert_se(set_unset_env("PATH", old, true) == 0);
369}
370
371TEST(error_catching) {
372 _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
373 const char *name, *name2, *name3;
374 int r;
375
376 assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir) >= 0);
377
378 const char *dirs[] = { tmpdir, NULL };
379
380 /* write files */
381 name = strjoina(tmpdir, "/10-foo");
382 name2 = strjoina(tmpdir, "/20-bar");
383 name3 = strjoina(tmpdir, "/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 if (access(name, X_OK) < 0 && ERRNO_IS_PRIVILEGE(errno))
400 return;
401
402 r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC,
403 /* callbacks = */ NULL, /* callback_args = */ NULL,
404 /* argv = */ NULL, /* envp = */ NULL, /* flags = */ 0);
405
406 /* we should exit with the error code of the first script that failed */
407 assert_se(r == 42);
408}
409
410TEST(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");
414 int r;
415
416 r = exec_command_flags_from_strv(valid_strv, &flags);
417
418 assert_se(r == 0);
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));
423
424 r = exec_command_flags_from_strv(invalid_strv, &flags);
425
426 assert_se(r == -EINVAL);
427}
428
429TEST(exec_command_flags_to_strv) {
430 _cleanup_strv_free_ char **opts = NULL;
431
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")));
434
435 opts = strv_free(opts);
436
437 ASSERT_OK(exec_command_flags_to_strv(0, &opts));
438 assert_se(strv_isempty(opts));
439
440 opts = strv_free(opts);
441
442 ASSERT_ERROR(exec_command_flags_to_strv(1U << 20, &opts), EINVAL);
443}
444
445DEFINE_TEST_MAIN(LOG_DEBUG);