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