]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tmpfiles/tmpfiles.c
shared/acl-util: add mask only when needed, always add base ACLs
[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
3f93da98 7 Copyright 2015 Zbigniew Jędrzejewski-Szmek
5008d581
LP
8
9 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
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
5008d581
LP
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
5430f7f2 17 Lesser General Public License for more details.
5008d581 18
5430f7f2 19 You should have received a copy of the GNU Lesser General Public License
5008d581
LP
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21***/
22
23#include <unistd.h>
24#include <fcntl.h>
25#include <errno.h>
26#include <string.h>
5008d581
LP
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>
b8bb3e8f
LP
37#include <glob.h>
38#include <fnmatch.h>
505ef0e3
ZJS
39#include <sys/stat.h>
40#include <sys/types.h>
41#include <sys/param.h>
ebf4e801 42#include <sys/xattr.h>
5008d581
LP
43
44#include "log.h"
45#include "util.h"
54693d9b 46#include "macro.h"
d39efe74 47#include "missing.h"
49e942b2 48#include "mkdir.h"
9eb977db 49#include "path-util.h"
5008d581
LP
50#include "strv.h"
51#include "label.h"
3b63d2d3 52#include "set.h"
2c21044f 53#include "conf-files.h"
cb7ed9df 54#include "capability.h"
1731e34a 55#include "specifier.h"
eb9da376 56#include "build.h"
849958d1 57#include "copy.h"
d7b8eec7
LP
58#include "selinux-util.h"
59#include "btrfs-util.h"
f8eeeaf9 60#include "acl-util.h"
5008d581 61
01000479 62/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates
5008d581 63 * them in the file system. This is intended to be used to create
db019b8d
KS
64 * properly owned directories beneath /tmp, /var/tmp, /run, which are
65 * volatile and hence need to be recreated on bootup. */
5008d581 66
66ccd038 67typedef enum ItemType {
b8bb3e8f 68 /* These ones take file names */
3b63d2d3
LP
69 CREATE_FILE = 'f',
70 TRUNCATE_FILE = 'F',
71 CREATE_DIRECTORY = 'd',
72 TRUNCATE_DIRECTORY = 'D',
d7b8eec7 73 CREATE_SUBVOLUME = 'v',
ee17ee7c 74 CREATE_FIFO = 'p',
468d726b
LP
75 CREATE_SYMLINK = 'L',
76 CREATE_CHAR_DEVICE = 'c',
77 CREATE_BLOCK_DEVICE = 'b',
849958d1 78 COPY_FILES = 'C',
b8bb3e8f
LP
79
80 /* These ones take globs */
b705ab6a
ZJS
81 SET_XATTR = 't',
82 RECURSIVE_SET_XATTR = 'T',
83 SET_ACL = 'a',
84 RECURSIVE_SET_ACL = 'A',
cde684a2 85 WRITE_FILE = 'w',
3b63d2d3 86 IGNORE_PATH = 'x',
78a92a5a 87 IGNORE_DIRECTORY_PATH = 'X',
3b63d2d3 88 REMOVE_PATH = 'r',
a8d88783 89 RECURSIVE_REMOVE_PATH = 'R',
e73a03e0 90 ADJUST_MODE = 'm', /* legacy, 'z' is identical to this */
777b87e7 91 RELABEL_PATH = 'z',
e73a03e0 92 RECURSIVE_RELABEL_PATH = 'Z',
66ccd038 93} ItemType;
3b63d2d3
LP
94
95typedef struct Item {
66ccd038 96 ItemType type;
3b63d2d3
LP
97
98 char *path;
468d726b 99 char *argument;
ebf4e801 100 char **xattrs;
f8eeeaf9
ZJS
101#ifdef HAVE_ACL
102 acl_t acl_access;
103 acl_t acl_default;
104#endif
5008d581
LP
105 uid_t uid;
106 gid_t gid;
3b63d2d3
LP
107 mode_t mode;
108 usec_t age;
109
468d726b
LP
110 dev_t major_minor;
111
3b63d2d3
LP
112 bool uid_set:1;
113 bool gid_set:1;
114 bool mode_set:1;
115 bool age_set:1;
abef3f91 116 bool mask_perms:1;
24f3a374
LP
117
118 bool keep_first_level:1;
1910cd0e 119
2e78fa79
LP
120 bool force:1;
121
1910cd0e 122 bool done:1;
3b63d2d3
LP
123} Item;
124
3f93da98
ZJS
125typedef struct ItemArray {
126 Item *items;
127 size_t count;
128 size_t size;
129} ItemArray;
130
3b63d2d3
LP
131static bool arg_create = false;
132static bool arg_clean = false;
133static bool arg_remove = false;
81815651 134static bool arg_boot = false;
3b63d2d3 135
7bc040fa
LP
136static char **arg_include_prefixes = NULL;
137static char **arg_exclude_prefixes = NULL;
cf9a4abd 138static char *arg_root = NULL;
fba6e687 139
7f0a55d4 140static const char conf_file_dirs[] = CONF_DIRS_NULSTR("tmpfiles");
9125670f 141
3b63d2d3
LP
142#define MAX_DEPTH 256
143
7bc040fa
LP
144static Hashmap *items = NULL, *globs = NULL;
145static Set *unix_sockets = NULL;
146
66ccd038 147static bool needs_glob(ItemType t) {
cde684a2
LP
148 return IN_SET(t,
149 WRITE_FILE,
150 IGNORE_PATH,
151 IGNORE_DIRECTORY_PATH,
152 REMOVE_PATH,
153 RECURSIVE_REMOVE_PATH,
e73a03e0 154 ADJUST_MODE,
cde684a2 155 RELABEL_PATH,
b705ab6a
ZJS
156 RECURSIVE_RELABEL_PATH,
157 SET_XATTR,
158 RECURSIVE_SET_XATTR,
159 SET_ACL,
160 RECURSIVE_SET_ACL);
b8bb3e8f
LP
161}
162
3f93da98
ZJS
163static bool takes_ownership(ItemType t) {
164 return IN_SET(t,
165 CREATE_FILE,
166 TRUNCATE_FILE,
167 CREATE_DIRECTORY,
168 TRUNCATE_DIRECTORY,
169 CREATE_SUBVOLUME,
170 CREATE_FIFO,
171 CREATE_SYMLINK,
172 CREATE_CHAR_DEVICE,
173 CREATE_BLOCK_DEVICE,
174 COPY_FILES,
175
176 WRITE_FILE,
177 IGNORE_PATH,
178 IGNORE_DIRECTORY_PATH,
179 REMOVE_PATH,
180 RECURSIVE_REMOVE_PATH);
181}
182
b8bb3e8f 183static struct Item* find_glob(Hashmap *h, const char *match) {
3f93da98 184 ItemArray *j;
b8bb3e8f
LP
185 Iterator i;
186
3f93da98
ZJS
187 HASHMAP_FOREACH(j, h, i) {
188 unsigned n;
189
190 for (n = 0; n < j->count; n++) {
191 Item *item = j->items + n;
192
193 if (fnmatch(item->path, match, FNM_PATHNAME|FNM_PERIOD) == 0)
194 return item;
195 }
196 }
b8bb3e8f
LP
197
198 return NULL;
199}
200
17b90525 201static void load_unix_sockets(void) {
7fd1b19b 202 _cleanup_fclose_ FILE *f = NULL;
17b90525
LP
203 char line[LINE_MAX];
204
205 if (unix_sockets)
206 return;
207
208 /* We maintain a cache of the sockets we found in
209 * /proc/net/unix to speed things up a little. */
210
d5099efc 211 unix_sockets = set_new(&string_hash_ops);
fdcad0c2 212 if (!unix_sockets)
17b90525
LP
213 return;
214
fdcad0c2
LP
215 f = fopen("/proc/net/unix", "re");
216 if (!f)
17b90525
LP
217 return;
218
fdcad0c2
LP
219 /* Skip header */
220 if (!fgets(line, sizeof(line), f))
17b90525
LP
221 goto fail;
222
223 for (;;) {
224 char *p, *s;
225 int k;
226
fdcad0c2 227 if (!fgets(line, sizeof(line), f))
17b90525
LP
228 break;
229
230 truncate_nl(line);
231
fdcad0c2
LP
232 p = strchr(line, ':');
233 if (!p)
234 continue;
235
236 if (strlen(p) < 37)
17b90525
LP
237 continue;
238
fdcad0c2 239 p += 37;
17b90525 240 p += strspn(p, WHITESPACE);
fdcad0c2 241 p += strcspn(p, WHITESPACE); /* skip one more word */
17b90525
LP
242 p += strspn(p, WHITESPACE);
243
244 if (*p != '/')
245 continue;
246
fdcad0c2
LP
247 s = strdup(p);
248 if (!s)
17b90525
LP
249 goto fail;
250
4ff21d85
LP
251 path_kill_slashes(s);
252
ef42202a
ZJS
253 k = set_consume(unix_sockets, s);
254 if (k < 0 && k != -EEXIST)
255 goto fail;
17b90525
LP
256 }
257
258 return;
259
260fail:
261 set_free_free(unix_sockets);
262 unix_sockets = NULL;
17b90525
LP
263}
264
265static bool unix_socket_alive(const char *fn) {
266 assert(fn);
267
268 load_unix_sockets();
269
270 if (unix_sockets)
271 return !!set_get(unix_sockets, (char*) fn);
272
273 /* We don't know, so assume yes */
274 return true;
275}
276
99d680ac 277static int dir_is_mount_point(DIR *d, const char *subdir) {
cde684a2 278
2695c5c4 279 union file_handle_union h = FILE_HANDLE_INIT;
99d680ac
KS
280 int mount_id_parent, mount_id;
281 int r_p, r;
282
370c860f 283 r_p = name_to_handle_at(dirfd(d), ".", &h.handle, &mount_id_parent, 0);
99d680ac
KS
284 if (r_p < 0)
285 r_p = -errno;
286
370c860f
DR
287 h.handle.handle_bytes = MAX_HANDLE_SZ;
288 r = name_to_handle_at(dirfd(d), subdir, &h.handle, &mount_id, 0);
99d680ac
KS
289 if (r < 0)
290 r = -errno;
291
292 /* got no handle; make no assumptions, return error */
293 if (r_p < 0 && r < 0)
294 return r_p;
295
296 /* got both handles; if they differ, it is a mount point */
297 if (r_p >= 0 && r >= 0)
298 return mount_id_parent != mount_id;
299
300 /* got only one handle; assume different mount points if one
301 * of both queries was not supported by the filesystem */
e7aab541 302 if (r_p == -ENOSYS || r_p == -EOPNOTSUPP || r == -ENOSYS || r == -EOPNOTSUPP)
99d680ac
KS
303 return true;
304
305 /* return error */
306 if (r_p < 0)
307 return r_p;
308 return r;
309}
310
3b63d2d3 311static int dir_cleanup(
78a92a5a 312 Item *i,
3b63d2d3
LP
313 const char *p,
314 DIR *d,
315 const struct stat *ds,
316 usec_t cutoff,
317 dev_t rootdev,
318 bool mountpoint,
24f3a374 319 int maxdepth,
265ffa1e
LP
320 bool keep_this_level) {
321
3b63d2d3
LP
322 struct dirent *dent;
323 struct timespec times[2];
324 bool deleted = false;
3b63d2d3
LP
325 int r = 0;
326
327 while ((dent = readdir(d))) {
328 struct stat s;
329 usec_t age;
7fd1b19b 330 _cleanup_free_ char *sub_path = NULL;
3b63d2d3
LP
331
332 if (streq(dent->d_name, ".") ||
333 streq(dent->d_name, ".."))
334 continue;
5008d581 335
3b63d2d3 336 if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) {
ca2f4176
KS
337 if (errno == ENOENT)
338 continue;
5008d581 339
ca2f4176
KS
340 /* FUSE, NFS mounts, SELinux might return EACCES */
341 if (errno == EACCES)
56f64d95 342 log_debug_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name);
ca2f4176 343 else
56f64d95 344 log_error_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name);
ca2f4176 345 r = -errno;
3b63d2d3
LP
346 continue;
347 }
348
349 /* Stay on the same filesystem */
350 if (s.st_dev != rootdev)
351 continue;
352
99d680ac
KS
353 /* Try to detect bind mounts of the same filesystem instance; they
354 * do not differ in device major/minors. This type of query is not
355 * supported on all kernels or filesystem types though. */
356 if (S_ISDIR(s.st_mode) && dir_is_mount_point(d, dent->d_name) > 0)
357 continue;
358
3b63d2d3
LP
359 /* Do not delete read-only files owned by root */
360 if (s.st_uid == 0 && !(s.st_mode & S_IWUSR))
361 continue;
362
cde684a2
LP
363 sub_path = strjoin(p, "/", dent->d_name, NULL);
364 if (!sub_path) {
0d0f0c50 365 r = log_oom();
3b63d2d3
LP
366 goto finish;
367 }
368
369 /* Is there an item configured for this path? */
370 if (hashmap_get(items, sub_path))
371 continue;
372
b8bb3e8f
LP
373 if (find_glob(globs, sub_path))
374 continue;
375
3b63d2d3
LP
376 if (S_ISDIR(s.st_mode)) {
377
378 if (mountpoint &&
379 streq(dent->d_name, "lost+found") &&
380 s.st_uid == 0)
381 continue;
382
383 if (maxdepth <= 0)
384 log_warning("Reached max depth on %s.", sub_path);
385 else {
7fd1b19b 386 _cleanup_closedir_ DIR *sub_dir;
3b63d2d3
LP
387 int q;
388
e5f3d1ba 389 sub_dir = xopendirat(dirfd(d), dent->d_name, O_NOFOLLOW|O_NOATIME);
cde684a2 390 if (!sub_dir) {
3b63d2d3 391 if (errno != ENOENT) {
56f64d95 392 log_error_errno(errno, "opendir(%s/%s) failed: %m", p, dent->d_name);
3b63d2d3
LP
393 r = -errno;
394 }
395
396 continue;
397 }
398
78a92a5a 399 q = dir_cleanup(i, sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1, false);
3b63d2d3
LP
400 if (q < 0)
401 r = q;
402 }
403
24f3a374
LP
404 /* Note: if you are wondering why we don't
405 * support the sticky bit for excluding
406 * directories from cleaning like we do it for
407 * other file system objects: well, the sticky
408 * bit already has a meaning for directories,
409 * so we don't want to overload that. */
410
411 if (keep_this_level)
412 continue;
413
3b63d2d3
LP
414 /* Ignore ctime, we change it when deleting */
415 age = MAX(timespec_load(&s.st_mtim),
416 timespec_load(&s.st_atim));
417 if (age >= cutoff)
418 continue;
419
a6187d4c 420 if (i->type != IGNORE_DIRECTORY_PATH || !streq(dent->d_name, p)) {
9f6445e3 421 log_debug("rmdir '%s'", sub_path);
3b63d2d3 422
78a92a5a
MS
423 if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0) {
424 if (errno != ENOENT && errno != ENOTEMPTY) {
56f64d95 425 log_error_errno(errno, "rmdir(%s): %m", sub_path);
78a92a5a
MS
426 r = -errno;
427 }
3b63d2d3
LP
428 }
429 }
430
431 } else {
9c73736d
LP
432 /* Skip files for which the sticky bit is
433 * set. These are semantics we define, and are
434 * unknown elsewhere. See XDG_RUNTIME_DIR
435 * specification for details. */
436 if (s.st_mode & S_ISVTX)
437 continue;
438
17b90525 439 if (mountpoint && S_ISREG(s.st_mode)) {
3b63d2d3
LP
440 if (streq(dent->d_name, ".journal") &&
441 s.st_uid == 0)
442 continue;
443
444 if (streq(dent->d_name, "aquota.user") ||
445 streq(dent->d_name, "aquota.group"))
446 continue;
447 }
448
17b90525
LP
449 /* Ignore sockets that are listed in /proc/net/unix */
450 if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path))
451 continue;
452
78ab08eb
LP
453 /* Ignore device nodes */
454 if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode))
455 continue;
456
24f3a374
LP
457 /* Keep files on this level around if this is
458 * requested */
459 if (keep_this_level)
460 continue;
461
3b63d2d3
LP
462 age = MAX3(timespec_load(&s.st_mtim),
463 timespec_load(&s.st_atim),
464 timespec_load(&s.st_ctim));
465
466 if (age >= cutoff)
467 continue;
468
9f6445e3 469 log_debug("unlink '%s'", sub_path);
3b63d2d3
LP
470
471 if (unlinkat(dirfd(d), dent->d_name, 0) < 0) {
472 if (errno != ENOENT) {
56f64d95 473 log_error_errno(errno, "unlink(%s): %m", sub_path);
3b63d2d3
LP
474 r = -errno;
475 }
476 }
477
478 deleted = true;
479 }
480 }
481
482finish:
483 if (deleted) {
484 /* Restore original directory timestamps */
485 times[0] = ds->st_atim;
486 times[1] = ds->st_mtim;
487
488 if (futimens(dirfd(d), times) < 0)
56f64d95 489 log_error_errno(errno, "utimensat(%s): %m", p);
3b63d2d3
LP
490 }
491
3b63d2d3
LP
492 return r;
493}
494
b705ab6a 495static int path_set_perms(Item *i, const char *path) {
1924a97d
MO
496 struct stat st;
497 bool st_valid;
498
cde684a2
LP
499 assert(i);
500 assert(path);
501
1924a97d
MO
502 st_valid = stat(path, &st) == 0;
503
062e01bb 504 /* not using i->path directly because it may be a glob */
abef3f91
LP
505 if (i->mode_set) {
506 mode_t m = i->mode;
507
1924a97d
MO
508 if (i->mask_perms && st_valid) {
509 if (!(st.st_mode & 0111))
510 m &= ~0111;
511 if (!(st.st_mode & 0222))
512 m &= ~0222;
513 if (!(st.st_mode & 0444))
514 m &= ~0444;
515 if (!S_ISDIR(st.st_mode))
516 m &= ~07000; /* remove sticky/sgid/suid bit, unless directory */
abef3f91
LP
517 }
518
1924a97d 519 if (!st_valid || m != (st.st_mode & 07777)) {
4a62c710
MS
520 if (chmod(path, m) < 0)
521 return log_error_errno(errno, "chmod(%s) failed: %m", path);
062e01bb 522 }
abef3f91 523 }
062e01bb 524
1924a97d
MO
525 if ((!st_valid || (i->uid != st.st_uid || i->gid != st.st_gid)) &&
526 (i->uid_set || i->gid_set))
062e01bb 527 if (chown(path,
fed1e721 528 i->uid_set ? i->uid : UID_INVALID,
505ef0e3 529 i->gid_set ? i->gid : GID_INVALID) < 0)
062e01bb 530
505ef0e3 531 return log_error_errno(errno, "chown(%s) failed: %m", path);
062e01bb 532
9855d6c7 533 return label_fix(path, false, false);
062e01bb
MS
534}
535
ebf4e801
MW
536static int get_xattrs_from_arg(Item *i) {
537 char *xattr;
538 const char *p;
539 int r;
540
541 assert(i);
505ef0e3 542 assert(i->argument);
ebf4e801 543
ebf4e801
MW
544 p = i->argument;
545
546 while ((r = unquote_first_word(&p, &xattr, false)) > 0) {
505ef0e3
ZJS
547 _cleanup_free_ char *tmp = NULL, *name = NULL,
548 *value = NULL, *value2 = NULL, *_xattr = xattr;
549
ebf4e801
MW
550 r = split_pair(xattr, "=", &name, &value);
551 if (r < 0) {
552 log_warning("Illegal xattr found: \"%s\" - ignoring.", xattr);
ebf4e801
MW
553 continue;
554 }
505ef0e3
ZJS
555
556 if (strempty(name) || strempty(value)) {
557 log_warning("Malformed xattr found: \"%s\" - ignoring.", xattr);
ebf4e801
MW
558 continue;
559 }
505ef0e3 560
ebf4e801
MW
561 tmp = unquote(value, "\"");
562 if (!tmp)
563 return log_oom();
505ef0e3
ZJS
564
565 value2 = cunescape(tmp);
566 if (!value2)
ebf4e801 567 return log_oom();
505ef0e3
ZJS
568
569 if (strv_push_pair(&i->xattrs, name, value2) < 0)
ebf4e801 570 return log_oom();
505ef0e3 571 name = value2 = NULL;
ebf4e801
MW
572 }
573
574 return r;
575}
576
b705ab6a 577static int path_set_xattrs(Item *i, const char *path) {
ebf4e801
MW
578 char **name, **value;
579
580 assert(i);
581 assert(path);
582
ebf4e801
MW
583 STRV_FOREACH_PAIR(name, value, i->xattrs) {
584 int n;
505ef0e3 585
ebf4e801
MW
586 n = strlen(*value);
587 if (lsetxattr(path, *name, *value, n, 0) < 0) {
505ef0e3
ZJS
588 log_error("Setting extended attribute %s=%s on %s failed: %m",
589 *name, *value, path);
ebf4e801
MW
590 return -errno;
591 }
592 }
593 return 0;
594}
595
f8eeeaf9
ZJS
596static int get_acls_from_arg(Item *item) {
597#ifdef HAVE_ACL
598 int r;
599 _cleanup_(acl_freep) acl_t a = NULL, d = NULL;
600
601 assert(item);
602
50d9e46d
ZJS
603 /* If force (= modify) is set, we will not modify the acl
604 * afterwards, so the mask can be added now if necessary. */
605 r = parse_acl(item->argument, &item->acl_access, &item->acl_default, !item->force);
f8eeeaf9
ZJS
606 if (r < 0)
607 log_warning_errno(errno, "Failed to parse ACL \"%s\": %m. Ignoring",
608 item->argument);
609#else
610 log_warning_errno(ENOSYS, "ACLs are not supported. Ignoring");
611#endif
612
613 return 0;
614}
615
50d9e46d 616static int path_set_acl(const char *path, acl_type_t type, acl_t acl, bool modify) {
dd4105b0 617 _cleanup_(acl_freep) acl_t dup = NULL;
50d9e46d
ZJS
618 int r;
619
620 if (modify) {
dd4105b0 621 r = acls_for_file(path, type, acl, &dup);
50d9e46d
ZJS
622 if (r < 0)
623 return r;
50d9e46d 624
dd4105b0
ZJS
625 r = calc_acl_mask_if_needed(&dup);
626 if (r < 0)
627 return r;
628 } else {
629 dup = acl_dup(acl);
630 if (!dup)
631 return -errno;
632
633 /* the mask was already added earlier if needed */
634 }
635
636 r = add_base_acls_if_needed(&dup, path);
637 if (r < 0)
638 return r;
639
640 r = acl_set_file(path, type, dup);
50d9e46d
ZJS
641 if (r < 0) {
642 _cleanup_(acl_free_charpp) char *t;
643
644 r = -errno;
dd4105b0 645 t = acl_to_any_text(dup, NULL, ',', TEXT_ABBREVIATE);
50d9e46d
ZJS
646 log_error_errno(r,
647 "Setting %s ACL \"%s\" on %s failed: %m",
648 type == ACL_TYPE_ACCESS ? "access" : "default",
649 strna(t), path);
650 }
651
652 return r;
653}
654
b705ab6a 655static int path_set_acls(Item *item, const char *path) {
f8eeeaf9
ZJS
656#ifdef HAVE_ACL
657 int r;
658
659 assert(item);
660 assert(path);
661
662 if (item->acl_access) {
50d9e46d
ZJS
663 r = path_set_acl(path, ACL_TYPE_ACCESS, item->acl_access, item->force);
664 if (r < 0)
665 return r;
f8eeeaf9
ZJS
666 }
667
668 if (item->acl_default) {
50d9e46d
ZJS
669 r = path_set_acl(path, ACL_TYPE_DEFAULT, item->acl_default, item->force);
670 if (r < 0)
671 return r;
f8eeeaf9
ZJS
672 }
673#endif
674
675 return 0;
676}
677
d4e9eb91 678static int write_one_file(Item *i, const char *path) {
43ad6e31
LP
679 _cleanup_close_ int fd = -1;
680 int flags, r = 0;
d4e9eb91 681 struct stat st;
d4e9eb91 682
874f1947
LP
683 assert(i);
684 assert(path);
685
43ad6e31
LP
686 flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND|O_NOFOLLOW :
687 i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC|O_NOFOLLOW : 0;
d4e9eb91 688
43ad6e31 689 RUN_WITH_UMASK(0000) {
ecabcf8b 690 mac_selinux_create_file_prepare(path, S_IFREG);
43ad6e31 691 fd = open(path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode);
ecabcf8b 692 mac_selinux_create_file_clear();
5c0d398d 693 }
d4e9eb91
DR
694
695 if (fd < 0) {
696 if (i->type == WRITE_FILE && errno == ENOENT)
697 return 0;
698
56f64d95 699 log_error_errno(errno, "Failed to create file %s: %m", path);
d4e9eb91
DR
700 return -errno;
701 }
702
703 if (i->argument) {
cde684a2 704 _cleanup_free_ char *unescaped;
d4e9eb91
DR
705 ssize_t n;
706 size_t l;
d4e9eb91 707
54693d9b 708 unescaped = cunescape(i->argument);
43ad6e31 709 if (!unescaped)
54693d9b 710 return log_oom();
d4e9eb91 711
54693d9b
DR
712 l = strlen(unescaped);
713 n = write(fd, unescaped, l);
d4e9eb91 714
d4e9eb91
DR
715 if (n < 0 || (size_t) n < l) {
716 log_error("Failed to write file %s: %s", path, n < 0 ? strerror(-n) : "Short write");
d4e9eb91
DR
717 return n < 0 ? n : -EIO;
718 }
719 }
720
43ad6e31 721 fd = safe_close(fd);
3612fbc1 722
4a62c710
MS
723 if (stat(path, &st) < 0)
724 return log_error_errno(errno, "stat(%s) failed: %m", path);
d4e9eb91
DR
725
726 if (!S_ISREG(st.st_mode)) {
727 log_error("%s is not a file.", path);
728 return -EEXIST;
729 }
730
b705ab6a 731 r = path_set_perms(i, path);
d4e9eb91
DR
732 if (r < 0)
733 return r;
734
735 return 0;
736}
737
081043cf
ZJS
738typedef int (*action_t)(Item *, const char *);
739
740static int item_do_children(Item *i, const char *path, action_t action) {
7fd1b19b 741 _cleanup_closedir_ DIR *d;
e73a03e0
LP
742 int r = 0;
743
744 assert(i);
745 assert(path);
a8d88783
MS
746
747 /* This returns the first error we run into, but nevertheless
748 * tries to go on */
749
750 d = opendir(path);
751 if (!d)
e73a03e0 752 return errno == ENOENT || errno == ENOTDIR ? 0 : -errno;
a8d88783
MS
753
754 for (;;) {
e73a03e0 755 _cleanup_free_ char *p = NULL;
7d5e9c0f 756 struct dirent *de;
e73a03e0 757 int q;
a8d88783 758
d78096b3
FW
759 errno = 0;
760 de = readdir(d);
e73a03e0
LP
761 if (!de) {
762 if (errno != 0 && r == 0)
763 r = -errno;
a8d88783 764
a8d88783 765 break;
e73a03e0 766 }
a8d88783
MS
767
768 if (streq(de->d_name, ".") || streq(de->d_name, ".."))
769 continue;
770
e73a03e0
LP
771 p = strjoin(path, "/", de->d_name, NULL);
772 if (!p)
773 return -ENOMEM;
a8d88783 774
081043cf 775 q = action(i, p);
e73a03e0
LP
776 if (q < 0 && q != -ENOENT && r == 0)
777 r = q;
a8d88783 778
e73a03e0 779 if (IN_SET(de->d_type, DT_UNKNOWN, DT_DIR)) {
081043cf 780 q = item_do_children(i, p, action);
e73a03e0
LP
781 if (q < 0 && r == 0)
782 r = q;
a8d88783 783 }
a8d88783
MS
784 }
785
e73a03e0 786 return r;
a8d88783
MS
787}
788
081043cf 789static int glob_item(Item *i, action_t action, bool recursive) {
7fd1b19b 790 _cleanup_globfree_ glob_t g = {};
e73a03e0 791 int r = 0, k;
99e68c0b
MS
792 char **fn;
793
99e68c0b 794 errno = 0;
c84a9488 795 k = glob(i->path, GLOB_NOSORT|GLOB_BRACE, NULL, &g);
081043cf
ZJS
796 if (k != 0 && k != GLOB_NOMATCH)
797 return log_error_errno(errno ?: EIO, "glob(%s) failed: %m", i->path);
99e68c0b 798
c84a9488
ZJS
799 STRV_FOREACH(fn, g.gl_pathv) {
800 k = action(i, *fn);
e73a03e0 801 if (k < 0 && r == 0)
99e68c0b 802 r = k;
081043cf
ZJS
803
804 if (recursive) {
805 k = item_do_children(i, *fn, action);
806 if (k < 0 && r == 0)
807 r = k;
808 }
c84a9488 809 }
99e68c0b 810
99e68c0b
MS
811 return r;
812}
813
3b63d2d3 814static int create_item(Item *i) {
3b63d2d3 815 struct stat st;
df28bc08 816 int r = 0;
5008d581 817
3b63d2d3 818 assert(i);
5008d581 819
3b63d2d3
LP
820 switch (i->type) {
821
822 case IGNORE_PATH:
78a92a5a 823 case IGNORE_DIRECTORY_PATH:
3b63d2d3
LP
824 case REMOVE_PATH:
825 case RECURSIVE_REMOVE_PATH:
826 return 0;
5008d581 827
3b63d2d3 828 case CREATE_FILE:
31ed59c5 829 case TRUNCATE_FILE:
1845fdd9
DR
830 r = write_one_file(i, i->path);
831 if (r < 0)
832 return r;
833 break;
265ffa1e 834
849958d1 835 case COPY_FILES:
e156347e 836 r = copy_tree(i->argument, i->path, false);
849958d1 837 if (r < 0) {
e156347e
LP
838 struct stat a, b;
839
8d3d7072
MS
840 if (r != -EEXIST)
841 return log_error_errno(r, "Failed to copy files to %s: %m", i->path);
e156347e 842
4a62c710
MS
843 if (stat(i->argument, &a) < 0)
844 return log_error_errno(errno, "stat(%s) failed: %m", i->argument);
e156347e 845
4a62c710
MS
846 if (stat(i->path, &b) < 0)
847 return log_error_errno(errno, "stat(%s) failed: %m", i->path);
e156347e
LP
848
849 if ((a.st_mode ^ b.st_mode) & S_IFMT) {
850 log_debug("Can't copy to %s, file exists already and is of different type", i->path);
851 return 0;
852 }
849958d1
LP
853 }
854
b705ab6a 855 r = path_set_perms(i, i->path);
849958d1
LP
856 if (r < 0)
857 return r;
858
859 break;
860
d4e9eb91 861 case WRITE_FILE:
081043cf 862 r = glob_item(i, write_one_file, false);
f05bc3f7
MS
863 if (r < 0)
864 return r;
5008d581 865
3b63d2d3
LP
866 break;
867
3b63d2d3 868 case CREATE_DIRECTORY:
d7b8eec7
LP
869 case TRUNCATE_DIRECTORY:
870 case CREATE_SUBVOLUME:
5008d581 871
d7b8eec7 872 RUN_WITH_UMASK(0000)
5c0d398d 873 mkdir_parents_label(i->path, 0755);
d7b8eec7
LP
874
875 if (i->type == CREATE_SUBVOLUME) {
876 RUN_WITH_UMASK((~i->mode) & 0777)
877 r = btrfs_subvol_make(i->path);
878 } else
879 r = 0;
880
505ef0e3 881 if (IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY) || r == -ENOTTY) {
d7b8eec7
LP
882 RUN_WITH_UMASK(0000)
883 r = mkdir_label(i->path, i->mode);
5c0d398d 884 }
5008d581 885
e156347e 886 if (r < 0) {
f647962d 887 if (r != -EEXIST)
d7b8eec7 888 return log_error_errno(r, "Failed to create directory or subvolume %s: %m", i->path);
5008d581 889
4a62c710
MS
890 if (stat(i->path, &st) < 0)
891 return log_error_errno(errno, "stat(%s) failed: %m", i->path);
5008d581 892
e156347e
LP
893 if (!S_ISDIR(st.st_mode)) {
894 log_debug("%s already exists and is not a directory.", i->path);
895 return 0;
896 }
5008d581
LP
897 }
898
b705ab6a 899 r = path_set_perms(i, i->path);
f05bc3f7
MS
900 if (r < 0)
901 return r;
3b63d2d3
LP
902
903 break;
ee17ee7c
LP
904
905 case CREATE_FIFO:
906
5c0d398d 907 RUN_WITH_UMASK(0000) {
ecabcf8b 908 mac_selinux_create_file_prepare(i->path, S_IFIFO);
5c0d398d 909 r = mkfifo(i->path, i->mode);
ecabcf8b 910 mac_selinux_create_file_clear();
5c0d398d 911 }
ee17ee7c 912
1554afae 913 if (r < 0) {
4a62c710
MS
914 if (errno != EEXIST)
915 return log_error_errno(errno, "Failed to create fifo %s: %m", i->path);
ee17ee7c 916
4a62c710
MS
917 if (stat(i->path, &st) < 0)
918 return log_error_errno(errno, "stat(%s) failed: %m", i->path);
ee17ee7c 919
1554afae
LP
920 if (!S_ISFIFO(st.st_mode)) {
921
922 if (i->force) {
923
924 RUN_WITH_UMASK(0000) {
ecabcf8b 925 mac_selinux_create_file_prepare(i->path, S_IFIFO);
1554afae 926 r = mkfifo_atomic(i->path, i->mode);
ecabcf8b 927 mac_selinux_create_file_clear();
1554afae
LP
928 }
929
f647962d
MS
930 if (r < 0)
931 return log_error_errno(r, "Failed to create fifo %s: %m", i->path);
1554afae
LP
932 } else {
933 log_debug("%s is not a fifo.", i->path);
934 return 0;
935 }
936 }
ee17ee7c
LP
937 }
938
b705ab6a 939 r = path_set_perms(i, i->path);
f05bc3f7
MS
940 if (r < 0)
941 return r;
ee17ee7c
LP
942
943 break;
a8d88783 944
2e78fa79 945 case CREATE_SYMLINK:
468d726b 946
ecabcf8b 947 mac_selinux_create_file_prepare(i->path, S_IFLNK);
468d726b 948 r = symlink(i->argument, i->path);
ecabcf8b 949 mac_selinux_create_file_clear();
e9a5ef7c 950
468d726b 951 if (r < 0) {
2e78fa79 952 _cleanup_free_ char *x = NULL;
468d726b 953
4a62c710
MS
954 if (errno != EEXIST)
955 return log_error_errno(errno, "symlink(%s, %s) failed: %m", i->argument, i->path);
2e78fa79
LP
956
957 r = readlink_malloc(i->path, &x);
958 if (r < 0 || !streq(i->argument, x)) {
959
960 if (i->force) {
ecabcf8b 961 mac_selinux_create_file_prepare(i->path, S_IFLNK);
2e78fa79 962 r = symlink_atomic(i->argument, i->path);
ecabcf8b 963 mac_selinux_create_file_clear();
2e78fa79 964
f647962d
MS
965 if (r < 0)
966 return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path);
1554afae 967 } else {
2e78fa79 968 log_debug("%s is not a symlink or does not point to the correct path.", i->path);
1554afae
LP
969 return 0;
970 }
2e78fa79 971 }
468d726b
LP
972 }
973
468d726b 974 break;
468d726b
LP
975
976 case CREATE_BLOCK_DEVICE:
977 case CREATE_CHAR_DEVICE: {
cb7ed9df
LP
978 mode_t file_type;
979
980 if (have_effective_cap(CAP_MKNOD) == 0) {
981 /* In a container we lack CAP_MKNOD. We
ab06eef8 982 shouldn't attempt to create the device node in
cb7ed9df
LP
983 that case to avoid noise, and we don't support
984 virtualized devices in containers anyway. */
985
986 log_debug("We lack CAP_MKNOD, skipping creation of device node %s.", i->path);
987 return 0;
988 }
989
1554afae 990 file_type = i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR;
468d726b 991
5c0d398d 992 RUN_WITH_UMASK(0000) {
ecabcf8b 993 mac_selinux_create_file_prepare(i->path, file_type);
5c0d398d 994 r = mknod(i->path, i->mode | file_type, i->major_minor);
ecabcf8b 995 mac_selinux_create_file_clear();
5c0d398d 996 }
468d726b 997
6555ad8e
KS
998 if (r < 0) {
999 if (errno == EPERM) {
1000 log_debug("We lack permissions, possibly because of cgroup configuration; "
1001 "skipping creation of device node %s.", i->path);
1002 return 0;
1003 }
1004
4a62c710
MS
1005 if (errno != EEXIST)
1006 return log_error_errno(errno, "Failed to create device node %s: %m", i->path);
468d726b 1007
4a62c710
MS
1008 if (stat(i->path, &st) < 0)
1009 return log_error_errno(errno, "stat(%s) failed: %m", i->path);
468d726b 1010
1554afae
LP
1011 if ((st.st_mode & S_IFMT) != file_type) {
1012
1013 if (i->force) {
1014
1015 RUN_WITH_UMASK(0000) {
ecabcf8b 1016 mac_selinux_create_file_prepare(i->path, file_type);
1554afae 1017 r = mknod_atomic(i->path, i->mode | file_type, i->major_minor);
ecabcf8b 1018 mac_selinux_create_file_clear();
1554afae
LP
1019 }
1020
f647962d
MS
1021 if (r < 0)
1022 return log_error_errno(r, "Failed to create device node %s: %m", i->path);
1554afae
LP
1023 } else {
1024 log_debug("%s is not a device node.", i->path);
1025 return 0;
1026 }
1027 }
468d726b
LP
1028 }
1029
b705ab6a 1030 r = path_set_perms(i, i->path);
468d726b
LP
1031 if (r < 0)
1032 return r;
1033
1034 break;
1035 }
1036
e73a03e0 1037 case ADJUST_MODE:
777b87e7 1038 case RELABEL_PATH:
b705ab6a 1039 r = glob_item(i, path_set_perms, false);
777b87e7 1040 if (r < 0)
96ca8194 1041 return r;
777b87e7
MS
1042 break;
1043
a8d88783 1044 case RECURSIVE_RELABEL_PATH:
b705ab6a 1045 r = glob_item(i, path_set_perms, true);
a8d88783
MS
1046 if (r < 0)
1047 return r;
ebf4e801 1048 break;
e73a03e0 1049
ebf4e801 1050 case SET_XATTR:
b705ab6a
ZJS
1051 r = glob_item(i, path_set_xattrs, false);
1052 if (r < 0)
1053 return r;
1054 break;
1055
1056 case RECURSIVE_SET_XATTR:
1057 r = glob_item(i, path_set_xattrs, true);
ebf4e801
MW
1058 if (r < 0)
1059 return r;
e73a03e0 1060 break;
f8eeeaf9
ZJS
1061
1062 case SET_ACL:
b705ab6a 1063 r = glob_item(i, path_set_acls, false);
f8eeeaf9
ZJS
1064 if (r < 0)
1065 return r;
b705ab6a
ZJS
1066 break;
1067
1068 case RECURSIVE_SET_ACL:
1069 r = glob_item(i, path_set_acls, true);
1070 if (r < 0)
1071 return r;
1072 break;
3b63d2d3
LP
1073 }
1074
3b63d2d3
LP
1075 log_debug("%s created successfully.", i->path);
1076
f05bc3f7 1077 return 0;
3b63d2d3
LP
1078}
1079
a0896123 1080static int remove_item_instance(Item *i, const char *instance) {
3b63d2d3
LP
1081 int r;
1082
1083 assert(i);
1084
1085 switch (i->type) {
1086
1087 case CREATE_FILE:
1088 case TRUNCATE_FILE:
1089 case CREATE_DIRECTORY:
d7b8eec7 1090 case CREATE_SUBVOLUME:
ee17ee7c 1091 case CREATE_FIFO:
468d726b
LP
1092 case CREATE_SYMLINK:
1093 case CREATE_BLOCK_DEVICE:
1094 case CREATE_CHAR_DEVICE:
3b63d2d3 1095 case IGNORE_PATH:
78a92a5a 1096 case IGNORE_DIRECTORY_PATH:
e73a03e0 1097 case ADJUST_MODE:
777b87e7 1098 case RELABEL_PATH:
a8d88783 1099 case RECURSIVE_RELABEL_PATH:
31ed59c5 1100 case WRITE_FILE:
849958d1 1101 case COPY_FILES:
ebf4e801 1102 case SET_XATTR:
b705ab6a 1103 case RECURSIVE_SET_XATTR:
f8eeeaf9 1104 case SET_ACL:
b705ab6a 1105 case RECURSIVE_SET_ACL:
3b63d2d3
LP
1106 break;
1107
1108 case REMOVE_PATH:
4a62c710 1109 if (remove(instance) < 0 && errno != ENOENT)
3f93da98 1110 return log_error_errno(errno, "rm(%s): %m", instance);
3b63d2d3
LP
1111
1112 break;
1113
1114 case TRUNCATE_DIRECTORY:
1115 case RECURSIVE_REMOVE_PATH:
d139b24a
LP
1116 /* FIXME: we probably should use dir_cleanup() here
1117 * instead of rm_rf() so that 'x' is honoured. */
f56d5db9 1118 r = rm_rf_dangerous(instance, false, i->type == RECURSIVE_REMOVE_PATH, false);
f647962d
MS
1119 if (r < 0 && r != -ENOENT)
1120 return log_error_errno(r, "rm_rf(%s): %m", instance);
3b63d2d3
LP
1121
1122 break;
1123 }
1124
1125 return 0;
1126}
1127
a0896123 1128static int remove_item(Item *i) {
99e68c0b
MS
1129 int r = 0;
1130
b8bb3e8f
LP
1131 assert(i);
1132
1133 switch (i->type) {
1134
1135 case CREATE_FILE:
1136 case TRUNCATE_FILE:
1137 case CREATE_DIRECTORY:
d7b8eec7 1138 case CREATE_SUBVOLUME:
ee17ee7c 1139 case CREATE_FIFO:
468d726b
LP
1140 case CREATE_SYMLINK:
1141 case CREATE_CHAR_DEVICE:
1142 case CREATE_BLOCK_DEVICE:
b8bb3e8f 1143 case IGNORE_PATH:
78a92a5a 1144 case IGNORE_DIRECTORY_PATH:
e73a03e0 1145 case ADJUST_MODE:
777b87e7 1146 case RELABEL_PATH:
a8d88783 1147 case RECURSIVE_RELABEL_PATH:
31ed59c5 1148 case WRITE_FILE:
849958d1 1149 case COPY_FILES:
ebf4e801 1150 case SET_XATTR:
b705ab6a 1151 case RECURSIVE_SET_XATTR:
f8eeeaf9 1152 case SET_ACL:
b705ab6a 1153 case RECURSIVE_SET_ACL:
b8bb3e8f
LP
1154 break;
1155
1156 case REMOVE_PATH:
1157 case TRUNCATE_DIRECTORY:
99e68c0b 1158 case RECURSIVE_REMOVE_PATH:
081043cf 1159 r = glob_item(i, remove_item_instance, false);
99e68c0b 1160 break;
b8bb3e8f
LP
1161 }
1162
99e68c0b 1163 return r;
b8bb3e8f
LP
1164}
1165
78a92a5a 1166static int clean_item_instance(Item *i, const char* instance) {
7fd1b19b 1167 _cleanup_closedir_ DIR *d = NULL;
78a92a5a
MS
1168 struct stat s, ps;
1169 bool mountpoint;
1170 int r;
1171 usec_t cutoff, n;
1172
1173 assert(i);
1174
1175 if (!i->age_set)
1176 return 0;
1177
1178 n = now(CLOCK_REALTIME);
1179 if (n < i->age)
1180 return 0;
1181
1182 cutoff = n - i->age;
1183
1184 d = opendir(instance);
1185 if (!d) {
1186 if (errno == ENOENT || errno == ENOTDIR)
1187 return 0;
1188
56f64d95 1189 log_error_errno(errno, "Failed to open directory %s: %m", i->path);
78a92a5a
MS
1190 return -errno;
1191 }
1192
4a62c710
MS
1193 if (fstat(dirfd(d), &s) < 0)
1194 return log_error_errno(errno, "stat(%s) failed: %m", i->path);
78a92a5a
MS
1195
1196 if (!S_ISDIR(s.st_mode)) {
1197 log_error("%s is not a directory.", i->path);
19fbec19 1198 return -ENOTDIR;
78a92a5a
MS
1199 }
1200
4a62c710
MS
1201 if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0)
1202 return log_error_errno(errno, "stat(%s/..) failed: %m", i->path);
78a92a5a
MS
1203
1204 mountpoint = s.st_dev != ps.st_dev ||
1205 (s.st_dev == ps.st_dev && s.st_ino == ps.st_ino);
1206
19fbec19
ZJS
1207 r = dir_cleanup(i, instance, d, &s, cutoff, s.st_dev, mountpoint,
1208 MAX_DEPTH, i->keep_first_level);
78a92a5a
MS
1209 return r;
1210}
1211
1212static int clean_item(Item *i) {
1213 int r = 0;
1214
1215 assert(i);
1216
1217 switch (i->type) {
1218 case CREATE_DIRECTORY:
d7b8eec7 1219 case CREATE_SUBVOLUME:
78a92a5a
MS
1220 case TRUNCATE_DIRECTORY:
1221 case IGNORE_PATH:
849958d1 1222 case COPY_FILES:
78a92a5a
MS
1223 clean_item_instance(i, i->path);
1224 break;
1225 case IGNORE_DIRECTORY_PATH:
081043cf 1226 r = glob_item(i, clean_item_instance, false);
78a92a5a
MS
1227 break;
1228 default:
1229 break;
1230 }
1231
1232 return r;
1233}
1234
3f93da98
ZJS
1235static int process_item_array(ItemArray *array);
1236
3b63d2d3 1237static int process_item(Item *i) {
1e95893a 1238 int r, q, p, t = 0;
9348f0e6 1239 _cleanup_free_ char *prefix = NULL;
3b63d2d3
LP
1240
1241 assert(i);
1242
1910cd0e
LP
1243 if (i->done)
1244 return 0;
1245
1246 i->done = true;
1247
9348f0e6
ZJS
1248 prefix = malloc(strlen(i->path) + 1);
1249 if (!prefix)
1250 return log_oom();
1251
1910cd0e 1252 PATH_FOREACH_PREFIX(prefix, i->path) {
3f93da98 1253 ItemArray *j;
1910cd0e
LP
1254
1255 j = hashmap_get(items, prefix);
1db50423
ZJS
1256 if (j) {
1257 int s;
1258
3f93da98 1259 s = process_item_array(j);
1db50423
ZJS
1260 if (s < 0 && t == 0)
1261 t = s;
1262 }
1910cd0e
LP
1263 }
1264
3b63d2d3 1265 r = arg_create ? create_item(i) : 0;
a0896123 1266 q = arg_remove ? remove_item(i) : 0;
3b63d2d3
LP
1267 p = arg_clean ? clean_item(i) : 0;
1268
1db50423
ZJS
1269 return t < 0 ? t :
1270 r < 0 ? r :
1271 q < 0 ? q :
1272 p;
3b63d2d3
LP
1273}
1274
3f93da98
ZJS
1275static int process_item_array(ItemArray *array) {
1276 unsigned n;
1277 int r = 0, k;
753615e8 1278
3f93da98
ZJS
1279 assert(array);
1280
1281 for (n = 0; n < array->count; n++) {
1282 k = process_item(array->items + n);
1283 if (k < 0 && r == 0)
1284 r = k;
1285 }
1286
1287 return r;
1288}
3b63d2d3 1289
3f93da98
ZJS
1290static void item_free_contents(Item *i) {
1291 assert(i);
3b63d2d3 1292 free(i->path);
468d726b 1293 free(i->argument);
ebf4e801 1294 strv_free(i->xattrs);
f8eeeaf9
ZJS
1295
1296#ifdef HAVE_ACL
1297 acl_free(i->acl_access);
1298 acl_free(i->acl_default);
1299#endif
3b63d2d3
LP
1300}
1301
3f93da98
ZJS
1302static void item_array_free(ItemArray *a) {
1303 unsigned n;
1304
1305 if (!a)
1306 return;
1307
1308 for (n = 0; n < a->count; n++)
1309 item_free_contents(a->items + n);
1310 free(a->items);
1311 free(a);
1312}
e2f2fb78 1313
3f93da98 1314static bool item_compatible(Item *a, Item *b) {
bfe95f35
LP
1315 assert(a);
1316 assert(b);
3f93da98 1317 assert(streq(a->path, b->path));
bfe95f35 1318
3f93da98
ZJS
1319 if (takes_ownership(a->type) && takes_ownership(b->type))
1320 /* check if the items are the same */
1321 return streq_ptr(a->argument, b->argument) &&
bfe95f35 1322
3f93da98
ZJS
1323 a->uid_set == b->uid_set &&
1324 a->uid == b->uid &&
bfe95f35 1325
3f93da98
ZJS
1326 a->gid_set == b->gid_set &&
1327 a->gid == b->gid &&
bfe95f35 1328
3f93da98
ZJS
1329 a->mode_set == b->mode_set &&
1330 a->mode == b->mode &&
bfe95f35 1331
3f93da98
ZJS
1332 a->age_set == b->age_set &&
1333 a->age == b->age &&
bfe95f35 1334
3f93da98 1335 a->mask_perms == b->mask_perms &&
bfe95f35 1336
3f93da98 1337 a->keep_first_level == b->keep_first_level &&
468d726b 1338
3f93da98 1339 a->major_minor == b->major_minor;
468d726b 1340
bfe95f35
LP
1341 return true;
1342}
1343
a2aced4a
DR
1344static bool should_include_path(const char *path) {
1345 char **prefix;
1346
abef3f91 1347 STRV_FOREACH(prefix, arg_exclude_prefixes)
5c795114
DR
1348 if (path_startswith(path, *prefix))
1349 return false;
a2aced4a 1350
abef3f91 1351 STRV_FOREACH(prefix, arg_include_prefixes)
a2aced4a
DR
1352 if (path_startswith(path, *prefix))
1353 return true;
a2aced4a 1354
5c795114
DR
1355 /* no matches, so we should include this path only if we
1356 * have no whitelist at all */
7bc040fa 1357 return strv_length(arg_include_prefixes) == 0;
a2aced4a
DR
1358}
1359
fba6e687 1360static int parse_line(const char *fname, unsigned line, const char *buffer) {
1731e34a
LP
1361
1362 static const Specifier specifier_table[] = {
1363 { 'm', specifier_machine_id, NULL },
1364 { 'b', specifier_boot_id, NULL },
1365 { 'H', specifier_host_name, NULL },
1366 { 'v', specifier_kernel_release, NULL },
1367 {}
1368 };
1369
cde684a2 1370 _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
3f93da98
ZJS
1371 _cleanup_(item_free_contents) Item i = {};
1372 ItemArray *existing;
bfe95f35 1373 Hashmap *h;
3f93da98 1374 int r, c = -1, pos;
5f255144 1375 bool force = false, boot = false;
3b63d2d3
LP
1376
1377 assert(fname);
1378 assert(line >= 1);
1379 assert(buffer);
1380
19fbec19 1381 r = sscanf(buffer,
c4708f13
ZJS
1382 "%ms %ms %ms %ms %ms %ms %n",
1383 &action,
1731e34a 1384 &path,
bd40a2d8
LP
1385 &mode,
1386 &user,
1387 &group,
468d726b 1388 &age,
3f93da98 1389 &c);
19fbec19 1390 if (r < 2) {
3b63d2d3 1391 log_error("[%s:%u] Syntax error.", fname, line);
7f2c1f4d 1392 return -EIO;
5008d581
LP
1393 }
1394
2e78fa79
LP
1395 if (isempty(action)) {
1396 log_error("[%s:%u] Command too short '%s'.", fname, line, action);
c4708f13 1397 return -EINVAL;
2e78fa79
LP
1398 }
1399
5f255144
ZJS
1400 for (pos = 1; action[pos]; pos++) {
1401 if (action[pos] == '!' && !boot)
1402 boot = true;
1403 else if (action[pos] == '+' && !force)
1404 force = true;
1405 else {
1406 log_error("[%s:%u] Unknown modifiers in command '%s'",
1407 fname, line, action);
1408 return -EINVAL;
1409 }
2e78fa79
LP
1410 }
1411
5f255144 1412 if (boot && !arg_boot)
c4708f13
ZJS
1413 return 0;
1414
3f93da98
ZJS
1415 i.type = action[0];
1416 i.force = force;
2e78fa79 1417
3f93da98 1418 r = specifier_printf(path, specifier_table, NULL, &i.path);
1731e34a
LP
1419 if (r < 0) {
1420 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, path);
1421 return r;
1422 }
1423
3f93da98
ZJS
1424 if (c >= 0) {
1425 c += strspn(buffer+c, WHITESPACE);
1426 if (buffer[c] != 0 && (buffer[c] != '-' || buffer[c+1] != 0)) {
1427 i.argument = unquote(buffer+c, "\"");
1428 if (!i.argument)
0d0f0c50 1429 return log_oom();
31ed59c5
LP
1430 }
1431 }
1432
3f93da98 1433 switch (i.type) {
468d726b 1434
777b87e7
MS
1435 case CREATE_FILE:
1436 case TRUNCATE_FILE:
1437 case CREATE_DIRECTORY:
d7b8eec7 1438 case CREATE_SUBVOLUME:
777b87e7
MS
1439 case TRUNCATE_DIRECTORY:
1440 case CREATE_FIFO:
1441 case IGNORE_PATH:
78a92a5a 1442 case IGNORE_DIRECTORY_PATH:
777b87e7
MS
1443 case REMOVE_PATH:
1444 case RECURSIVE_REMOVE_PATH:
e73a03e0 1445 case ADJUST_MODE:
777b87e7
MS
1446 case RELABEL_PATH:
1447 case RECURSIVE_RELABEL_PATH:
1448 break;
468d726b
LP
1449
1450 case CREATE_SYMLINK:
3f93da98
ZJS
1451 if (!i.argument) {
1452 i.argument = strappend("/usr/share/factory/", i.path);
1453 if (!i.argument)
2f3b873a 1454 return log_oom();
468d726b
LP
1455 }
1456 break;
1457
31ed59c5 1458 case WRITE_FILE:
3f93da98 1459 if (!i.argument) {
31ed59c5 1460 log_error("[%s:%u] Write file requires argument.", fname, line);
7f2c1f4d 1461 return -EBADMSG;
31ed59c5
LP
1462 }
1463 break;
1464
849958d1 1465 case COPY_FILES:
3f93da98
ZJS
1466 if (!i.argument) {
1467 i.argument = strappend("/usr/share/factory/", i.path);
1468 if (!i.argument)
2f3b873a 1469 return log_oom();
3f93da98 1470 } else if (!path_is_absolute(i.argument)) {
849958d1
LP
1471 log_error("[%s:%u] Source path is not absolute.", fname, line);
1472 return -EBADMSG;
1473 }
1474
3f93da98 1475 path_kill_slashes(i.argument);
849958d1
LP
1476 break;
1477
468d726b
LP
1478 case CREATE_CHAR_DEVICE:
1479 case CREATE_BLOCK_DEVICE: {
1480 unsigned major, minor;
1481
3f93da98 1482 if (!i.argument) {
468d726b 1483 log_error("[%s:%u] Device file requires argument.", fname, line);
7f2c1f4d 1484 return -EBADMSG;
468d726b
LP
1485 }
1486
3f93da98
ZJS
1487 if (sscanf(i.argument, "%u:%u", &major, &minor) != 2) {
1488 log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument);
7f2c1f4d 1489 return -EBADMSG;
468d726b
LP
1490 }
1491
3f93da98 1492 i.major_minor = makedev(major, minor);
468d726b
LP
1493 break;
1494 }
1495
ebf4e801 1496 case SET_XATTR:
b705ab6a 1497 case RECURSIVE_SET_XATTR:
3f93da98 1498 if (!i.argument) {
ebf4e801
MW
1499 log_error("[%s:%u] Set extended attribute requires argument.", fname, line);
1500 return -EBADMSG;
1501 }
3f93da98 1502 r = get_xattrs_from_arg(&i);
ebf4e801
MW
1503 if (r < 0)
1504 return r;
1505 break;
1506
f8eeeaf9 1507 case SET_ACL:
b705ab6a 1508 case RECURSIVE_SET_ACL:
f8eeeaf9
ZJS
1509 if (!i.argument) {
1510 log_error("[%s:%u] Set ACLs requires argument.", fname, line);
1511 return -EBADMSG;
1512 }
1513 r = get_acls_from_arg(&i);
1514 if (r < 0)
1515 return r;
1516 break;
1517
777b87e7 1518 default:
3f93da98 1519 log_error("[%s:%u] Unknown command type '%c'.", fname, line, i.type);
7f2c1f4d 1520 return -EBADMSG;
3b63d2d3 1521 }
468d726b 1522
3f93da98
ZJS
1523 if (!path_is_absolute(i.path)) {
1524 log_error("[%s:%u] Path '%s' not absolute.", fname, line, i.path);
7f2c1f4d 1525 return -EBADMSG;
3b63d2d3
LP
1526 }
1527
3f93da98 1528 path_kill_slashes(i.path);
3b63d2d3 1529
3f93da98 1530 if (!should_include_path(i.path))
7f2c1f4d 1531 return 0;
5008d581 1532
cf9a4abd 1533 if (arg_root) {
cde684a2
LP
1534 char *p;
1535
3f93da98 1536 p = strappend(arg_root, i.path);
cf9a4abd
MM
1537 if (!p)
1538 return log_oom();
1539
3f93da98
ZJS
1540 free(i.path);
1541 i.path = p;
cf9a4abd
MM
1542 }
1543
3b63d2d3 1544 if (user && !streq(user, "-")) {
4b67834e
LP
1545 const char *u = user;
1546
3f93da98 1547 r = get_user_creds(&u, &i.uid, NULL, NULL, NULL);
4b67834e 1548 if (r < 0) {
3b63d2d3 1549 log_error("[%s:%u] Unknown user '%s'.", fname, line, user);
7f2c1f4d 1550 return r;
3b63d2d3
LP
1551 }
1552
3f93da98 1553 i.uid_set = true;
3b63d2d3
LP
1554 }
1555
1556 if (group && !streq(group, "-")) {
4b67834e
LP
1557 const char *g = group;
1558
3f93da98 1559 r = get_group_creds(&g, &i.gid);
4b67834e 1560 if (r < 0) {
3b63d2d3 1561 log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
7f2c1f4d 1562 return r;
3b63d2d3
LP
1563 }
1564
3f93da98 1565 i.gid_set = true;
3b63d2d3
LP
1566 }
1567
1568 if (mode && !streq(mode, "-")) {
abef3f91 1569 const char *mm = mode;
3b63d2d3
LP
1570 unsigned m;
1571
abef3f91 1572 if (*mm == '~') {
3f93da98 1573 i.mask_perms = true;
abef3f91
LP
1574 mm++;
1575 }
1576
1577 if (sscanf(mm, "%o", &m) != 1) {
3b63d2d3 1578 log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
7f2c1f4d 1579 return -ENOENT;
3b63d2d3
LP
1580 }
1581
3f93da98
ZJS
1582 i.mode = m;
1583 i.mode_set = true;
3b63d2d3 1584 } else
3f93da98
ZJS
1585 i.mode = IN_SET(i.type, CREATE_DIRECTORY, CREATE_SUBVOLUME, TRUNCATE_DIRECTORY)
1586 ? 0755 : 0644;
3b63d2d3
LP
1587
1588 if (age && !streq(age, "-")) {
24f3a374
LP
1589 const char *a = age;
1590
1591 if (*a == '~') {
3f93da98 1592 i.keep_first_level = true;
24f3a374
LP
1593 a++;
1594 }
1595
3f93da98 1596 if (parse_sec(a, &i.age) < 0) {
3b63d2d3 1597 log_error("[%s:%u] Invalid age '%s'.", fname, line, age);
7f2c1f4d 1598 return -EBADMSG;
3b63d2d3
LP
1599 }
1600
3f93da98 1601 i.age_set = true;
3b63d2d3
LP
1602 }
1603
3f93da98 1604 h = needs_glob(i.type) ? globs : items;
bfe95f35 1605
3f93da98 1606 existing = hashmap_get(h, i.path);
468d726b 1607 if (existing) {
3f93da98
ZJS
1608 unsigned n;
1609
1610 for (n = 0; n < existing->count; n++) {
1611 if (!item_compatible(existing->items + n, &i))
505ef0e3 1612 log_warning("[%s:%u] Duplicate line for path \"%s\", ignoring.",
3f93da98 1613 fname, line, i.path);
ebf4e801
MW
1614 }
1615 } else {
3f93da98
ZJS
1616 existing = new0(ItemArray, 1);
1617 r = hashmap_put(h, i.path, existing);
1618 if (r < 0)
1619 return log_oom();
bfe95f35
LP
1620 }
1621
3f93da98
ZJS
1622 if (!GREEDY_REALLOC(existing->items, existing->size, existing->count + 1))
1623 return log_oom();
5008d581 1624
3f93da98
ZJS
1625 memcpy(existing->items + existing->count++, &i, sizeof(i));
1626 zero(i);
7f2c1f4d 1627 return 0;
5008d581
LP
1628}
1629
601185b4 1630static void help(void) {
522d4a49
LP
1631 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1632 "Creates, deletes and cleans up volatile and temporary files and directories.\n\n"
5c795114 1633 " -h --help Show this help\n"
eb9da376 1634 " --version Show package version\n"
5c795114
DR
1635 " --create Create marked files/directories\n"
1636 " --clean Clean up marked directories\n"
1637 " --remove Remove marked files/directories\n"
81815651 1638 " --boot Execute actions only safe at boot\n"
5c795114 1639 " --prefix=PATH Only apply rules that apply to paths with the specified prefix\n"
cf9a4abd
MM
1640 " --exclude-prefix=PATH Ignore rules that apply to paths with the specified prefix\n"
1641 " --root=PATH Operate on an alternate filesystem root\n",
3b63d2d3 1642 program_invocation_short_name);
3b63d2d3
LP
1643}
1644
1645static int parse_argv(int argc, char *argv[]) {
1646
1647 enum {
eb9da376 1648 ARG_VERSION = 0x100,
3b63d2d3
LP
1649 ARG_CREATE,
1650 ARG_CLEAN,
fba6e687 1651 ARG_REMOVE,
81815651 1652 ARG_BOOT,
5c795114
DR
1653 ARG_PREFIX,
1654 ARG_EXCLUDE_PREFIX,
cf9a4abd 1655 ARG_ROOT,
3b63d2d3
LP
1656 };
1657
1658 static const struct option options[] = {
5c795114 1659 { "help", no_argument, NULL, 'h' },
eb9da376 1660 { "version", no_argument, NULL, ARG_VERSION },
5c795114
DR
1661 { "create", no_argument, NULL, ARG_CREATE },
1662 { "clean", no_argument, NULL, ARG_CLEAN },
1663 { "remove", no_argument, NULL, ARG_REMOVE },
81815651 1664 { "boot", no_argument, NULL, ARG_BOOT },
5c795114
DR
1665 { "prefix", required_argument, NULL, ARG_PREFIX },
1666 { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX },
cf9a4abd 1667 { "root", required_argument, NULL, ARG_ROOT },
eb9da376 1668 {}
3b63d2d3
LP
1669 };
1670
1671 int c;
1672
1673 assert(argc >= 0);
1674 assert(argv);
1675
601185b4 1676 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
3b63d2d3
LP
1677
1678 switch (c) {
1679
1680 case 'h':
601185b4
ZJS
1681 help();
1682 return 0;
eb9da376
LP
1683
1684 case ARG_VERSION:
1685 puts(PACKAGE_STRING);
1686 puts(SYSTEMD_FEATURES);
3b63d2d3
LP
1687 return 0;
1688
1689 case ARG_CREATE:
1690 arg_create = true;
1691 break;
1692
1693 case ARG_CLEAN:
1694 arg_clean = true;
1695 break;
1696
1697 case ARG_REMOVE:
1698 arg_remove = true;
1699 break;
1700
81815651
ZJS
1701 case ARG_BOOT:
1702 arg_boot = true;
c4708f13
ZJS
1703 break;
1704
fba6e687 1705 case ARG_PREFIX:
7bc040fa 1706 if (strv_push(&arg_include_prefixes, optarg) < 0)
a2aced4a 1707 return log_oom();
fba6e687
LP
1708 break;
1709
5c795114 1710 case ARG_EXCLUDE_PREFIX:
7bc040fa 1711 if (strv_push(&arg_exclude_prefixes, optarg) < 0)
5c795114
DR
1712 return log_oom();
1713 break;
1714
cf9a4abd 1715 case ARG_ROOT:
753615e8 1716 free(arg_root);
cf9a4abd
MM
1717 arg_root = path_make_absolute_cwd(optarg);
1718 if (!arg_root)
1719 return log_oom();
753615e8 1720
cf9a4abd
MM
1721 path_kill_slashes(arg_root);
1722 break;
1723
3b63d2d3
LP
1724 case '?':
1725 return -EINVAL;
1726
1727 default:
eb9da376 1728 assert_not_reached("Unhandled option");
3b63d2d3 1729 }
3b63d2d3
LP
1730
1731 if (!arg_clean && !arg_create && !arg_remove) {
35b8ca3a 1732 log_error("You need to specify at least one of --clean, --create or --remove.");
3b63d2d3
LP
1733 return -EINVAL;
1734 }
1735
1736 return 1;
1737}
1738
fba6e687 1739static int read_config_file(const char *fn, bool ignore_enoent) {
1731e34a
LP
1740 _cleanup_fclose_ FILE *f = NULL;
1741 char line[LINE_MAX];
78a92a5a 1742 Iterator iterator;
1731e34a 1743 unsigned v = 0;
78a92a5a 1744 Item *i;
1731e34a 1745 int r;
fba6e687
LP
1746
1747 assert(fn);
1748
cf9a4abd 1749 r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &f);
fabe5c0e
LP
1750 if (r < 0) {
1751 if (ignore_enoent && r == -ENOENT)
fba6e687
LP
1752 return 0;
1753
8d3d7072 1754 return log_error_errno(r, "Failed to open '%s', ignoring: %m", fn);
fba6e687
LP
1755 }
1756
1731e34a
LP
1757 FOREACH_LINE(line, f, break) {
1758 char *l;
fba6e687
LP
1759 int k;
1760
fba6e687
LP
1761 v++;
1762
1763 l = strstrip(line);
1764 if (*l == '#' || *l == 0)
1765 continue;
1766
1731e34a
LP
1767 k = parse_line(fn, v, l);
1768 if (k < 0 && r == 0)
1769 r = k;
fba6e687
LP
1770 }
1771
78a92a5a
MS
1772 /* we have to determine age parameter for each entry of type X */
1773 HASHMAP_FOREACH(i, globs, iterator) {
1774 Iterator iter;
1775 Item *j, *candidate_item = NULL;
1776
1777 if (i->type != IGNORE_DIRECTORY_PATH)
1778 continue;
1779
1780 HASHMAP_FOREACH(j, items, iter) {
d7b8eec7 1781 if (j->type != CREATE_DIRECTORY && j->type != TRUNCATE_DIRECTORY && j->type != CREATE_SUBVOLUME)
78a92a5a
MS
1782 continue;
1783
1784 if (path_equal(j->path, i->path)) {
1785 candidate_item = j;
1786 break;
1787 }
1788
1789 if ((!candidate_item && path_startswith(i->path, j->path)) ||
1790 (candidate_item && path_startswith(j->path, candidate_item->path) && (fnmatch(i->path, j->path, FNM_PATHNAME | FNM_PERIOD) == 0)))
1791 candidate_item = j;
1792 }
1793
9ed2a35e 1794 if (candidate_item && candidate_item->age_set) {
78a92a5a
MS
1795 i->age = candidate_item->age;
1796 i->age_set = true;
1797 }
1798 }
1799
fba6e687 1800 if (ferror(f)) {
56f64d95 1801 log_error_errno(errno, "Failed to read from file %s: %m", fn);
fba6e687
LP
1802 if (r == 0)
1803 r = -EIO;
1804 }
1805
fba6e687
LP
1806 return r;
1807}
1808
5008d581 1809int main(int argc, char *argv[]) {
fabe5c0e 1810 int r, k;
3f93da98 1811 ItemArray *a;
3b63d2d3
LP
1812 Iterator iterator;
1813
fdcad0c2
LP
1814 r = parse_argv(argc, argv);
1815 if (r <= 0)
753615e8 1816 goto finish;
5008d581 1817
eb0ca9eb 1818 log_set_target(LOG_TARGET_AUTO);
5008d581
LP
1819 log_parse_environment();
1820 log_open();
1821
4c12626c
LP
1822 umask(0022);
1823
cc56fafe 1824 mac_selinux_init(NULL);
5008d581 1825
d5099efc
MS
1826 items = hashmap_new(&string_hash_ops);
1827 globs = hashmap_new(&string_hash_ops);
b8bb3e8f
LP
1828
1829 if (!items || !globs) {
fabe5c0e 1830 r = log_oom();
3b63d2d3
LP
1831 goto finish;
1832 }
1833
fabe5c0e 1834 r = 0;
5008d581 1835
fba6e687
LP
1836 if (optind < argc) {
1837 int j;
5008d581 1838
9125670f 1839 for (j = optind; j < argc; j++) {
fabe5c0e
LP
1840 k = read_config_file(argv[j], false);
1841 if (k < 0 && r == 0)
1842 r = k;
9125670f 1843 }
5008d581 1844
fba6e687 1845 } else {
fabe5c0e
LP
1846 _cleanup_strv_free_ char **files = NULL;
1847 char **f;
5008d581 1848
cf9a4abd 1849 r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs);
44143309 1850 if (r < 0) {
da927ba9 1851 log_error_errno(r, "Failed to enumerate tmpfiles.d files: %m");
44143309
KS
1852 goto finish;
1853 }
3b63d2d3 1854
772f8371 1855 STRV_FOREACH(f, files) {
fabe5c0e
LP
1856 k = read_config_file(*f, true);
1857 if (k < 0 && r == 0)
1858 r = k;
5008d581 1859 }
772f8371 1860 }
5008d581 1861
3f93da98
ZJS
1862 HASHMAP_FOREACH(a, globs, iterator) {
1863 k = process_item_array(a);
1db50423
ZJS
1864 if (k < 0 && r == 0)
1865 r = k;
1866 }
b8bb3e8f 1867
3f93da98
ZJS
1868 HASHMAP_FOREACH(a, items, iterator) {
1869 k = process_item_array(a);
1db50423
ZJS
1870 if (k < 0 && r == 0)
1871 r = k;
1872 }
3b63d2d3 1873
5008d581 1874finish:
3f93da98
ZJS
1875 while ((a = hashmap_steal_first(items)))
1876 item_array_free(a);
3b63d2d3 1877
3f93da98
ZJS
1878 while ((a = hashmap_steal_first(globs)))
1879 item_array_free(a);
17b90525 1880
3b63d2d3 1881 hashmap_free(items);
b8bb3e8f 1882 hashmap_free(globs);
5008d581 1883
7bc040fa
LP
1884 free(arg_include_prefixes);
1885 free(arg_exclude_prefixes);
cf9a4abd 1886 free(arg_root);
a2aced4a 1887
17b90525
LP
1888 set_free_free(unix_sockets);
1889
cc56fafe 1890 mac_selinux_finish();
29003cff 1891
fabe5c0e 1892 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
5008d581 1893}