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