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