]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/test/test-exec-util.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / test / test-exec-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6 Copyright 2013 Thomas H.P. Andersen
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <sys/wait.h>
26 #include <unistd.h>
27
28 #include "alloc-util.h"
29 #include "copy.h"
30 #include "def.h"
31 #include "env-util.h"
32 #include "exec-util.h"
33 #include "fd-util.h"
34 #include "fileio.h"
35 #include "fs-util.h"
36 #include "log.h"
37 #include "macro.h"
38 #include "rm-rf.h"
39 #include "string-util.h"
40 #include "strv.h"
41
42 static int here = 0, here2 = 0, here3 = 0;
43 void *ignore_stdout_args[] = {&here, &here2, &here3};
44
45 /* noop handlers, just check that arguments are passed correctly */
46 static int ignore_stdout_func(int fd, void *arg) {
47 assert(fd >= 0);
48 assert(arg == &here);
49 safe_close(fd);
50
51 return 0;
52 }
53 static int ignore_stdout_func2(int fd, void *arg) {
54 assert(fd >= 0);
55 assert(arg == &here2);
56 safe_close(fd);
57
58 return 0;
59 }
60 static int ignore_stdout_func3(int fd, void *arg) {
61 assert(fd >= 0);
62 assert(arg == &here3);
63 safe_close(fd);
64
65 return 0;
66 }
67
68 static const gather_stdout_callback_t ignore_stdout[] = {
69 ignore_stdout_func,
70 ignore_stdout_func2,
71 ignore_stdout_func3,
72 };
73
74 static void test_execute_directory(bool gather_stdout) {
75 char template_lo[] = "/tmp/test-exec-util.lo.XXXXXXX";
76 char template_hi[] = "/tmp/test-exec-util.hi.XXXXXXX";
77 const char * dirs[] = {template_hi, template_lo, NULL};
78 const char *name, *name2, *name3,
79 *overridden, *override,
80 *masked, *mask,
81 *masked2, *mask2, /* the mask is non-executable */
82 *masked2e, *mask2e; /* the mask is executable */
83
84 log_info("/* %s (%s) */", __func__, gather_stdout ? "gathering stdout" : "asynchronous");
85
86 assert_se(mkdtemp(template_lo));
87 assert_se(mkdtemp(template_hi));
88
89 name = strjoina(template_lo, "/script");
90 name2 = strjoina(template_hi, "/script2");
91 name3 = strjoina(template_lo, "/useless");
92 overridden = strjoina(template_lo, "/overridden");
93 override = strjoina(template_hi, "/overridden");
94 masked = strjoina(template_lo, "/masked");
95 mask = strjoina(template_hi, "/masked");
96 masked2 = strjoina(template_lo, "/masked2");
97 mask2 = strjoina(template_hi, "/masked2");
98 masked2e = strjoina(template_lo, "/masked2e");
99 mask2e = strjoina(template_hi, "/masked2e");
100
101 assert_se(write_string_file(name,
102 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works",
103 WRITE_STRING_FILE_CREATE) == 0);
104 assert_se(write_string_file(name2,
105 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works2",
106 WRITE_STRING_FILE_CREATE) == 0);
107 assert_se(write_string_file(overridden,
108 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
109 WRITE_STRING_FILE_CREATE) == 0);
110 assert_se(write_string_file(override,
111 "#!/bin/sh\necho 'Executing '$0",
112 WRITE_STRING_FILE_CREATE) == 0);
113 assert_se(write_string_file(masked,
114 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
115 WRITE_STRING_FILE_CREATE) == 0);
116 assert_se(write_string_file(masked2,
117 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
118 WRITE_STRING_FILE_CREATE) == 0);
119 assert_se(write_string_file(masked2e,
120 "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed",
121 WRITE_STRING_FILE_CREATE) == 0);
122 assert_se(symlink("/dev/null", mask) == 0);
123 assert_se(touch(mask2) == 0);
124 assert_se(touch(mask2e) == 0);
125 assert_se(touch(name3) >= 0);
126
127 assert_se(chmod(name, 0755) == 0);
128 assert_se(chmod(name2, 0755) == 0);
129 assert_se(chmod(overridden, 0755) == 0);
130 assert_se(chmod(override, 0755) == 0);
131 assert_se(chmod(masked, 0755) == 0);
132 assert_se(chmod(masked2, 0755) == 0);
133 assert_se(chmod(masked2e, 0755) == 0);
134 assert_se(chmod(mask2e, 0755) == 0);
135
136 if (gather_stdout)
137 execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL);
138 else
139 execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, NULL);
140
141 assert_se(chdir(template_lo) == 0);
142 assert_se(access("it_works", F_OK) >= 0);
143 assert_se(access("failed", F_OK) < 0);
144
145 assert_se(chdir(template_hi) == 0);
146 assert_se(access("it_works2", F_OK) >= 0);
147 assert_se(access("failed", F_OK) < 0);
148
149 (void) rm_rf(template_lo, REMOVE_ROOT|REMOVE_PHYSICAL);
150 (void) rm_rf(template_hi, REMOVE_ROOT|REMOVE_PHYSICAL);
151 }
152
153 static void test_execution_order(void) {
154 char template_lo[] = "/tmp/test-exec-util-lo.XXXXXXX";
155 char template_hi[] = "/tmp/test-exec-util-hi.XXXXXXX";
156 const char *dirs[] = {template_hi, template_lo, NULL};
157 const char *name, *name2, *name3, *overridden, *override, *masked, *mask;
158 const char *output, *t;
159 _cleanup_free_ char *contents = NULL;
160
161 assert_se(mkdtemp(template_lo));
162 assert_se(mkdtemp(template_hi));
163
164 output = strjoina(template_hi, "/output");
165
166 log_info("/* %s >>%s */", __func__, output);
167
168 /* write files in "random" order */
169 name2 = strjoina(template_lo, "/90-bar");
170 name = strjoina(template_hi, "/80-foo");
171 name3 = strjoina(template_lo, "/last");
172 overridden = strjoina(template_lo, "/30-override");
173 override = strjoina(template_hi, "/30-override");
174 masked = strjoina(template_lo, "/10-masked");
175 mask = strjoina(template_hi, "/10-masked");
176
177 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
178 assert_se(write_string_file(name, t, WRITE_STRING_FILE_CREATE) == 0);
179
180 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
181 assert_se(write_string_file(name2, t, WRITE_STRING_FILE_CREATE) == 0);
182
183 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
184 assert_se(write_string_file(name3, t, WRITE_STRING_FILE_CREATE) == 0);
185
186 t = strjoina("#!/bin/sh\necho OVERRIDDEN >>", output);
187 assert_se(write_string_file(overridden, t, WRITE_STRING_FILE_CREATE) == 0);
188
189 t = strjoina("#!/bin/sh\necho $(basename $0) >>", output);
190 assert_se(write_string_file(override, t, WRITE_STRING_FILE_CREATE) == 0);
191
192 t = strjoina("#!/bin/sh\necho MASKED >>", output);
193 assert_se(write_string_file(masked, t, WRITE_STRING_FILE_CREATE) == 0);
194
195 assert_se(symlink("/dev/null", mask) == 0);
196
197 assert_se(chmod(name, 0755) == 0);
198 assert_se(chmod(name2, 0755) == 0);
199 assert_se(chmod(name3, 0755) == 0);
200 assert_se(chmod(overridden, 0755) == 0);
201 assert_se(chmod(override, 0755) == 0);
202 assert_se(chmod(masked, 0755) == 0);
203
204 execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL);
205
206 assert_se(read_full_file(output, &contents, NULL) >= 0);
207 assert_se(streq(contents, "30-override\n80-foo\n90-bar\nlast\n"));
208
209 (void) rm_rf(template_lo, REMOVE_ROOT|REMOVE_PHYSICAL);
210 (void) rm_rf(template_hi, REMOVE_ROOT|REMOVE_PHYSICAL);
211 }
212
213 static int gather_stdout_one(int fd, void *arg) {
214 char ***s = arg, *t;
215 char buf[128] = {};
216
217 assert_se(s);
218 assert_se(read(fd, buf, sizeof buf) >= 0);
219 safe_close(fd);
220
221 assert_se(t = strndup(buf, sizeof buf));
222 assert_se(strv_push(s, t) >= 0);
223
224 return 0;
225 }
226 static int gather_stdout_two(int fd, void *arg) {
227 char ***s = arg, **t;
228
229 STRV_FOREACH(t, *s)
230 assert_se(write(fd, *t, strlen(*t)) == (ssize_t) strlen(*t));
231 safe_close(fd);
232
233 return 0;
234 }
235 static int gather_stdout_three(int fd, void *arg) {
236 char **s = arg;
237 char buf[128] = {};
238
239 assert_se(read(fd, buf, sizeof buf - 1) > 0);
240 safe_close(fd);
241 assert_se(*s = strndup(buf, sizeof buf));
242
243 return 0;
244 }
245
246 const gather_stdout_callback_t gather_stdout[] = {
247 gather_stdout_one,
248 gather_stdout_two,
249 gather_stdout_three,
250 };
251
252
253 static void test_stdout_gathering(void) {
254 char template[] = "/tmp/test-exec-util.XXXXXXX";
255 const char *dirs[] = {template, NULL};
256 const char *name, *name2, *name3;
257 int r;
258
259 char **tmp = NULL; /* this is only used in the forked process, no cleanup here */
260 _cleanup_free_ char *output = NULL;
261
262 void* args[] = {&tmp, &tmp, &output};
263
264 assert_se(mkdtemp(template));
265
266 log_info("/* %s */", __func__);
267
268 /* write files */
269 name = strjoina(template, "/10-foo");
270 name2 = strjoina(template, "/20-bar");
271 name3 = strjoina(template, "/30-last");
272
273 assert_se(write_string_file(name,
274 "#!/bin/sh\necho a\necho b\necho c\n",
275 WRITE_STRING_FILE_CREATE) == 0);
276 assert_se(write_string_file(name2,
277 "#!/bin/sh\necho d\n",
278 WRITE_STRING_FILE_CREATE) == 0);
279 assert_se(write_string_file(name3,
280 "#!/bin/sh\nsleep 1",
281 WRITE_STRING_FILE_CREATE) == 0);
282
283 assert_se(chmod(name, 0755) == 0);
284 assert_se(chmod(name2, 0755) == 0);
285 assert_se(chmod(name3, 0755) == 0);
286
287 r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_stdout, args, NULL);
288 assert_se(r >= 0);
289
290 log_info("got: %s", output);
291
292 assert_se(streq(output, "a\nb\nc\nd\n"));
293 }
294
295 static void test_environment_gathering(void) {
296 char template[] = "/tmp/test-exec-util.XXXXXXX", **p;
297 const char *dirs[] = {template, NULL};
298 const char *name, *name2, *name3;
299 int r;
300
301 char **tmp = NULL; /* this is only used in the forked process, no cleanup here */
302 _cleanup_strv_free_ char **env = NULL;
303
304 void* const args[] = { &tmp, &tmp, &env };
305
306 assert_se(mkdtemp(template));
307
308 log_info("/* %s */", __func__);
309
310 /* write files */
311 name = strjoina(template, "/10-foo");
312 name2 = strjoina(template, "/20-bar");
313 name3 = strjoina(template, "/30-last");
314
315 assert_se(write_string_file(name,
316 "#!/bin/sh\n"
317 "echo A=23\n",
318 WRITE_STRING_FILE_CREATE) == 0);
319 assert_se(write_string_file(name2,
320 "#!/bin/sh\n"
321 "echo A=22:$A\n\n\n", /* substitution from previous generator */
322 WRITE_STRING_FILE_CREATE) == 0);
323 assert_se(write_string_file(name3,
324 "#!/bin/sh\n"
325 "echo A=$A:24\n"
326 "echo B=12\n"
327 "echo C=000\n"
328 "echo C=001\n" /* variable overwriting */
329 /* various invalid entries */
330 "echo unset A\n"
331 "echo unset A=\n"
332 "echo unset A=B\n"
333 "echo unset \n"
334 "echo A B=C\n"
335 "echo A\n"
336 /* test variable assignment without newline */
337 "echo PATH=$PATH:/no/such/file", /* no newline */
338 WRITE_STRING_FILE_CREATE) == 0);
339
340 assert_se(chmod(name, 0755) == 0);
341 assert_se(chmod(name2, 0755) == 0);
342 assert_se(chmod(name3, 0755) == 0);
343
344 r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL);
345 assert_se(r >= 0);
346
347 STRV_FOREACH(p, env)
348 log_info("got env: \"%s\"", *p);
349
350 assert_se(streq(strv_env_get(env, "A"), "22:23:24"));
351 assert_se(streq(strv_env_get(env, "B"), "12"));
352 assert_se(streq(strv_env_get(env, "C"), "001"));
353 assert_se(endswith(strv_env_get(env, "PATH"), ":/no/such/file"));
354 }
355
356 int main(int argc, char *argv[]) {
357 log_set_max_level(LOG_DEBUG);
358 log_parse_environment();
359 log_open();
360
361 test_execute_directory(true);
362 test_execute_directory(false);
363 test_execution_order();
364 test_stdout_gathering();
365 test_environment_gathering();
366
367 return 0;
368 }