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