chiark / gitweb /
load-fragment: allow quoting in command name and document allowed escapes
[elogind.git] / src / test / test-unit-file.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7   Copyright 2013 Zbigniew JÄ™drzejewski-Szmek
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <assert.h>
24 #include <stdio.h>
25 #include <stddef.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29
30 #include "install.h"
31 #include "install-printf.h"
32 #include "specifier.h"
33 #include "util.h"
34 #include "macro.h"
35 #include "hashmap.h"
36 #include "load-fragment.h"
37 #include "strv.h"
38 #include "fileio.h"
39 #include "test-helper.h"
40
41 static int test_unit_file_get_set(void) {
42         int r;
43         Hashmap *h;
44         Iterator i;
45         UnitFileList *p;
46
47         h = hashmap_new(&string_hash_ops);
48         assert_se(h);
49
50         r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h);
51
52         if (r == -EPERM || r == -EACCES) {
53                 printf("Skipping test: unit_file_get_list: %s", strerror(-r));
54                 return EXIT_TEST_SKIP;
55         }
56
57         log_full(r == 0 ? LOG_INFO : LOG_ERR,
58                  "unit_file_get_list: %s", strerror(-r));
59         if (r < 0)
60                 return EXIT_FAILURE;
61
62         HASHMAP_FOREACH(p, h, i)
63                 printf("%s = %s\n", p->path, unit_file_state_to_string(p->state));
64
65         unit_file_list_free(h);
66
67         return 0;
68 }
69
70 static void check_execcommand(ExecCommand *c,
71                               const char* path,
72                               const char* argv0,
73                               const char* argv1,
74                               const char* argv2,
75                               bool ignore) {
76         assert_se(c);
77         log_info("expect: \"%s\" [\"%s\" \"%s\" \"%s\"]",
78                  path, argv0 ?: path, argv1, argv2);
79         log_info("actual: \"%s\" [\"%s\" \"%s\" \"%s\"]",
80                  c->path, c->argv[0], c->argv[1], c->argv[2]);
81         assert_se(streq(c->path, path));
82         assert_se(streq(c->argv[0], argv0 ?: path));
83         assert_se(streq_ptr(c->argv[1], argv1));
84         assert_se(streq_ptr(c->argv[2], argv2));
85         assert_se(c->ignore == ignore);
86 }
87
88 static void test_config_parse_exec(void) {
89         /* int config_parse_exec(
90                  const char *filename,
91                  unsigned line,
92                  const char *section,
93                  unsigned section_line,
94                  const char *lvalue,
95                  int ltype,
96                  const char *rvalue,
97                  void *data,
98                  void *userdata) */
99         int r;
100
101         ExecCommand *c = NULL, *c1;
102         const char *ccc;
103
104         log_info("/* basic test */");
105         r = config_parse_exec(NULL, "fake", 1, "section", 1,
106                               "LValue", 0, "/RValue r1",
107                               &c, NULL);
108         assert_se(r >= 0);
109         check_execcommand(c, "/RValue", "/RValue", "r1", NULL, false);
110
111         r = config_parse_exec(NULL, "fake", 2, "section", 1,
112                               "LValue", 0, "/RValue///slashes r1///",
113                               &c, NULL);
114
115         log_info("/* test slashes */");
116         assert_se(r >= 0);
117         c1 = c->command_next;
118         check_execcommand(c1, "/RValue/slashes", "/RValue///slashes", "r1///", NULL, false);
119
120         log_info("/* trailing slash */");
121         r = config_parse_exec(NULL, "fake", 4, "section", 1,
122                               "LValue", 0, "/RValue/ argv0 r1",
123                               &c, NULL);
124         assert_se(r == 0);
125         assert_se(c1->command_next == NULL);
126
127         log_info("/* honour_argv0 */");
128         r = config_parse_exec(NULL, "fake", 3, "section", 1,
129                               "LValue", 0, "@/RValue///slashes2 ///argv0 r1",
130                               &c, NULL);
131         assert_se(r >= 0);
132         c1 = c1->command_next;
133         check_execcommand(c1, "/RValue/slashes2", "///argv0", "r1", NULL, false);
134
135         log_info("/* ignore && honour_argv0 */");
136         r = config_parse_exec(NULL, "fake", 4, "section", 1,
137                               "LValue", 0, "-@/RValue///slashes3 argv0a r1",
138                               &c, NULL);
139         assert_se(r >= 0);
140         c1 = c1->command_next;
141         check_execcommand(c1, "/RValue/slashes3", "argv0a", "r1", NULL, true);
142
143         log_info("/* ignore && honour_argv0 */");
144         r = config_parse_exec(NULL, "fake", 4, "section", 1,
145                               "LValue", 0, "@-/RValue///slashes4 argv0b r1",
146                               &c, NULL);
147         assert_se(r >= 0);
148         c1 = c1->command_next;
149         check_execcommand(c1, "/RValue/slashes4", "argv0b", "r1", NULL, true);
150
151         log_info("/* ignore && ignore */");
152         r = config_parse_exec(NULL, "fake", 4, "section", 1,
153                               "LValue", 0, "--/RValue argv0 r1",
154                               &c, NULL);
155         assert_se(r == 0);
156         assert_se(c1->command_next == NULL);
157
158         log_info("/* ignore && ignore (2) */");
159         r = config_parse_exec(NULL, "fake", 4, "section", 1,
160                               "LValue", 0, "-@-/RValue argv0 r1",
161                               &c, NULL);
162         assert_se(r == 0);
163         assert_se(c1->command_next == NULL);
164
165         log_info("/* semicolon */");
166         r = config_parse_exec(NULL, "fake", 5, "section", 1,
167                               "LValue", 0,
168                               "-@/RValue argv0 r1 ; "
169                               "/goo/goo boo",
170                               &c, NULL);
171         assert_se(r >= 0);
172         c1 = c1->command_next;
173         check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true);
174
175         c1 = c1->command_next;
176         check_execcommand(c1, "/goo/goo", NULL, "boo", NULL, false);
177
178         log_info("/* trailing semicolon */");
179         r = config_parse_exec(NULL, "fake", 5, "section", 1,
180                               "LValue", 0,
181                               "-@/RValue argv0 r1 ; ",
182                               &c, NULL);
183         assert_se(r >= 0);
184         c1 = c1->command_next;
185         check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true);
186
187         assert_se(c1->command_next == NULL);
188
189         log_info("/* escaped semicolon */");
190         r = config_parse_exec(NULL, "fake", 5, "section", 1,
191                               "LValue", 0,
192                               "/bin/find \\;",
193                               &c, NULL);
194         assert_se(r >= 0);
195         c1 = c1->command_next;
196         check_execcommand(c1, "/bin/find", NULL, ";", NULL, false);
197
198         log_info("/* escaped semicolon with following arg */");
199         r = config_parse_exec(NULL, "fake", 5, "section", 1,
200                               "LValue", 0,
201                               "/sbin/find \\; x",
202                               &c, NULL);
203         assert_se(r >= 0);
204         c1 = c1->command_next;
205         check_execcommand(c1,
206                           "/sbin/find", NULL, ";", "x", false);
207
208         log_info("/* spaces in the filename */");
209         r = config_parse_exec(NULL, "fake", 5, "section", 1,
210                               "LValue", 0,
211                               "\"/PATH WITH SPACES/daemon\" -1 -2",
212                               &c, NULL);
213         assert_se(r >= 0);
214         c1 = c1->command_next;
215         check_execcommand(c1,
216                           "/PATH WITH SPACES/daemon", NULL, "-1", "-2", false);
217
218         log_info("/* spaces in the filename, no args */");
219         r = config_parse_exec(NULL, "fake", 5, "section", 1,
220                               "LValue", 0,
221                               "\"/PATH WITH SPACES/daemon -1 -2\"",
222                               &c, NULL);
223         assert_se(r >= 0);
224         c1 = c1->command_next;
225         check_execcommand(c1,
226                           "/PATH WITH SPACES/daemon -1 -2", NULL, NULL, NULL, false);
227
228         log_info("/* spaces in the filename, everything quoted */");
229         r = config_parse_exec(NULL, "fake", 5, "section", 1,
230                               "LValue", 0,
231                               "\"/PATH WITH SPACES/daemon\" \"-1\" '-2'",
232                               &c, NULL);
233         assert_se(r >= 0);
234         c1 = c1->command_next;
235         check_execcommand(c1,
236                           "/PATH WITH SPACES/daemon", NULL, "-1", "-2", false);
237
238         log_info("/* escaped spaces in the filename */");
239         r = config_parse_exec(NULL, "fake", 5, "section", 1,
240                               "LValue", 0,
241                               "\"/PATH\\sWITH\\sSPACES/daemon\" '-1 -2'",
242                               &c, NULL);
243         assert_se(r >= 0);
244         c1 = c1->command_next;
245         check_execcommand(c1,
246                           "/PATH WITH SPACES/daemon", NULL, "-1 -2", NULL, false);
247
248         log_info("/* escaped spaces in the filename (2) */");
249         r = config_parse_exec(NULL, "fake", 5, "section", 1,
250                               "LValue", 0,
251                               "\"/PATH\\x20WITH\\x20SPACES/daemon\" \"-1 -2\"",
252                               &c, NULL);
253         assert_se(r >= 0);
254         c1 = c1->command_next;
255         check_execcommand(c1,
256                           "/PATH WITH SPACES/daemon", NULL, "-1 -2", NULL, false);
257
258         for (ccc = "abfnrtv\\\'\"x"; *ccc; ccc++) {
259                 /* \\x is an incomplete hexadecimal sequence, invalid because of the slash */
260                 char path[] = "/path\\X";
261                 path[sizeof(path) - 2] = *ccc;
262
263                 log_info("/* invalid character: \\%c */", *ccc);
264                 r = config_parse_exec(NULL, "fake", 4, "section", 1,
265                                       "LValue", 0, path,
266                                       &c, NULL);
267                 assert_se(r == 0);
268                 assert_se(c1->command_next == NULL);
269         }
270
271         log_info("/* valid character: \\s */");
272         r = config_parse_exec(NULL, "fake", 4, "section", 1,
273                               "LValue", 0, "/path\\s",
274                               &c, NULL);
275         assert_se(r >= 0);
276         c1 = c1->command_next;
277         check_execcommand(c1, "/path ", NULL, NULL, NULL, false);
278
279         log_info("/* trailing backslash: \\ */");
280         /* backslash is invalid */
281         r = config_parse_exec(NULL, "fake", 4, "section", 1,
282                               "LValue", 0, "/path\\",
283                               &c, NULL);
284         assert_se(r == 0);
285         assert_se(c1->command_next == NULL);
286
287         exec_command_free_list(c);
288 }
289
290 #define env_file_1                              \
291         "a=a\n"                                 \
292         "b=b\\\n"                               \
293         "c\n"                                   \
294         "d=d\\\n"                               \
295         "e\\\n"                                 \
296         "f\n"                                   \
297         "g=g\\ \n"                              \
298         "h=h\n"                                 \
299         "i=i\\"
300
301 #define env_file_2                              \
302         "a=a\\\n"
303
304 #define env_file_3 \
305         "#SPAMD_ARGS=\"-d --socketpath=/var/lib/bulwark/spamd \\\n" \
306         "#--nouser-config                                     \\\n" \
307         "normal=line"
308
309 #define env_file_4 \
310        "# Generated\n" \
311        "\n" \
312        "HWMON_MODULES=\"coretemp f71882fg\"\n" \
313        "\n" \
314        "# For compatibility reasons\n" \
315        "\n" \
316        "MODULE_0=coretemp\n" \
317        "MODULE_1=f71882fg"
318
319 #define env_file_5                              \
320         "a=\n"                                 \
321         "b="
322
323 static void test_load_env_file_1(void) {
324         _cleanup_strv_free_ char **data = NULL;
325         int r;
326
327         char name[] = "/tmp/test-load-env-file.XXXXXX";
328         _cleanup_close_ int fd;
329
330         fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC);
331         assert_se(fd >= 0);
332         assert_se(write(fd, env_file_1, sizeof(env_file_1)) == sizeof(env_file_1));
333
334         r = load_env_file(NULL, name, NULL, &data);
335         assert_se(r == 0);
336         assert_se(streq(data[0], "a=a"));
337         assert_se(streq(data[1], "b=bc"));
338         assert_se(streq(data[2], "d=def"));
339         assert_se(streq(data[3], "g=g "));
340         assert_se(streq(data[4], "h=h"));
341         assert_se(streq(data[5], "i=i"));
342         assert_se(data[6] == NULL);
343         unlink(name);
344 }
345
346 static void test_load_env_file_2(void) {
347         _cleanup_strv_free_ char **data = NULL;
348         int r;
349
350         char name[] = "/tmp/test-load-env-file.XXXXXX";
351         _cleanup_close_ int fd;
352
353         fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC);
354         assert_se(fd >= 0);
355         assert_se(write(fd, env_file_2, sizeof(env_file_2)) == sizeof(env_file_2));
356
357         r = load_env_file(NULL, name, NULL, &data);
358         assert_se(r == 0);
359         assert_se(streq(data[0], "a=a"));
360         assert_se(data[1] == NULL);
361         unlink(name);
362 }
363
364 static void test_load_env_file_3(void) {
365         _cleanup_strv_free_ char **data = NULL;
366         int r;
367
368         char name[] = "/tmp/test-load-env-file.XXXXXX";
369         _cleanup_close_ int fd;
370
371         fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC);
372         assert_se(fd >= 0);
373         assert_se(write(fd, env_file_3, sizeof(env_file_3)) == sizeof(env_file_3));
374
375         r = load_env_file(NULL, name, NULL, &data);
376         assert_se(r == 0);
377         assert_se(data == NULL);
378         unlink(name);
379 }
380
381 static void test_load_env_file_4(void) {
382         _cleanup_strv_free_ char **data = NULL;
383         char name[] = "/tmp/test-load-env-file.XXXXXX";
384         _cleanup_close_ int fd;
385         int r;
386
387         fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC);
388         assert_se(fd >= 0);
389         assert_se(write(fd, env_file_4, sizeof(env_file_4)) == sizeof(env_file_4));
390
391         r = load_env_file(NULL, name, NULL, &data);
392         assert_se(r == 0);
393         assert_se(streq(data[0], "HWMON_MODULES=coretemp f71882fg"));
394         assert_se(streq(data[1], "MODULE_0=coretemp"));
395         assert_se(streq(data[2], "MODULE_1=f71882fg"));
396         assert_se(data[3] == NULL);
397         unlink(name);
398 }
399
400 static void test_load_env_file_5(void) {
401         _cleanup_strv_free_ char **data = NULL;
402         int r;
403
404         char name[] = "/tmp/test-load-env-file.XXXXXX";
405         _cleanup_close_ int fd;
406
407         fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC);
408         assert_se(fd >= 0);
409         assert_se(write(fd, env_file_5, sizeof(env_file_5)) == sizeof(env_file_5));
410
411         r = load_env_file(NULL, name, NULL, &data);
412         assert_se(r == 0);
413         assert_se(streq(data[0], "a="));
414         assert_se(streq(data[1], "b="));
415         assert_se(data[2] == NULL);
416         unlink(name);
417 }
418
419 static void test_install_printf(void) {
420         char    name[] = "name.service",
421                 path[] = "/run/systemd/system/name.service",
422                 user[] = "xxxx-no-such-user";
423         InstallInfo i = {name, path, user};
424         InstallInfo i2 = {name, path, NULL};
425         char    name3[] = "name@inst.service",
426                 path3[] = "/run/systemd/system/name.service";
427         InstallInfo i3 = {name3, path3, user};
428         InstallInfo i4 = {name3, path3, NULL};
429
430         _cleanup_free_ char *mid, *bid, *host;
431
432         assert_se(specifier_machine_id('m', NULL, NULL, &mid) >= 0 && mid);
433         assert_se(specifier_boot_id('b', NULL, NULL, &bid) >= 0 && bid);
434         assert_se((host = gethostname_malloc()));
435
436 #define expect(src, pattern, result)                                    \
437         do {                                                            \
438                 _cleanup_free_ char *t = NULL;                          \
439                 _cleanup_free_ char                                     \
440                         *d1 = strdup(i.name),                           \
441                         *d2 = strdup(i.path),                           \
442                         *d3 = strdup(i.user);                           \
443                 assert_se(install_full_printf(&src, pattern, &t) >= 0 || !result); \
444                 memzero(i.name, strlen(i.name));                        \
445                 memzero(i.path, strlen(i.path));                        \
446                 memzero(i.user, strlen(i.user));                        \
447                 assert_se(d1 && d2 && d3);                                 \
448                 if (result) {                                           \
449                         printf("%s\n", t);                              \
450                         assert_se(streq(t, result));                       \
451                 } else assert_se(t == NULL);                               \
452                 strcpy(i.name, d1);                                     \
453                 strcpy(i.path, d2);                                     \
454                 strcpy(i.user, d3);                                     \
455         } while(false)
456
457         assert_se(setenv("USER", "root", 1) == 0);
458
459         expect(i, "%n", "name.service");
460         expect(i, "%N", "name");
461         expect(i, "%p", "name");
462         expect(i, "%i", "");
463         expect(i, "%u", "xxxx-no-such-user");
464
465         DISABLE_WARNING_NONNULL;
466         expect(i, "%U", NULL);
467         REENABLE_WARNING;
468
469         expect(i, "%m", mid);
470         expect(i, "%b", bid);
471         expect(i, "%H", host);
472
473         expect(i2, "%u", "root");
474         expect(i2, "%U", "0");
475
476         expect(i3, "%n", "name@inst.service");
477         expect(i3, "%N", "name@inst");
478         expect(i3, "%p", "name");
479         expect(i3, "%u", "xxxx-no-such-user");
480
481         DISABLE_WARNING_NONNULL;
482         expect(i3, "%U", NULL);
483         REENABLE_WARNING;
484
485         expect(i3, "%m", mid);
486         expect(i3, "%b", bid);
487         expect(i3, "%H", host);
488
489         expect(i4, "%u", "root");
490         expect(i4, "%U", "0");
491 }
492
493 int main(int argc, char *argv[]) {
494         int r;
495
496         log_parse_environment();
497         log_open();
498
499         r = test_unit_file_get_set();
500         test_config_parse_exec();
501         test_load_env_file_1();
502         test_load_env_file_2();
503         test_load_env_file_3();
504         test_load_env_file_4();
505         test_load_env_file_5();
506         TEST_REQ_RUNNING_SYSTEMD(test_install_printf());
507
508         return r;
509 }