]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tmpfiles/tmpfiles.c
Use initalization instead of explicit zeroing
[thirdparty/systemd.git] / src / tmpfiles / tmpfiles.c
CommitLineData
5008d581
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
3b63d2d3 6 Copyright 2010 Lennart Poettering, Kay Sievers
5008d581
LP
7
8 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
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
5008d581
LP
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
5430f7f2 16 Lesser General Public License for more details.
5008d581 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
5008d581
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <unistd.h>
23#include <fcntl.h>
24#include <errno.h>
25#include <string.h>
26#include <sys/stat.h>
27#include <limits.h>
28#include <dirent.h>
29#include <grp.h>
30#include <pwd.h>
3b63d2d3
LP
31#include <stdio.h>
32#include <stdlib.h>
33#include <stddef.h>
34#include <getopt.h>
35#include <stdbool.h>
36#include <time.h>
37#include <sys/types.h>
38#include <sys/param.h>
b8bb3e8f
LP
39#include <glob.h>
40#include <fnmatch.h>
cb7ed9df 41#include <sys/capability.h>
5008d581
LP
42
43#include "log.h"
44#include "util.h"
54693d9b 45#include "macro.h"
d39efe74 46#include "missing.h"
49e942b2 47#include "mkdir.h"
9eb977db 48#include "path-util.h"
5008d581
LP
49#include "strv.h"
50#include "label.h"
3b63d2d3 51#include "set.h"
2c21044f 52#include "conf-files.h"
cb7ed9df 53#include "capability.h"
5008d581 54
01000479 55/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates
5008d581 56 * them in the file system. This is intended to be used to create
db019b8d
KS
57 * properly owned directories beneath /tmp, /var/tmp, /run, which are
58 * volatile and hence need to be recreated on bootup. */
5008d581 59
66ccd038 60typedef enum ItemType {
b8bb3e8f 61 /* These ones take file names */
3b63d2d3
LP
62 CREATE_FILE = 'f',
63 TRUNCATE_FILE = 'F',
31ed59c5 64 WRITE_FILE = 'w',
3b63d2d3
LP
65 CREATE_DIRECTORY = 'd',
66 TRUNCATE_DIRECTORY = 'D',
ee17ee7c 67 CREATE_FIFO = 'p',
468d726b
LP
68 CREATE_SYMLINK = 'L',
69 CREATE_CHAR_DEVICE = 'c',
70 CREATE_BLOCK_DEVICE = 'b',
b8bb3e8f
LP
71
72 /* These ones take globs */
3b63d2d3 73 IGNORE_PATH = 'x',
78a92a5a 74 IGNORE_DIRECTORY_PATH = 'X',
3b63d2d3 75 REMOVE_PATH = 'r',
a8d88783 76 RECURSIVE_REMOVE_PATH = 'R',
777b87e7 77 RELABEL_PATH = 'z',
a8d88783 78 RECURSIVE_RELABEL_PATH = 'Z'
66ccd038 79} ItemType;
3b63d2d3
LP
80
81typedef struct Item {
66ccd038 82 ItemType type;
3b63d2d3
LP
83
84 char *path;
468d726b 85 char *argument;
5008d581
LP
86 uid_t uid;
87 gid_t gid;
3b63d2d3
LP
88 mode_t mode;
89 usec_t age;
90
468d726b
LP
91 dev_t major_minor;
92
3b63d2d3
LP
93 bool uid_set:1;
94 bool gid_set:1;
95 bool mode_set:1;
96 bool age_set:1;
24f3a374
LP
97
98 bool keep_first_level:1;
3b63d2d3
LP
99} Item;
100
b8bb3e8f 101static Hashmap *items = NULL, *globs = NULL;
17b90525 102static Set *unix_sockets = NULL;
3b63d2d3
LP
103
104static bool arg_create = false;
105static bool arg_clean = false;
106static bool arg_remove = false;
107
fba6e687
LP
108static const char *arg_prefix = NULL;
109
fabe5c0e
LP
110static const char conf_file_dirs[] =
111 "/etc/tmpfiles.d\0"
112 "/run/tmpfiles.d\0"
113 "/usr/local/lib/tmpfiles.d\0"
114 "/usr/lib/tmpfiles.d\0"
3f2afb29 115#ifdef HAVE_SPLIT_USR
fabe5c0e 116 "/lib/tmpfiles.d\0"
3f2afb29 117#endif
fabe5c0e 118 ;
9125670f 119
3b63d2d3
LP
120#define MAX_DEPTH 256
121
66ccd038 122static bool needs_glob(ItemType t) {
78a92a5a 123 return t == IGNORE_PATH || t == IGNORE_DIRECTORY_PATH || t == REMOVE_PATH || t == RECURSIVE_REMOVE_PATH || t == RELABEL_PATH || t == RECURSIVE_RELABEL_PATH;
b8bb3e8f
LP
124}
125
126static struct Item* find_glob(Hashmap *h, const char *match) {
127 Item *j;
128 Iterator i;
129
130 HASHMAP_FOREACH(j, h, i)
131 if (fnmatch(j->path, match, FNM_PATHNAME|FNM_PERIOD) == 0)
132 return j;
133
134 return NULL;
135}
136
17b90525 137static void load_unix_sockets(void) {
19fbec19 138 FILE _cleanup_fclose_ *f = NULL;
17b90525
LP
139 char line[LINE_MAX];
140
141 if (unix_sockets)
142 return;
143
144 /* We maintain a cache of the sockets we found in
145 * /proc/net/unix to speed things up a little. */
146
fdcad0c2
LP
147 unix_sockets = set_new(string_hash_func, string_compare_func);
148 if (!unix_sockets)
17b90525
LP
149 return;
150
fdcad0c2
LP
151 f = fopen("/proc/net/unix", "re");
152 if (!f)
17b90525
LP
153 return;
154
fdcad0c2
LP
155 /* Skip header */
156 if (!fgets(line, sizeof(line), f))
17b90525
LP
157 goto fail;
158
159 for (;;) {
160 char *p, *s;
161 int k;
162
fdcad0c2 163 if (!fgets(line, sizeof(line), f))
17b90525
LP
164 break;
165
166 truncate_nl(line);
167
fdcad0c2
LP
168 p = strchr(line, ':');
169 if (!p)
170 continue;
171
172 if (strlen(p) < 37)
17b90525
LP
173 continue;
174
fdcad0c2 175 p += 37;
17b90525 176 p += strspn(p, WHITESPACE);
fdcad0c2 177 p += strcspn(p, WHITESPACE); /* skip one more word */
17b90525
LP
178 p += strspn(p, WHITESPACE);
179
180 if (*p != '/')
181 continue;
182
fdcad0c2
LP
183 s = strdup(p);
184 if (!s)
17b90525
LP
185 goto fail;
186
4ff21d85
LP
187 path_kill_slashes(s);
188
fdcad0c2
LP
189 k = set_put(unix_sockets, s);
190 if (k < 0) {
17b90525
LP
191 free(s);
192
193 if (k != -EEXIST)
194 goto fail;
195 }
196 }
197
198 return;
199
200fail:
201 set_free_free(unix_sockets);
202 unix_sockets = NULL;
17b90525
LP
203}
204
205static bool unix_socket_alive(const char *fn) {
206 assert(fn);
207
208 load_unix_sockets();
209
210 if (unix_sockets)
211 return !!set_get(unix_sockets, (char*) fn);
212
213 /* We don't know, so assume yes */
214 return true;
215}
216
99d680ac
KS
217static int dir_is_mount_point(DIR *d, const char *subdir) {
218 struct file_handle *h;
219 int mount_id_parent, mount_id;
220 int r_p, r;
221
222 h = alloca(MAX_HANDLE_SZ);
223
224 h->handle_bytes = MAX_HANDLE_SZ;
225 r_p = name_to_handle_at(dirfd(d), ".", h, &mount_id_parent, 0);
226 if (r_p < 0)
227 r_p = -errno;
228
229 h->handle_bytes = MAX_HANDLE_SZ;
230 r = name_to_handle_at(dirfd(d), subdir, h, &mount_id, 0);
231 if (r < 0)
232 r = -errno;
233
234 /* got no handle; make no assumptions, return error */
235 if (r_p < 0 && r < 0)
236 return r_p;
237
238 /* got both handles; if they differ, it is a mount point */
239 if (r_p >= 0 && r >= 0)
240 return mount_id_parent != mount_id;
241
242 /* got only one handle; assume different mount points if one
243 * of both queries was not supported by the filesystem */
244 if (r_p == -ENOSYS || r_p == -ENOTSUP || r == -ENOSYS || r == -ENOTSUP)
245 return true;
246
247 /* return error */
248 if (r_p < 0)
249 return r_p;
250 return r;
251}
252
3b63d2d3 253static int dir_cleanup(
78a92a5a 254 Item *i,
3b63d2d3
LP
255 const char *p,
256 DIR *d,
257 const struct stat *ds,
258 usec_t cutoff,
259 dev_t rootdev,
260 bool mountpoint,
24f3a374
LP
261 int maxdepth,
262 bool keep_this_level)
3b63d2d3
LP
263{
264 struct dirent *dent;
265 struct timespec times[2];
266 bool deleted = false;
3b63d2d3
LP
267 int r = 0;
268
269 while ((dent = readdir(d))) {
270 struct stat s;
271 usec_t age;
19fbec19 272 char _cleanup_free_ *sub_path = NULL;
3b63d2d3
LP
273
274 if (streq(dent->d_name, ".") ||
275 streq(dent->d_name, ".."))
276 continue;
5008d581 277
3b63d2d3 278 if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) {
5008d581 279
3b63d2d3
LP
280 if (errno != ENOENT) {
281 log_error("stat(%s/%s) failed: %m", p, dent->d_name);
282 r = -errno;
283 }
284
285 continue;
286 }
287
288 /* Stay on the same filesystem */
289 if (s.st_dev != rootdev)
290 continue;
291
99d680ac
KS
292 /* Try to detect bind mounts of the same filesystem instance; they
293 * do not differ in device major/minors. This type of query is not
294 * supported on all kernels or filesystem types though. */
295 if (S_ISDIR(s.st_mode) && dir_is_mount_point(d, dent->d_name) > 0)
296 continue;
297
3b63d2d3
LP
298 /* Do not delete read-only files owned by root */
299 if (s.st_uid == 0 && !(s.st_mode & S_IWUSR))
300 continue;
301
3b63d2d3 302 if (asprintf(&sub_path, "%s/%s", p, dent->d_name) < 0) {
0d0f0c50 303 r = log_oom();
3b63d2d3
LP
304 goto finish;
305 }
306
307 /* Is there an item configured for this path? */
308 if (hashmap_get(items, sub_path))
309 continue;
310
b8bb3e8f
LP
311 if (find_glob(globs, sub_path))
312 continue;
313
3b63d2d3
LP
314 if (S_ISDIR(s.st_mode)) {
315
316 if (mountpoint &&
317 streq(dent->d_name, "lost+found") &&
318 s.st_uid == 0)
319 continue;
320
321 if (maxdepth <= 0)
322 log_warning("Reached max depth on %s.", sub_path);
323 else {
19fbec19 324 DIR _cleanup_closedir_ *sub_dir;
3b63d2d3
LP
325 int q;
326
e5f3d1ba 327 sub_dir = xopendirat(dirfd(d), dent->d_name, O_NOFOLLOW|O_NOATIME);
3b63d2d3
LP
328 if (sub_dir == NULL) {
329 if (errno != ENOENT) {
330 log_error("opendir(%s/%s) failed: %m", p, dent->d_name);
331 r = -errno;
332 }
333
334 continue;
335 }
336
78a92a5a 337 q = dir_cleanup(i, sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1, false);
3b63d2d3
LP
338
339 if (q < 0)
340 r = q;
341 }
342
24f3a374
LP
343 /* Note: if you are wondering why we don't
344 * support the sticky bit for excluding
345 * directories from cleaning like we do it for
346 * other file system objects: well, the sticky
347 * bit already has a meaning for directories,
348 * so we don't want to overload that. */
349
350 if (keep_this_level)
351 continue;
352
3b63d2d3
LP
353 /* Ignore ctime, we change it when deleting */
354 age = MAX(timespec_load(&s.st_mtim),
355 timespec_load(&s.st_atim));
356 if (age >= cutoff)
357 continue;
358
a6187d4c 359 if (i->type != IGNORE_DIRECTORY_PATH || !streq(dent->d_name, p)) {
78a92a5a 360 log_debug("rmdir '%s'\n", sub_path);
3b63d2d3 361
78a92a5a
MS
362 if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0) {
363 if (errno != ENOENT && errno != ENOTEMPTY) {
364 log_error("rmdir(%s): %m", sub_path);
365 r = -errno;
366 }
3b63d2d3
LP
367 }
368 }
369
370 } else {
9c73736d
LP
371 /* Skip files for which the sticky bit is
372 * set. These are semantics we define, and are
373 * unknown elsewhere. See XDG_RUNTIME_DIR
374 * specification for details. */
375 if (s.st_mode & S_ISVTX)
376 continue;
377
17b90525 378 if (mountpoint && S_ISREG(s.st_mode)) {
3b63d2d3
LP
379 if (streq(dent->d_name, ".journal") &&
380 s.st_uid == 0)
381 continue;
382
383 if (streq(dent->d_name, "aquota.user") ||
384 streq(dent->d_name, "aquota.group"))
385 continue;
386 }
387
17b90525
LP
388 /* Ignore sockets that are listed in /proc/net/unix */
389 if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path))
390 continue;
391
78ab08eb
LP
392 /* Ignore device nodes */
393 if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode))
394 continue;
395
24f3a374
LP
396 /* Keep files on this level around if this is
397 * requested */
398 if (keep_this_level)
399 continue;
400
3b63d2d3
LP
401 age = MAX3(timespec_load(&s.st_mtim),
402 timespec_load(&s.st_atim),
403 timespec_load(&s.st_ctim));
404
405 if (age >= cutoff)
406 continue;
407
408 log_debug("unlink '%s'\n", sub_path);
409
410 if (unlinkat(dirfd(d), dent->d_name, 0) < 0) {
411 if (errno != ENOENT) {
412 log_error("unlink(%s): %m", sub_path);
413 r = -errno;
414 }
415 }
416
417 deleted = true;
418 }
419 }
420
421finish:
422 if (deleted) {
423 /* Restore original directory timestamps */
424 times[0] = ds->st_atim;
425 times[1] = ds->st_mtim;
426
427 if (futimens(dirfd(d), times) < 0)
428 log_error("utimensat(%s): %m", p);
429 }
430
3b63d2d3
LP
431 return r;
432}
433
062e01bb
MS
434static int item_set_perms(Item *i, const char *path) {
435 /* not using i->path directly because it may be a glob */
436 if (i->mode_set)
437 if (chmod(path, i->mode) < 0) {
438 log_error("chmod(%s) failed: %m", path);
439 return -errno;
440 }
441
442 if (i->uid_set || i->gid_set)
443 if (chown(path,
444 i->uid_set ? i->uid : (uid_t) -1,
445 i->gid_set ? i->gid : (gid_t) -1) < 0) {
446
447 log_error("chown(%s) failed: %m", path);
448 return -errno;
449 }
450
c9bc0764 451 return label_fix(path, false, false);
062e01bb
MS
452}
453
d4e9eb91
DR
454static int write_one_file(Item *i, const char *path) {
455 int r, e, fd, flags;
456 struct stat st;
d4e9eb91
DR
457
458 flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND :
459 i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC : 0;
460
5c0d398d
LP
461 RUN_WITH_UMASK(0) {
462 label_context_set(path, S_IFREG);
463 fd = open(path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY|O_NOFOLLOW, i->mode);
464 e = errno;
465 label_context_clear();
466 errno = e;
467 }
d4e9eb91
DR
468
469 if (fd < 0) {
470 if (i->type == WRITE_FILE && errno == ENOENT)
471 return 0;
472
473 log_error("Failed to create file %s: %m", path);
474 return -errno;
475 }
476
477 if (i->argument) {
478 ssize_t n;
479 size_t l;
54693d9b 480 _cleanup_free_ char *unescaped;
d4e9eb91 481
54693d9b 482 unescaped = cunescape(i->argument);
3785ba69
TJ
483 if (unescaped == NULL) {
484 close_nointr_nofail(fd);
54693d9b 485 return log_oom();
3785ba69 486 }
d4e9eb91 487
54693d9b
DR
488 l = strlen(unescaped);
489 n = write(fd, unescaped, l);
d4e9eb91 490
d4e9eb91
DR
491 if (n < 0 || (size_t) n < l) {
492 log_error("Failed to write file %s: %s", path, n < 0 ? strerror(-n) : "Short write");
493 close_nointr_nofail(fd);
494 return n < 0 ? n : -EIO;
495 }
496 }
497
3612fbc1
DR
498 close_nointr_nofail(fd);
499
d4e9eb91
DR
500 if (stat(path, &st) < 0) {
501 log_error("stat(%s) failed: %m", path);
502 return -errno;
503 }
504
505 if (!S_ISREG(st.st_mode)) {
506 log_error("%s is not a file.", path);
507 return -EEXIST;
508 }
509
510 r = item_set_perms(i, path);
511 if (r < 0)
512 return r;
513
514 return 0;
515}
516
062e01bb 517static int recursive_relabel_children(Item *i, const char *path) {
19fbec19 518 DIR _cleanup_closedir_ *d;
a8d88783
MS
519 int ret = 0;
520
521 /* This returns the first error we run into, but nevertheless
522 * tries to go on */
523
524 d = opendir(path);
525 if (!d)
526 return errno == ENOENT ? 0 : -errno;
527
528 for (;;) {
7d5e9c0f
LP
529 struct dirent *de;
530 union dirent_storage buf;
a8d88783
MS
531 bool is_dir;
532 int r;
19fbec19 533 char _cleanup_free_ *entry_path = NULL;
a8d88783 534
7d5e9c0f 535 r = readdir_r(d, &buf.de, &de);
a8d88783
MS
536 if (r != 0) {
537 if (ret == 0)
538 ret = -r;
539 break;
540 }
541
542 if (!de)
543 break;
544
545 if (streq(de->d_name, ".") || streq(de->d_name, ".."))
546 continue;
547
548 if (asprintf(&entry_path, "%s/%s", path, de->d_name) < 0) {
549 if (ret == 0)
550 ret = -ENOMEM;
551 continue;
552 }
553
554 if (de->d_type == DT_UNKNOWN) {
555 struct stat st;
556
557 if (lstat(entry_path, &st) < 0) {
558 if (ret == 0 && errno != ENOENT)
559 ret = -errno;
a8d88783
MS
560 continue;
561 }
562
563 is_dir = S_ISDIR(st.st_mode);
564
565 } else
566 is_dir = de->d_type == DT_DIR;
567
062e01bb 568 r = item_set_perms(i, entry_path);
a8d88783
MS
569 if (r < 0) {
570 if (ret == 0 && r != -ENOENT)
571 ret = r;
a8d88783
MS
572 continue;
573 }
574
575 if (is_dir) {
062e01bb 576 r = recursive_relabel_children(i, entry_path);
a8d88783
MS
577 if (r < 0 && ret == 0)
578 ret = r;
579 }
a8d88783
MS
580 }
581
a8d88783
MS
582 return ret;
583}
584
585static int recursive_relabel(Item *i, const char *path) {
586 int r;
587 struct stat st;
588
062e01bb 589 r = item_set_perms(i, path);
a8d88783
MS
590 if (r < 0)
591 return r;
592
593 if (lstat(path, &st) < 0)
594 return -errno;
595
596 if (S_ISDIR(st.st_mode))
062e01bb 597 r = recursive_relabel_children(i, path);
a8d88783
MS
598
599 return r;
600}
601
99e68c0b
MS
602static int glob_item(Item *i, int (*action)(Item *, const char *)) {
603 int r = 0, k;
b92bea5d 604 glob_t g = {};
99e68c0b
MS
605 char **fn;
606
99e68c0b
MS
607 errno = 0;
608 if ((k = glob(i->path, GLOB_NOSORT|GLOB_BRACE, NULL, &g)) != 0) {
609
610 if (k != GLOB_NOMATCH) {
8333c77e 611 if (errno > 0)
99e68c0b
MS
612 errno = EIO;
613
614 log_error("glob(%s) failed: %m", i->path);
615 return -errno;
616 }
617 }
618
619 STRV_FOREACH(fn, g.gl_pathv)
620 if ((k = action(i, *fn)) < 0)
621 r = k;
622
623 globfree(&g);
624 return r;
625}
626
3b63d2d3 627static int create_item(Item *i) {
e9a5ef7c 628 int r, e;
3b63d2d3 629 struct stat st;
5008d581 630
3b63d2d3 631 assert(i);
5008d581 632
3b63d2d3
LP
633 switch (i->type) {
634
635 case IGNORE_PATH:
78a92a5a 636 case IGNORE_DIRECTORY_PATH:
3b63d2d3
LP
637 case REMOVE_PATH:
638 case RECURSIVE_REMOVE_PATH:
639 return 0;
5008d581 640
3b63d2d3 641 case CREATE_FILE:
31ed59c5 642 case TRUNCATE_FILE:
1845fdd9
DR
643 r = write_one_file(i, i->path);
644 if (r < 0)
645 return r;
646 break;
d4e9eb91
DR
647 case WRITE_FILE:
648 r = glob_item(i, write_one_file);
f05bc3f7
MS
649 if (r < 0)
650 return r;
5008d581 651
3b63d2d3
LP
652 break;
653
654 case TRUNCATE_DIRECTORY:
655 case CREATE_DIRECTORY:
5008d581 656
5c0d398d
LP
657 RUN_WITH_UMASK(0000) {
658 mkdir_parents_label(i->path, 0755);
659 r = mkdir(i->path, i->mode);
660 }
5008d581
LP
661
662 if (r < 0 && errno != EEXIST) {
3b63d2d3 663 log_error("Failed to create directory %s: %m", i->path);
f05bc3f7 664 return -errno;
5008d581
LP
665 }
666
3b63d2d3
LP
667 if (stat(i->path, &st) < 0) {
668 log_error("stat(%s) failed: %m", i->path);
f05bc3f7 669 return -errno;
5008d581
LP
670 }
671
672 if (!S_ISDIR(st.st_mode)) {
3b63d2d3 673 log_error("%s is not a directory.", i->path);
f05bc3f7 674 return -EEXIST;
5008d581
LP
675 }
676
062e01bb 677 r = item_set_perms(i, i->path);
f05bc3f7
MS
678 if (r < 0)
679 return r;
3b63d2d3
LP
680
681 break;
ee17ee7c
LP
682
683 case CREATE_FIFO:
684
5c0d398d
LP
685 RUN_WITH_UMASK(0000) {
686 r = mkfifo(i->path, i->mode);
687 }
ee17ee7c
LP
688
689 if (r < 0 && errno != EEXIST) {
690 log_error("Failed to create fifo %s: %m", i->path);
f05bc3f7 691 return -errno;
ee17ee7c
LP
692 }
693
694 if (stat(i->path, &st) < 0) {
695 log_error("stat(%s) failed: %m", i->path);
f05bc3f7 696 return -errno;
ee17ee7c
LP
697 }
698
699 if (!S_ISFIFO(st.st_mode)) {
700 log_error("%s is not a fifo.", i->path);
f05bc3f7 701 return -EEXIST;
ee17ee7c
LP
702 }
703
062e01bb 704 r = item_set_perms(i, i->path);
f05bc3f7
MS
705 if (r < 0)
706 return r;
ee17ee7c
LP
707
708 break;
a8d88783 709
468d726b
LP
710 case CREATE_SYMLINK: {
711 char *x;
712
e9a5ef7c 713 label_context_set(i->path, S_IFLNK);
468d726b 714 r = symlink(i->argument, i->path);
e9a5ef7c
KS
715 e = errno;
716 label_context_clear();
717 errno = e;
718
468d726b
LP
719 if (r < 0 && errno != EEXIST) {
720 log_error("symlink(%s, %s) failed: %m", i->argument, i->path);
721 return -errno;
722 }
723
724 r = readlink_malloc(i->path, &x);
725 if (r < 0) {
726 log_error("readlink(%s) failed: %s", i->path, strerror(-r));
727 return -errno;
728 }
729
730 if (!streq(i->argument, x)) {
731 free(x);
732 log_error("%s is not the right symlinks.", i->path);
733 return -EEXIST;
734 }
735
736 free(x);
737 break;
738 }
739
740 case CREATE_BLOCK_DEVICE:
741 case CREATE_CHAR_DEVICE: {
cb7ed9df
LP
742 mode_t file_type;
743
744 if (have_effective_cap(CAP_MKNOD) == 0) {
745 /* In a container we lack CAP_MKNOD. We
746 shouldnt attempt to create the device node in
747 that case to avoid noise, and we don't support
748 virtualized devices in containers anyway. */
749
750 log_debug("We lack CAP_MKNOD, skipping creation of device node %s.", i->path);
751 return 0;
752 }
753
754 file_type = (i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR);
468d726b 755
5c0d398d
LP
756 RUN_WITH_UMASK(0000) {
757 label_context_set(i->path, file_type);
758 r = mknod(i->path, i->mode | file_type, i->major_minor);
759 e = errno;
760 label_context_clear();
761 errno = e;
762 }
468d726b
LP
763
764 if (r < 0 && errno != EEXIST) {
765 log_error("Failed to create device node %s: %m", i->path);
766 return -errno;
767 }
768
769 if (stat(i->path, &st) < 0) {
770 log_error("stat(%s) failed: %m", i->path);
771 return -errno;
772 }
773
e7aee759 774 if ((st.st_mode & S_IFMT) != file_type) {
468d726b
LP
775 log_error("%s is not a device node.", i->path);
776 return -EEXIST;
777 }
778
779 r = item_set_perms(i, i->path);
780 if (r < 0)
781 return r;
782
783 break;
784 }
785
777b87e7
MS
786 case RELABEL_PATH:
787
788 r = glob_item(i, item_set_perms);
789 if (r < 0)
790 return 0;
791 break;
792
a8d88783
MS
793 case RECURSIVE_RELABEL_PATH:
794
795 r = glob_item(i, recursive_relabel);
796 if (r < 0)
797 return r;
3b63d2d3
LP
798 }
799
3b63d2d3
LP
800 log_debug("%s created successfully.", i->path);
801
f05bc3f7 802 return 0;
3b63d2d3
LP
803}
804
a0896123 805static int remove_item_instance(Item *i, const char *instance) {
3b63d2d3
LP
806 int r;
807
808 assert(i);
809
810 switch (i->type) {
811
812 case CREATE_FILE:
813 case TRUNCATE_FILE:
814 case CREATE_DIRECTORY:
ee17ee7c 815 case CREATE_FIFO:
468d726b
LP
816 case CREATE_SYMLINK:
817 case CREATE_BLOCK_DEVICE:
818 case CREATE_CHAR_DEVICE:
3b63d2d3 819 case IGNORE_PATH:
78a92a5a 820 case IGNORE_DIRECTORY_PATH:
777b87e7 821 case RELABEL_PATH:
a8d88783 822 case RECURSIVE_RELABEL_PATH:
31ed59c5 823 case WRITE_FILE:
3b63d2d3
LP
824 break;
825
826 case REMOVE_PATH:
b8bb3e8f
LP
827 if (remove(instance) < 0 && errno != ENOENT) {
828 log_error("remove(%s): %m", instance);
3b63d2d3 829 return -errno;
5008d581 830 }
3b63d2d3
LP
831
832 break;
833
834 case TRUNCATE_DIRECTORY:
835 case RECURSIVE_REMOVE_PATH:
d139b24a
LP
836 /* FIXME: we probably should use dir_cleanup() here
837 * instead of rm_rf() so that 'x' is honoured. */
f56d5db9 838 r = rm_rf_dangerous(instance, false, i->type == RECURSIVE_REMOVE_PATH, false);
468d726b 839 if (r < 0 && r != -ENOENT) {
b8bb3e8f 840 log_error("rm_rf(%s): %s", instance, strerror(-r));
3b63d2d3
LP
841 return r;
842 }
843
844 break;
845 }
846
847 return 0;
848}
849
a0896123 850static int remove_item(Item *i) {
99e68c0b
MS
851 int r = 0;
852
b8bb3e8f
LP
853 assert(i);
854
855 switch (i->type) {
856
857 case CREATE_FILE:
858 case TRUNCATE_FILE:
859 case CREATE_DIRECTORY:
ee17ee7c 860 case CREATE_FIFO:
468d726b
LP
861 case CREATE_SYMLINK:
862 case CREATE_CHAR_DEVICE:
863 case CREATE_BLOCK_DEVICE:
b8bb3e8f 864 case IGNORE_PATH:
78a92a5a 865 case IGNORE_DIRECTORY_PATH:
777b87e7 866 case RELABEL_PATH:
a8d88783 867 case RECURSIVE_RELABEL_PATH:
31ed59c5 868 case WRITE_FILE:
b8bb3e8f
LP
869 break;
870
871 case REMOVE_PATH:
872 case TRUNCATE_DIRECTORY:
99e68c0b
MS
873 case RECURSIVE_REMOVE_PATH:
874 r = glob_item(i, remove_item_instance);
875 break;
b8bb3e8f
LP
876 }
877
99e68c0b 878 return r;
b8bb3e8f
LP
879}
880
78a92a5a 881static int clean_item_instance(Item *i, const char* instance) {
19fbec19 882 DIR _cleanup_closedir_ *d = NULL;
78a92a5a
MS
883 struct stat s, ps;
884 bool mountpoint;
885 int r;
886 usec_t cutoff, n;
887
888 assert(i);
889
890 if (!i->age_set)
891 return 0;
892
893 n = now(CLOCK_REALTIME);
894 if (n < i->age)
895 return 0;
896
897 cutoff = n - i->age;
898
899 d = opendir(instance);
900 if (!d) {
901 if (errno == ENOENT || errno == ENOTDIR)
902 return 0;
903
904 log_error("Failed to open directory %s: %m", i->path);
905 return -errno;
906 }
907
908 if (fstat(dirfd(d), &s) < 0) {
909 log_error("stat(%s) failed: %m", i->path);
19fbec19 910 return -errno;
78a92a5a
MS
911 }
912
913 if (!S_ISDIR(s.st_mode)) {
914 log_error("%s is not a directory.", i->path);
19fbec19 915 return -ENOTDIR;
78a92a5a
MS
916 }
917
918 if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) {
919 log_error("stat(%s/..) failed: %m", i->path);
19fbec19 920 return -errno;
78a92a5a
MS
921 }
922
923 mountpoint = s.st_dev != ps.st_dev ||
924 (s.st_dev == ps.st_dev && s.st_ino == ps.st_ino);
925
19fbec19
ZJS
926 r = dir_cleanup(i, instance, d, &s, cutoff, s.st_dev, mountpoint,
927 MAX_DEPTH, i->keep_first_level);
78a92a5a
MS
928 return r;
929}
930
931static int clean_item(Item *i) {
932 int r = 0;
933
934 assert(i);
935
936 switch (i->type) {
937 case CREATE_DIRECTORY:
938 case TRUNCATE_DIRECTORY:
939 case IGNORE_PATH:
940 clean_item_instance(i, i->path);
941 break;
942 case IGNORE_DIRECTORY_PATH:
943 r = glob_item(i, clean_item_instance);
944 break;
945 default:
946 break;
947 }
948
949 return r;
950}
951
3b63d2d3
LP
952static int process_item(Item *i) {
953 int r, q, p;
954
955 assert(i);
956
957 r = arg_create ? create_item(i) : 0;
a0896123 958 q = arg_remove ? remove_item(i) : 0;
3b63d2d3
LP
959 p = arg_clean ? clean_item(i) : 0;
960
961 if (r < 0)
962 return r;
963
964 if (q < 0)
965 return q;
966
967 return p;
968}
969
970static void item_free(Item *i) {
971 assert(i);
972
973 free(i->path);
468d726b 974 free(i->argument);
3b63d2d3
LP
975 free(i);
976}
977
bfe95f35
LP
978static bool item_equal(Item *a, Item *b) {
979 assert(a);
980 assert(b);
981
982 if (!streq_ptr(a->path, b->path))
983 return false;
984
985 if (a->type != b->type)
986 return false;
987
988 if (a->uid_set != b->uid_set ||
989 (a->uid_set && a->uid != b->uid))
990 return false;
991
992 if (a->gid_set != b->gid_set ||
993 (a->gid_set && a->gid != b->gid))
994 return false;
995
996 if (a->mode_set != b->mode_set ||
997 (a->mode_set && a->mode != b->mode))
998 return false;
999
1000 if (a->age_set != b->age_set ||
1001 (a->age_set && a->age != b->age))
1002 return false;
1003
31ed59c5
LP
1004 if ((a->type == CREATE_FILE ||
1005 a->type == TRUNCATE_FILE ||
1006 a->type == WRITE_FILE ||
1007 a->type == CREATE_SYMLINK) &&
1733ca54 1008 !streq_ptr(a->argument, b->argument))
468d726b
LP
1009 return false;
1010
1011 if ((a->type == CREATE_CHAR_DEVICE ||
1012 a->type == CREATE_BLOCK_DEVICE) &&
1013 a->major_minor != b->major_minor)
1014 return false;
1015
bfe95f35
LP
1016 return true;
1017}
1018
fba6e687 1019static int parse_line(const char *fname, unsigned line, const char *buffer) {
7f2c1f4d
ZJS
1020 Item _cleanup_free_ *i = NULL;
1021 Item *existing;
19fbec19
ZJS
1022 char _cleanup_free_
1023 *mode = NULL, *user = NULL, *group = NULL, *age = NULL;
66ccd038 1024 char type;
bfe95f35 1025 Hashmap *h;
31ed59c5 1026 int r, n = -1;
3b63d2d3
LP
1027
1028 assert(fname);
1029 assert(line >= 1);
1030 assert(buffer);
1031
468d726b 1032 i = new0(Item, 1);
0d0f0c50
SL
1033 if (!i)
1034 return log_oom();
3b63d2d3 1035
19fbec19
ZJS
1036 r = sscanf(buffer,
1037 "%c %ms %ms %ms %ms %ms %n",
66ccd038 1038 &type,
bd40a2d8
LP
1039 &i->path,
1040 &mode,
1041 &user,
1042 &group,
468d726b 1043 &age,
19fbec19
ZJS
1044 &n);
1045 if (r < 2) {
3b63d2d3 1046 log_error("[%s:%u] Syntax error.", fname, line);
7f2c1f4d 1047 return -EIO;
5008d581
LP
1048 }
1049
31ed59c5
LP
1050 if (n >= 0) {
1051 n += strspn(buffer+n, WHITESPACE);
1052 if (buffer[n] != 0 && (buffer[n] != '-' || buffer[n+1] != 0)) {
1053 i->argument = unquote(buffer+n, "\"");
0d0f0c50
SL
1054 if (!i->argument)
1055 return log_oom();
31ed59c5
LP
1056 }
1057 }
1058
777b87e7 1059 switch(type) {
468d726b 1060
777b87e7
MS
1061 case CREATE_FILE:
1062 case TRUNCATE_FILE:
1063 case CREATE_DIRECTORY:
1064 case TRUNCATE_DIRECTORY:
1065 case CREATE_FIFO:
1066 case IGNORE_PATH:
78a92a5a 1067 case IGNORE_DIRECTORY_PATH:
777b87e7
MS
1068 case REMOVE_PATH:
1069 case RECURSIVE_REMOVE_PATH:
1070 case RELABEL_PATH:
1071 case RECURSIVE_RELABEL_PATH:
1072 break;
468d726b
LP
1073
1074 case CREATE_SYMLINK:
1075 if (!i->argument) {
1076 log_error("[%s:%u] Symlink file requires argument.", fname, line);
7f2c1f4d 1077 return -EBADMSG;
468d726b
LP
1078 }
1079 break;
1080
31ed59c5
LP
1081 case WRITE_FILE:
1082 if (!i->argument) {
1083 log_error("[%s:%u] Write file requires argument.", fname, line);
7f2c1f4d 1084 return -EBADMSG;
31ed59c5
LP
1085 }
1086 break;
1087
468d726b
LP
1088 case CREATE_CHAR_DEVICE:
1089 case CREATE_BLOCK_DEVICE: {
1090 unsigned major, minor;
1091
1092 if (!i->argument) {
1093 log_error("[%s:%u] Device file requires argument.", fname, line);
7f2c1f4d 1094 return -EBADMSG;
468d726b
LP
1095 }
1096
1097 if (sscanf(i->argument, "%u:%u", &major, &minor) != 2) {
1098 log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i->argument);
7f2c1f4d 1099 return -EBADMSG;
468d726b
LP
1100 }
1101
1102 i->major_minor = makedev(major, minor);
1103 break;
1104 }
1105
777b87e7 1106 default:
a8d88783 1107 log_error("[%s:%u] Unknown file type '%c'.", fname, line, type);
7f2c1f4d 1108 return -EBADMSG;
3b63d2d3 1109 }
468d726b 1110
a8d88783 1111 i->type = type;
3b63d2d3
LP
1112
1113 if (!path_is_absolute(i->path)) {
1114 log_error("[%s:%u] Path '%s' not absolute.", fname, line, i->path);
7f2c1f4d 1115 return -EBADMSG;
3b63d2d3
LP
1116 }
1117
1118 path_kill_slashes(i->path);
1119
7f2c1f4d
ZJS
1120 if (arg_prefix && !path_startswith(i->path, arg_prefix))
1121 return 0;
5008d581 1122
3b63d2d3 1123 if (user && !streq(user, "-")) {
4b67834e
LP
1124 const char *u = user;
1125
d05c5031 1126 r = get_user_creds(&u, &i->uid, NULL, NULL, NULL);
4b67834e 1127 if (r < 0) {
3b63d2d3 1128 log_error("[%s:%u] Unknown user '%s'.", fname, line, user);
7f2c1f4d 1129 return r;
3b63d2d3
LP
1130 }
1131
1132 i->uid_set = true;
1133 }
1134
1135 if (group && !streq(group, "-")) {
4b67834e
LP
1136 const char *g = group;
1137
1138 r = get_group_creds(&g, &i->gid);
1139 if (r < 0) {
3b63d2d3 1140 log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
7f2c1f4d 1141 return r;
3b63d2d3
LP
1142 }
1143
1144 i->gid_set = true;
1145 }
1146
1147 if (mode && !streq(mode, "-")) {
1148 unsigned m;
1149
1150 if (sscanf(mode, "%o", &m) != 1) {
1151 log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
7f2c1f4d 1152 return -ENOENT;
3b63d2d3
LP
1153 }
1154
1155 i->mode = m;
1156 i->mode_set = true;
1157 } else
468d726b
LP
1158 i->mode =
1159 i->type == CREATE_DIRECTORY ||
1160 i->type == TRUNCATE_DIRECTORY ? 0755 : 0644;
3b63d2d3
LP
1161
1162 if (age && !streq(age, "-")) {
24f3a374
LP
1163 const char *a = age;
1164
1165 if (*a == '~') {
1166 i->keep_first_level = true;
1167 a++;
1168 }
1169
7f602784 1170 if (parse_sec(a, &i->age) < 0) {
3b63d2d3 1171 log_error("[%s:%u] Invalid age '%s'.", fname, line, age);
7f2c1f4d 1172 return -EBADMSG;
3b63d2d3
LP
1173 }
1174
1175 i->age_set = true;
1176 }
1177
bfe95f35
LP
1178 h = needs_glob(i->type) ? globs : items;
1179
468d726b
LP
1180 existing = hashmap_get(h, i->path);
1181 if (existing) {
bfe95f35
LP
1182
1183 /* Two identical items are fine */
1184 if (!item_equal(existing, i))
022707d9 1185 log_warning("Two or more conflicting lines for %s configured, ignoring.", i->path);
022707d9 1186
7f2c1f4d 1187 return 0;
bfe95f35
LP
1188 }
1189
468d726b
LP
1190 r = hashmap_put(h, i->path, i);
1191 if (r < 0) {
3b63d2d3 1192 log_error("Failed to insert item %s: %s", i->path, strerror(-r));
7f2c1f4d 1193 return r;
3b63d2d3
LP
1194 }
1195
7f2c1f4d 1196 i = NULL; /* avoid cleanup */
5008d581 1197
7f2c1f4d 1198 return 0;
5008d581
LP
1199}
1200
3b63d2d3
LP
1201static int help(void) {
1202
522d4a49
LP
1203 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1204 "Creates, deletes and cleans up volatile and temporary files and directories.\n\n"
3b63d2d3
LP
1205 " -h --help Show this help\n"
1206 " --create Create marked files/directories\n"
1207 " --clean Clean up marked directories\n"
fba6e687 1208 " --remove Remove marked files/directories\n"
522d4a49 1209 " --prefix=PATH Only apply rules that apply to paths with the specified prefix\n",
3b63d2d3
LP
1210 program_invocation_short_name);
1211
1212 return 0;
1213}
1214
1215static int parse_argv(int argc, char *argv[]) {
1216
1217 enum {
1218 ARG_CREATE,
1219 ARG_CLEAN,
fba6e687
LP
1220 ARG_REMOVE,
1221 ARG_PREFIX
3b63d2d3
LP
1222 };
1223
1224 static const struct option options[] = {
1225 { "help", no_argument, NULL, 'h' },
1226 { "create", no_argument, NULL, ARG_CREATE },
1227 { "clean", no_argument, NULL, ARG_CLEAN },
1228 { "remove", no_argument, NULL, ARG_REMOVE },
fba6e687 1229 { "prefix", required_argument, NULL, ARG_PREFIX },
3b63d2d3
LP
1230 { NULL, 0, NULL, 0 }
1231 };
1232
1233 int c;
1234
1235 assert(argc >= 0);
1236 assert(argv);
1237
1238 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
1239
1240 switch (c) {
1241
1242 case 'h':
1243 help();
1244 return 0;
1245
1246 case ARG_CREATE:
1247 arg_create = true;
1248 break;
1249
1250 case ARG_CLEAN:
1251 arg_clean = true;
1252 break;
1253
1254 case ARG_REMOVE:
1255 arg_remove = true;
1256 break;
1257
fba6e687
LP
1258 case ARG_PREFIX:
1259 arg_prefix = optarg;
1260 break;
1261
3b63d2d3
LP
1262 case '?':
1263 return -EINVAL;
1264
1265 default:
1266 log_error("Unknown option code %c", c);
1267 return -EINVAL;
1268 }
1269 }
1270
1271 if (!arg_clean && !arg_create && !arg_remove) {
35b8ca3a 1272 log_error("You need to specify at least one of --clean, --create or --remove.");
3b63d2d3
LP
1273 return -EINVAL;
1274 }
1275
1276 return 1;
1277}
1278
fba6e687
LP
1279static int read_config_file(const char *fn, bool ignore_enoent) {
1280 FILE *f;
1281 unsigned v = 0;
fabe5c0e 1282 int r;
78a92a5a
MS
1283 Iterator iterator;
1284 Item *i;
fba6e687
LP
1285
1286 assert(fn);
1287
fabe5c0e
LP
1288 r = search_and_fopen_nulstr(fn, "re", conf_file_dirs, &f);
1289 if (r < 0) {
1290 if (ignore_enoent && r == -ENOENT)
fba6e687
LP
1291 return 0;
1292
fabe5c0e
LP
1293 log_error("Failed to open '%s', ignoring: %s", fn, strerror(-r));
1294 return r;
fba6e687
LP
1295 }
1296
772f8371 1297 log_debug("apply: %s\n", fn);
fba6e687
LP
1298 for (;;) {
1299 char line[LINE_MAX], *l;
1300 int k;
1301
1302 if (!(fgets(line, sizeof(line), f)))
1303 break;
1304
1305 v++;
1306
1307 l = strstrip(line);
1308 if (*l == '#' || *l == 0)
1309 continue;
1310
1311 if ((k = parse_line(fn, v, l)) < 0)
1312 if (r == 0)
1313 r = k;
1314 }
1315
78a92a5a
MS
1316 /* we have to determine age parameter for each entry of type X */
1317 HASHMAP_FOREACH(i, globs, iterator) {
1318 Iterator iter;
1319 Item *j, *candidate_item = NULL;
1320
1321 if (i->type != IGNORE_DIRECTORY_PATH)
1322 continue;
1323
1324 HASHMAP_FOREACH(j, items, iter) {
1325 if (j->type != CREATE_DIRECTORY && j->type != TRUNCATE_DIRECTORY)
1326 continue;
1327
1328 if (path_equal(j->path, i->path)) {
1329 candidate_item = j;
1330 break;
1331 }
1332
1333 if ((!candidate_item && path_startswith(i->path, j->path)) ||
1334 (candidate_item && path_startswith(j->path, candidate_item->path) && (fnmatch(i->path, j->path, FNM_PATHNAME | FNM_PERIOD) == 0)))
1335 candidate_item = j;
1336 }
1337
1338 if (candidate_item) {
1339 i->age = candidate_item->age;
1340 i->age_set = true;
1341 }
1342 }
1343
fba6e687
LP
1344 if (ferror(f)) {
1345 log_error("Failed to read from file %s: %m", fn);
1346 if (r == 0)
1347 r = -EIO;
1348 }
1349
1350 fclose(f);
1351
1352 return r;
1353}
1354
5008d581 1355int main(int argc, char *argv[]) {
fabe5c0e 1356 int r, k;
3b63d2d3
LP
1357 Item *i;
1358 Iterator iterator;
1359
fdcad0c2
LP
1360 r = parse_argv(argc, argv);
1361 if (r <= 0)
3b63d2d3 1362 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
5008d581 1363
eb0ca9eb 1364 log_set_target(LOG_TARGET_AUTO);
5008d581
LP
1365 log_parse_environment();
1366 log_open();
1367
4c12626c
LP
1368 umask(0022);
1369
e9a5ef7c 1370 label_init(NULL);
5008d581 1371
b8bb3e8f
LP
1372 items = hashmap_new(string_hash_func, string_compare_func);
1373 globs = hashmap_new(string_hash_func, string_compare_func);
1374
1375 if (!items || !globs) {
fabe5c0e 1376 r = log_oom();
3b63d2d3
LP
1377 goto finish;
1378 }
1379
fabe5c0e 1380 r = 0;
5008d581 1381
fba6e687
LP
1382 if (optind < argc) {
1383 int j;
5008d581 1384
9125670f 1385 for (j = optind; j < argc; j++) {
fabe5c0e
LP
1386 k = read_config_file(argv[j], false);
1387 if (k < 0 && r == 0)
1388 r = k;
9125670f 1389 }
5008d581 1390
fba6e687 1391 } else {
fabe5c0e
LP
1392 _cleanup_strv_free_ char **files = NULL;
1393 char **f;
5008d581 1394
fabe5c0e 1395 r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
44143309 1396 if (r < 0) {
44143309
KS
1397 log_error("Failed to enumerate tmpfiles.d files: %s", strerror(-r));
1398 goto finish;
1399 }
3b63d2d3 1400
772f8371 1401 STRV_FOREACH(f, files) {
fabe5c0e
LP
1402 k = read_config_file(*f, true);
1403 if (k < 0 && r == 0)
1404 r = k;
5008d581 1405 }
772f8371 1406 }
5008d581 1407
b8bb3e8f 1408 HASHMAP_FOREACH(i, globs, iterator)
21bdae12 1409 process_item(i);
b8bb3e8f 1410
3b63d2d3 1411 HASHMAP_FOREACH(i, items, iterator)
21bdae12 1412 process_item(i);
3b63d2d3 1413
5008d581 1414finish:
3b63d2d3
LP
1415 while ((i = hashmap_steal_first(items)))
1416 item_free(i);
1417
17b90525
LP
1418 while ((i = hashmap_steal_first(globs)))
1419 item_free(i);
1420
3b63d2d3 1421 hashmap_free(items);
b8bb3e8f 1422 hashmap_free(globs);
5008d581 1423
17b90525
LP
1424 set_free_free(unix_sockets);
1425
29003cff
LP
1426 label_finish();
1427
fabe5c0e 1428 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
5008d581 1429}