]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tmpfiles/tmpfiles.c
treewide: a few more log_*_errno + return simplifications
[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
8d3d7072
MS
670 if (r != -EEXIST)
671 return log_error_errno(r, "Failed to copy files to %s: %m", i->path);
e156347e
LP
672
673 if (stat(i->argument, &a) < 0) {
674 log_error("stat(%s) failed: %m", i->argument);
675 return -errno;
676 }
677
678 if (stat(i->path, &b) < 0) {
679 log_error("stat(%s) failed: %m", i->path);
680 return -errno;
681 }
682
683 if ((a.st_mode ^ b.st_mode) & S_IFMT) {
684 log_debug("Can't copy to %s, file exists already and is of different type", i->path);
685 return 0;
686 }
849958d1
LP
687 }
688
689 r = item_set_perms(i, i->path);
690 if (r < 0)
691 return r;
692
693 break;
694
d4e9eb91
DR
695 case WRITE_FILE:
696 r = glob_item(i, write_one_file);
f05bc3f7
MS
697 if (r < 0)
698 return r;
5008d581 699
3b63d2d3
LP
700 break;
701
702 case TRUNCATE_DIRECTORY:
703 case CREATE_DIRECTORY:
5008d581 704
5c0d398d
LP
705 RUN_WITH_UMASK(0000) {
706 mkdir_parents_label(i->path, 0755);
6f045293 707 r = mkdir_label(i->path, i->mode);
5c0d398d 708 }
5008d581 709
e156347e 710 if (r < 0) {
f647962d
MS
711 if (r != -EEXIST)
712 return log_error_errno(r, "Failed to create directory %s: %m", i->path);
5008d581 713
e156347e
LP
714 if (stat(i->path, &st) < 0) {
715 log_error("stat(%s) failed: %m", i->path);
716 return -errno;
717 }
5008d581 718
e156347e
LP
719 if (!S_ISDIR(st.st_mode)) {
720 log_debug("%s already exists and is not a directory.", i->path);
721 return 0;
722 }
5008d581
LP
723 }
724
062e01bb 725 r = item_set_perms(i, i->path);
f05bc3f7
MS
726 if (r < 0)
727 return r;
3b63d2d3
LP
728
729 break;
ee17ee7c
LP
730
731 case CREATE_FIFO:
732
5c0d398d 733 RUN_WITH_UMASK(0000) {
ecabcf8b 734 mac_selinux_create_file_prepare(i->path, S_IFIFO);
5c0d398d 735 r = mkfifo(i->path, i->mode);
ecabcf8b 736 mac_selinux_create_file_clear();
5c0d398d 737 }
ee17ee7c 738
1554afae
LP
739 if (r < 0) {
740 if (errno != EEXIST) {
741 log_error("Failed to create fifo %s: %m", i->path);
742 return -errno;
743 }
ee17ee7c 744
1554afae
LP
745 if (stat(i->path, &st) < 0) {
746 log_error("stat(%s) failed: %m", i->path);
747 return -errno;
748 }
ee17ee7c 749
1554afae
LP
750 if (!S_ISFIFO(st.st_mode)) {
751
752 if (i->force) {
753
754 RUN_WITH_UMASK(0000) {
ecabcf8b 755 mac_selinux_create_file_prepare(i->path, S_IFIFO);
1554afae 756 r = mkfifo_atomic(i->path, i->mode);
ecabcf8b 757 mac_selinux_create_file_clear();
1554afae
LP
758 }
759
f647962d
MS
760 if (r < 0)
761 return log_error_errno(r, "Failed to create fifo %s: %m", i->path);
1554afae
LP
762 } else {
763 log_debug("%s is not a fifo.", i->path);
764 return 0;
765 }
766 }
ee17ee7c
LP
767 }
768
062e01bb 769 r = item_set_perms(i, i->path);
f05bc3f7
MS
770 if (r < 0)
771 return r;
ee17ee7c
LP
772
773 break;
a8d88783 774
2e78fa79 775 case CREATE_SYMLINK:
468d726b 776
ecabcf8b 777 mac_selinux_create_file_prepare(i->path, S_IFLNK);
468d726b 778 r = symlink(i->argument, i->path);
ecabcf8b 779 mac_selinux_create_file_clear();
e9a5ef7c 780
468d726b 781 if (r < 0) {
2e78fa79 782 _cleanup_free_ char *x = NULL;
468d726b 783
2e78fa79
LP
784 if (errno != EEXIST) {
785 log_error("symlink(%s, %s) failed: %m", i->argument, i->path);
786 return -errno;
787 }
788
789 r = readlink_malloc(i->path, &x);
790 if (r < 0 || !streq(i->argument, x)) {
791
792 if (i->force) {
ecabcf8b 793 mac_selinux_create_file_prepare(i->path, S_IFLNK);
2e78fa79 794 r = symlink_atomic(i->argument, i->path);
ecabcf8b 795 mac_selinux_create_file_clear();
2e78fa79 796
f647962d
MS
797 if (r < 0)
798 return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path);
1554afae 799 } else {
2e78fa79 800 log_debug("%s is not a symlink or does not point to the correct path.", i->path);
1554afae
LP
801 return 0;
802 }
2e78fa79 803 }
468d726b
LP
804 }
805
468d726b 806 break;
468d726b
LP
807
808 case CREATE_BLOCK_DEVICE:
809 case CREATE_CHAR_DEVICE: {
cb7ed9df
LP
810 mode_t file_type;
811
812 if (have_effective_cap(CAP_MKNOD) == 0) {
813 /* In a container we lack CAP_MKNOD. We
ab06eef8 814 shouldn't attempt to create the device node in
cb7ed9df
LP
815 that case to avoid noise, and we don't support
816 virtualized devices in containers anyway. */
817
818 log_debug("We lack CAP_MKNOD, skipping creation of device node %s.", i->path);
819 return 0;
820 }
821
1554afae 822 file_type = i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR;
468d726b 823
5c0d398d 824 RUN_WITH_UMASK(0000) {
ecabcf8b 825 mac_selinux_create_file_prepare(i->path, file_type);
5c0d398d 826 r = mknod(i->path, i->mode | file_type, i->major_minor);
ecabcf8b 827 mac_selinux_create_file_clear();
5c0d398d 828 }
468d726b 829
6555ad8e
KS
830 if (r < 0) {
831 if (errno == EPERM) {
832 log_debug("We lack permissions, possibly because of cgroup configuration; "
833 "skipping creation of device node %s.", i->path);
834 return 0;
835 }
836
837 if (errno != EEXIST) {
838 log_error("Failed to create device node %s: %m", i->path);
839 return -errno;
840 }
468d726b 841
1554afae
LP
842 if (stat(i->path, &st) < 0) {
843 log_error("stat(%s) failed: %m", i->path);
844 return -errno;
845 }
468d726b 846
1554afae
LP
847 if ((st.st_mode & S_IFMT) != file_type) {
848
849 if (i->force) {
850
851 RUN_WITH_UMASK(0000) {
ecabcf8b 852 mac_selinux_create_file_prepare(i->path, file_type);
1554afae 853 r = mknod_atomic(i->path, i->mode | file_type, i->major_minor);
ecabcf8b 854 mac_selinux_create_file_clear();
1554afae
LP
855 }
856
f647962d
MS
857 if (r < 0)
858 return log_error_errno(r, "Failed to create device node %s: %m", i->path);
1554afae
LP
859 } else {
860 log_debug("%s is not a device node.", i->path);
861 return 0;
862 }
863 }
468d726b
LP
864 }
865
866 r = item_set_perms(i, i->path);
867 if (r < 0)
868 return r;
869
870 break;
871 }
872
e73a03e0 873 case ADJUST_MODE:
777b87e7
MS
874 case RELABEL_PATH:
875
876 r = glob_item(i, item_set_perms);
877 if (r < 0)
96ca8194 878 return r;
777b87e7
MS
879 break;
880
a8d88783
MS
881 case RECURSIVE_RELABEL_PATH:
882
e73a03e0 883 r = glob_item(i, item_set_perms_recursive);
a8d88783
MS
884 if (r < 0)
885 return r;
e73a03e0
LP
886
887 break;
3b63d2d3
LP
888 }
889
3b63d2d3
LP
890 log_debug("%s created successfully.", i->path);
891
f05bc3f7 892 return 0;
3b63d2d3
LP
893}
894
a0896123 895static int remove_item_instance(Item *i, const char *instance) {
3b63d2d3
LP
896 int r;
897
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_BLOCK_DEVICE:
908 case CREATE_CHAR_DEVICE:
3b63d2d3 909 case IGNORE_PATH:
78a92a5a 910 case IGNORE_DIRECTORY_PATH:
e73a03e0 911 case ADJUST_MODE:
777b87e7 912 case RELABEL_PATH:
a8d88783 913 case RECURSIVE_RELABEL_PATH:
31ed59c5 914 case WRITE_FILE:
849958d1 915 case COPY_FILES:
3b63d2d3
LP
916 break;
917
918 case REMOVE_PATH:
b8bb3e8f
LP
919 if (remove(instance) < 0 && errno != ENOENT) {
920 log_error("remove(%s): %m", instance);
3b63d2d3 921 return -errno;
5008d581 922 }
3b63d2d3
LP
923
924 break;
925
926 case TRUNCATE_DIRECTORY:
927 case RECURSIVE_REMOVE_PATH:
d139b24a
LP
928 /* FIXME: we probably should use dir_cleanup() here
929 * instead of rm_rf() so that 'x' is honoured. */
f56d5db9 930 r = rm_rf_dangerous(instance, false, i->type == RECURSIVE_REMOVE_PATH, false);
f647962d
MS
931 if (r < 0 && r != -ENOENT)
932 return log_error_errno(r, "rm_rf(%s): %m", instance);
3b63d2d3
LP
933
934 break;
935 }
936
937 return 0;
938}
939
a0896123 940static int remove_item(Item *i) {
99e68c0b
MS
941 int r = 0;
942
b8bb3e8f
LP
943 assert(i);
944
945 switch (i->type) {
946
947 case CREATE_FILE:
948 case TRUNCATE_FILE:
949 case CREATE_DIRECTORY:
ee17ee7c 950 case CREATE_FIFO:
468d726b
LP
951 case CREATE_SYMLINK:
952 case CREATE_CHAR_DEVICE:
953 case CREATE_BLOCK_DEVICE:
b8bb3e8f 954 case IGNORE_PATH:
78a92a5a 955 case IGNORE_DIRECTORY_PATH:
e73a03e0 956 case ADJUST_MODE:
777b87e7 957 case RELABEL_PATH:
a8d88783 958 case RECURSIVE_RELABEL_PATH:
31ed59c5 959 case WRITE_FILE:
849958d1 960 case COPY_FILES:
b8bb3e8f
LP
961 break;
962
963 case REMOVE_PATH:
964 case TRUNCATE_DIRECTORY:
99e68c0b
MS
965 case RECURSIVE_REMOVE_PATH:
966 r = glob_item(i, remove_item_instance);
967 break;
b8bb3e8f
LP
968 }
969
99e68c0b 970 return r;
b8bb3e8f
LP
971}
972
78a92a5a 973static int clean_item_instance(Item *i, const char* instance) {
7fd1b19b 974 _cleanup_closedir_ DIR *d = NULL;
78a92a5a
MS
975 struct stat s, ps;
976 bool mountpoint;
977 int r;
978 usec_t cutoff, n;
979
980 assert(i);
981
982 if (!i->age_set)
983 return 0;
984
985 n = now(CLOCK_REALTIME);
986 if (n < i->age)
987 return 0;
988
989 cutoff = n - i->age;
990
991 d = opendir(instance);
992 if (!d) {
993 if (errno == ENOENT || errno == ENOTDIR)
994 return 0;
995
996 log_error("Failed to open directory %s: %m", i->path);
997 return -errno;
998 }
999
1000 if (fstat(dirfd(d), &s) < 0) {
1001 log_error("stat(%s) failed: %m", i->path);
19fbec19 1002 return -errno;
78a92a5a
MS
1003 }
1004
1005 if (!S_ISDIR(s.st_mode)) {
1006 log_error("%s is not a directory.", i->path);
19fbec19 1007 return -ENOTDIR;
78a92a5a
MS
1008 }
1009
1010 if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) {
1011 log_error("stat(%s/..) failed: %m", i->path);
19fbec19 1012 return -errno;
78a92a5a
MS
1013 }
1014
1015 mountpoint = s.st_dev != ps.st_dev ||
1016 (s.st_dev == ps.st_dev && s.st_ino == ps.st_ino);
1017
19fbec19
ZJS
1018 r = dir_cleanup(i, instance, d, &s, cutoff, s.st_dev, mountpoint,
1019 MAX_DEPTH, i->keep_first_level);
78a92a5a
MS
1020 return r;
1021}
1022
1023static int clean_item(Item *i) {
1024 int r = 0;
1025
1026 assert(i);
1027
1028 switch (i->type) {
1029 case CREATE_DIRECTORY:
1030 case TRUNCATE_DIRECTORY:
1031 case IGNORE_PATH:
849958d1 1032 case COPY_FILES:
78a92a5a
MS
1033 clean_item_instance(i, i->path);
1034 break;
1035 case IGNORE_DIRECTORY_PATH:
1036 r = glob_item(i, clean_item_instance);
1037 break;
1038 default:
1039 break;
1040 }
1041
1042 return r;
1043}
1044
3b63d2d3
LP
1045static int process_item(Item *i) {
1046 int r, q, p;
9348f0e6 1047 _cleanup_free_ char *prefix = NULL;
3b63d2d3
LP
1048
1049 assert(i);
1050
1910cd0e
LP
1051 if (i->done)
1052 return 0;
1053
1054 i->done = true;
1055
9348f0e6
ZJS
1056 prefix = malloc(strlen(i->path) + 1);
1057 if (!prefix)
1058 return log_oom();
1059
1910cd0e
LP
1060 PATH_FOREACH_PREFIX(prefix, i->path) {
1061 Item *j;
1062
1063 j = hashmap_get(items, prefix);
1064 if (j)
1065 process_item(j);
1066 }
1067
3b63d2d3 1068 r = arg_create ? create_item(i) : 0;
a0896123 1069 q = arg_remove ? remove_item(i) : 0;
3b63d2d3
LP
1070 p = arg_clean ? clean_item(i) : 0;
1071
1072 if (r < 0)
1073 return r;
1074
1075 if (q < 0)
1076 return q;
1077
1078 return p;
1079}
1080
1081static void item_free(Item *i) {
753615e8
LP
1082
1083 if (!i)
1084 return;
3b63d2d3
LP
1085
1086 free(i->path);
468d726b 1087 free(i->argument);
3b63d2d3
LP
1088 free(i);
1089}
1090
14bf2c9d 1091DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
e2f2fb78 1092
bfe95f35
LP
1093static bool item_equal(Item *a, Item *b) {
1094 assert(a);
1095 assert(b);
1096
1097 if (!streq_ptr(a->path, b->path))
1098 return false;
1099
1100 if (a->type != b->type)
1101 return false;
1102
1103 if (a->uid_set != b->uid_set ||
1104 (a->uid_set && a->uid != b->uid))
1105 return false;
1106
1107 if (a->gid_set != b->gid_set ||
1108 (a->gid_set && a->gid != b->gid))
1109 return false;
1110
1111 if (a->mode_set != b->mode_set ||
1112 (a->mode_set && a->mode != b->mode))
1113 return false;
1114
1115 if (a->age_set != b->age_set ||
1116 (a->age_set && a->age != b->age))
1117 return false;
1118
31ed59c5
LP
1119 if ((a->type == CREATE_FILE ||
1120 a->type == TRUNCATE_FILE ||
1121 a->type == WRITE_FILE ||
849958d1
LP
1122 a->type == CREATE_SYMLINK ||
1123 a->type == COPY_FILES) &&
1733ca54 1124 !streq_ptr(a->argument, b->argument))
468d726b
LP
1125 return false;
1126
1127 if ((a->type == CREATE_CHAR_DEVICE ||
1128 a->type == CREATE_BLOCK_DEVICE) &&
1129 a->major_minor != b->major_minor)
1130 return false;
1131
bfe95f35
LP
1132 return true;
1133}
1134
a2aced4a
DR
1135static bool should_include_path(const char *path) {
1136 char **prefix;
1137
abef3f91 1138 STRV_FOREACH(prefix, arg_exclude_prefixes)
5c795114
DR
1139 if (path_startswith(path, *prefix))
1140 return false;
a2aced4a 1141
abef3f91 1142 STRV_FOREACH(prefix, arg_include_prefixes)
a2aced4a
DR
1143 if (path_startswith(path, *prefix))
1144 return true;
a2aced4a 1145
5c795114
DR
1146 /* no matches, so we should include this path only if we
1147 * have no whitelist at all */
7bc040fa 1148 return strv_length(arg_include_prefixes) == 0;
a2aced4a
DR
1149}
1150
fba6e687 1151static int parse_line(const char *fname, unsigned line, const char *buffer) {
1731e34a
LP
1152
1153 static const Specifier specifier_table[] = {
1154 { 'm', specifier_machine_id, NULL },
1155 { 'b', specifier_boot_id, NULL },
1156 { 'H', specifier_host_name, NULL },
1157 { 'v', specifier_kernel_release, NULL },
1158 {}
1159 };
1160
cde684a2
LP
1161 _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
1162 _cleanup_(item_freep) Item *i = NULL;
7f2c1f4d 1163 Item *existing;
66ccd038 1164 char type;
bfe95f35 1165 Hashmap *h;
31ed59c5 1166 int r, n = -1;
3b63d2d3
LP
1167
1168 assert(fname);
1169 assert(line >= 1);
1170 assert(buffer);
1171
19fbec19 1172 r = sscanf(buffer,
c4708f13
ZJS
1173 "%ms %ms %ms %ms %ms %ms %n",
1174 &action,
1731e34a 1175 &path,
bd40a2d8
LP
1176 &mode,
1177 &user,
1178 &group,
468d726b 1179 &age,
19fbec19
ZJS
1180 &n);
1181 if (r < 2) {
3b63d2d3 1182 log_error("[%s:%u] Syntax error.", fname, line);
7f2c1f4d 1183 return -EIO;
5008d581
LP
1184 }
1185
2e78fa79
LP
1186 if (isempty(action)) {
1187 log_error("[%s:%u] Command too short '%s'.", fname, line, action);
c4708f13 1188 return -EINVAL;
2e78fa79
LP
1189 }
1190
1191 if (strlen(action) > 1 && !in_charset(action+1, "!+")) {
1192 log_error("[%s:%u] Unknown modifiers in command '%s'", fname, line, action);
1193 return -EINVAL;
1194 }
1195
1196 if (strchr(action+1, '!') && !arg_boot)
c4708f13
ZJS
1197 return 0;
1198
1199 type = action[0];
1200
1731e34a
LP
1201 i = new0(Item, 1);
1202 if (!i)
1203 return log_oom();
1204
2e78fa79
LP
1205 i->force = !!strchr(action+1, '+');
1206
1731e34a
LP
1207 r = specifier_printf(path, specifier_table, NULL, &i->path);
1208 if (r < 0) {
1209 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, path);
1210 return r;
1211 }
1212
31ed59c5
LP
1213 if (n >= 0) {
1214 n += strspn(buffer+n, WHITESPACE);
1215 if (buffer[n] != 0 && (buffer[n] != '-' || buffer[n+1] != 0)) {
1216 i->argument = unquote(buffer+n, "\"");
0d0f0c50
SL
1217 if (!i->argument)
1218 return log_oom();
31ed59c5
LP
1219 }
1220 }
1221
753615e8 1222 switch (type) {
468d726b 1223
777b87e7
MS
1224 case CREATE_FILE:
1225 case TRUNCATE_FILE:
1226 case CREATE_DIRECTORY:
1227 case TRUNCATE_DIRECTORY:
1228 case CREATE_FIFO:
1229 case IGNORE_PATH:
78a92a5a 1230 case IGNORE_DIRECTORY_PATH:
777b87e7
MS
1231 case REMOVE_PATH:
1232 case RECURSIVE_REMOVE_PATH:
e73a03e0 1233 case ADJUST_MODE:
777b87e7
MS
1234 case RELABEL_PATH:
1235 case RECURSIVE_RELABEL_PATH:
1236 break;
468d726b
LP
1237
1238 case CREATE_SYMLINK:
1239 if (!i->argument) {
2f3b873a
KS
1240 i->argument = strappend("/usr/share/factory", i->path);
1241 if (!i->argument)
1242 return log_oom();
468d726b
LP
1243 }
1244 break;
1245
31ed59c5
LP
1246 case WRITE_FILE:
1247 if (!i->argument) {
1248 log_error("[%s:%u] Write file requires argument.", fname, line);
7f2c1f4d 1249 return -EBADMSG;
31ed59c5
LP
1250 }
1251 break;
1252
849958d1
LP
1253 case COPY_FILES:
1254 if (!i->argument) {
2f3b873a
KS
1255 i->argument = strappend("/usr/share/factory", i->path);
1256 if (!i->argument)
1257 return log_oom();
849958d1
LP
1258 }
1259
1260 if (!path_is_absolute(i->argument)) {
1261 log_error("[%s:%u] Source path is not absolute.", fname, line);
1262 return -EBADMSG;
1263 }
1264
1265 path_kill_slashes(i->argument);
1266 break;
1267
468d726b
LP
1268 case CREATE_CHAR_DEVICE:
1269 case CREATE_BLOCK_DEVICE: {
1270 unsigned major, minor;
1271
1272 if (!i->argument) {
1273 log_error("[%s:%u] Device file requires argument.", fname, line);
7f2c1f4d 1274 return -EBADMSG;
468d726b
LP
1275 }
1276
1277 if (sscanf(i->argument, "%u:%u", &major, &minor) != 2) {
1278 log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i->argument);
7f2c1f4d 1279 return -EBADMSG;
468d726b
LP
1280 }
1281
1282 i->major_minor = makedev(major, minor);
1283 break;
1284 }
1285
777b87e7 1286 default:
753615e8 1287 log_error("[%s:%u] Unknown command type '%c'.", fname, line, type);
7f2c1f4d 1288 return -EBADMSG;
3b63d2d3 1289 }
468d726b 1290
a8d88783 1291 i->type = type;
3b63d2d3
LP
1292
1293 if (!path_is_absolute(i->path)) {
1294 log_error("[%s:%u] Path '%s' not absolute.", fname, line, i->path);
7f2c1f4d 1295 return -EBADMSG;
3b63d2d3
LP
1296 }
1297
1298 path_kill_slashes(i->path);
1299
a2aced4a 1300 if (!should_include_path(i->path))
7f2c1f4d 1301 return 0;
5008d581 1302
cf9a4abd 1303 if (arg_root) {
cde684a2
LP
1304 char *p;
1305
1306 p = strappend(arg_root, i->path);
cf9a4abd
MM
1307 if (!p)
1308 return log_oom();
1309
1310 free(i->path);
1311 i->path = p;
1312 }
1313
3b63d2d3 1314 if (user && !streq(user, "-")) {
4b67834e
LP
1315 const char *u = user;
1316
d05c5031 1317 r = get_user_creds(&u, &i->uid, NULL, NULL, NULL);
4b67834e 1318 if (r < 0) {
3b63d2d3 1319 log_error("[%s:%u] Unknown user '%s'.", fname, line, user);
7f2c1f4d 1320 return r;
3b63d2d3
LP
1321 }
1322
1323 i->uid_set = true;
1324 }
1325
1326 if (group && !streq(group, "-")) {
4b67834e
LP
1327 const char *g = group;
1328
1329 r = get_group_creds(&g, &i->gid);
1330 if (r < 0) {
3b63d2d3 1331 log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
7f2c1f4d 1332 return r;
3b63d2d3
LP
1333 }
1334
1335 i->gid_set = true;
1336 }
1337
1338 if (mode && !streq(mode, "-")) {
abef3f91 1339 const char *mm = mode;
3b63d2d3
LP
1340 unsigned m;
1341
abef3f91
LP
1342 if (*mm == '~') {
1343 i->mask_perms = true;
1344 mm++;
1345 }
1346
1347 if (sscanf(mm, "%o", &m) != 1) {
3b63d2d3 1348 log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
7f2c1f4d 1349 return -ENOENT;
3b63d2d3
LP
1350 }
1351
1352 i->mode = m;
1353 i->mode_set = true;
1354 } else
468d726b
LP
1355 i->mode =
1356 i->type == CREATE_DIRECTORY ||
1357 i->type == TRUNCATE_DIRECTORY ? 0755 : 0644;
3b63d2d3
LP
1358
1359 if (age && !streq(age, "-")) {
24f3a374
LP
1360 const char *a = age;
1361
1362 if (*a == '~') {
1363 i->keep_first_level = true;
1364 a++;
1365 }
1366
7f602784 1367 if (parse_sec(a, &i->age) < 0) {
3b63d2d3 1368 log_error("[%s:%u] Invalid age '%s'.", fname, line, age);
7f2c1f4d 1369 return -EBADMSG;
3b63d2d3
LP
1370 }
1371
1372 i->age_set = true;
1373 }
1374
bfe95f35
LP
1375 h = needs_glob(i->type) ? globs : items;
1376
468d726b
LP
1377 existing = hashmap_get(h, i->path);
1378 if (existing) {
bfe95f35
LP
1379
1380 /* Two identical items are fine */
1381 if (!item_equal(existing, i))
022707d9 1382 log_warning("Two or more conflicting lines for %s configured, ignoring.", i->path);
022707d9 1383
7f2c1f4d 1384 return 0;
bfe95f35
LP
1385 }
1386
468d726b 1387 r = hashmap_put(h, i->path, i);
f647962d
MS
1388 if (r < 0)
1389 return log_error_errno(r, "Failed to insert item %s: %m", i->path);
3b63d2d3 1390
7f2c1f4d 1391 i = NULL; /* avoid cleanup */
5008d581 1392
7f2c1f4d 1393 return 0;
5008d581
LP
1394}
1395
601185b4 1396static void help(void) {
522d4a49
LP
1397 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1398 "Creates, deletes and cleans up volatile and temporary files and directories.\n\n"
5c795114 1399 " -h --help Show this help\n"
eb9da376 1400 " --version Show package version\n"
5c795114
DR
1401 " --create Create marked files/directories\n"
1402 " --clean Clean up marked directories\n"
1403 " --remove Remove marked files/directories\n"
81815651 1404 " --boot Execute actions only safe at boot\n"
5c795114 1405 " --prefix=PATH Only apply rules that apply to paths with the specified prefix\n"
cf9a4abd
MM
1406 " --exclude-prefix=PATH Ignore rules that apply to paths with the specified prefix\n"
1407 " --root=PATH Operate on an alternate filesystem root\n",
3b63d2d3 1408 program_invocation_short_name);
3b63d2d3
LP
1409}
1410
1411static int parse_argv(int argc, char *argv[]) {
1412
1413 enum {
eb9da376 1414 ARG_VERSION = 0x100,
3b63d2d3
LP
1415 ARG_CREATE,
1416 ARG_CLEAN,
fba6e687 1417 ARG_REMOVE,
81815651 1418 ARG_BOOT,
5c795114
DR
1419 ARG_PREFIX,
1420 ARG_EXCLUDE_PREFIX,
cf9a4abd 1421 ARG_ROOT,
3b63d2d3
LP
1422 };
1423
1424 static const struct option options[] = {
5c795114 1425 { "help", no_argument, NULL, 'h' },
eb9da376 1426 { "version", no_argument, NULL, ARG_VERSION },
5c795114
DR
1427 { "create", no_argument, NULL, ARG_CREATE },
1428 { "clean", no_argument, NULL, ARG_CLEAN },
1429 { "remove", no_argument, NULL, ARG_REMOVE },
81815651 1430 { "boot", no_argument, NULL, ARG_BOOT },
5c795114
DR
1431 { "prefix", required_argument, NULL, ARG_PREFIX },
1432 { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX },
cf9a4abd 1433 { "root", required_argument, NULL, ARG_ROOT },
eb9da376 1434 {}
3b63d2d3
LP
1435 };
1436
1437 int c;
1438
1439 assert(argc >= 0);
1440 assert(argv);
1441
601185b4 1442 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
3b63d2d3
LP
1443
1444 switch (c) {
1445
1446 case 'h':
601185b4
ZJS
1447 help();
1448 return 0;
eb9da376
LP
1449
1450 case ARG_VERSION:
1451 puts(PACKAGE_STRING);
1452 puts(SYSTEMD_FEATURES);
3b63d2d3
LP
1453 return 0;
1454
1455 case ARG_CREATE:
1456 arg_create = true;
1457 break;
1458
1459 case ARG_CLEAN:
1460 arg_clean = true;
1461 break;
1462
1463 case ARG_REMOVE:
1464 arg_remove = true;
1465 break;
1466
81815651
ZJS
1467 case ARG_BOOT:
1468 arg_boot = true;
c4708f13
ZJS
1469 break;
1470
fba6e687 1471 case ARG_PREFIX:
7bc040fa 1472 if (strv_push(&arg_include_prefixes, optarg) < 0)
a2aced4a 1473 return log_oom();
fba6e687
LP
1474 break;
1475
5c795114 1476 case ARG_EXCLUDE_PREFIX:
7bc040fa 1477 if (strv_push(&arg_exclude_prefixes, optarg) < 0)
5c795114
DR
1478 return log_oom();
1479 break;
1480
cf9a4abd 1481 case ARG_ROOT:
753615e8 1482 free(arg_root);
cf9a4abd
MM
1483 arg_root = path_make_absolute_cwd(optarg);
1484 if (!arg_root)
1485 return log_oom();
753615e8 1486
cf9a4abd
MM
1487 path_kill_slashes(arg_root);
1488 break;
1489
3b63d2d3
LP
1490 case '?':
1491 return -EINVAL;
1492
1493 default:
eb9da376 1494 assert_not_reached("Unhandled option");
3b63d2d3 1495 }
3b63d2d3
LP
1496
1497 if (!arg_clean && !arg_create && !arg_remove) {
35b8ca3a 1498 log_error("You need to specify at least one of --clean, --create or --remove.");
3b63d2d3
LP
1499 return -EINVAL;
1500 }
1501
1502 return 1;
1503}
1504
fba6e687 1505static int read_config_file(const char *fn, bool ignore_enoent) {
1731e34a
LP
1506 _cleanup_fclose_ FILE *f = NULL;
1507 char line[LINE_MAX];
78a92a5a 1508 Iterator iterator;
1731e34a 1509 unsigned v = 0;
78a92a5a 1510 Item *i;
1731e34a 1511 int r;
fba6e687
LP
1512
1513 assert(fn);
1514
cf9a4abd 1515 r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &f);
fabe5c0e
LP
1516 if (r < 0) {
1517 if (ignore_enoent && r == -ENOENT)
fba6e687
LP
1518 return 0;
1519
8d3d7072 1520 return log_error_errno(r, "Failed to open '%s', ignoring: %m", fn);
fba6e687
LP
1521 }
1522
1731e34a
LP
1523 FOREACH_LINE(line, f, break) {
1524 char *l;
fba6e687
LP
1525 int k;
1526
fba6e687
LP
1527 v++;
1528
1529 l = strstrip(line);
1530 if (*l == '#' || *l == 0)
1531 continue;
1532
1731e34a
LP
1533 k = parse_line(fn, v, l);
1534 if (k < 0 && r == 0)
1535 r = k;
fba6e687
LP
1536 }
1537
78a92a5a
MS
1538 /* we have to determine age parameter for each entry of type X */
1539 HASHMAP_FOREACH(i, globs, iterator) {
1540 Iterator iter;
1541 Item *j, *candidate_item = NULL;
1542
1543 if (i->type != IGNORE_DIRECTORY_PATH)
1544 continue;
1545
1546 HASHMAP_FOREACH(j, items, iter) {
1547 if (j->type != CREATE_DIRECTORY && j->type != TRUNCATE_DIRECTORY)
1548 continue;
1549
1550 if (path_equal(j->path, i->path)) {
1551 candidate_item = j;
1552 break;
1553 }
1554
1555 if ((!candidate_item && path_startswith(i->path, j->path)) ||
1556 (candidate_item && path_startswith(j->path, candidate_item->path) && (fnmatch(i->path, j->path, FNM_PATHNAME | FNM_PERIOD) == 0)))
1557 candidate_item = j;
1558 }
1559
9ed2a35e 1560 if (candidate_item && candidate_item->age_set) {
78a92a5a
MS
1561 i->age = candidate_item->age;
1562 i->age_set = true;
1563 }
1564 }
1565
fba6e687
LP
1566 if (ferror(f)) {
1567 log_error("Failed to read from file %s: %m", fn);
1568 if (r == 0)
1569 r = -EIO;
1570 }
1571
fba6e687
LP
1572 return r;
1573}
1574
5008d581 1575int main(int argc, char *argv[]) {
fabe5c0e 1576 int r, k;
3b63d2d3
LP
1577 Item *i;
1578 Iterator iterator;
1579
fdcad0c2
LP
1580 r = parse_argv(argc, argv);
1581 if (r <= 0)
753615e8 1582 goto finish;
5008d581 1583
eb0ca9eb 1584 log_set_target(LOG_TARGET_AUTO);
5008d581
LP
1585 log_parse_environment();
1586 log_open();
1587
4c12626c
LP
1588 umask(0022);
1589
cc56fafe 1590 mac_selinux_init(NULL);
5008d581 1591
d5099efc
MS
1592 items = hashmap_new(&string_hash_ops);
1593 globs = hashmap_new(&string_hash_ops);
b8bb3e8f
LP
1594
1595 if (!items || !globs) {
fabe5c0e 1596 r = log_oom();
3b63d2d3
LP
1597 goto finish;
1598 }
1599
fabe5c0e 1600 r = 0;
5008d581 1601
fba6e687
LP
1602 if (optind < argc) {
1603 int j;
5008d581 1604
9125670f 1605 for (j = optind; j < argc; j++) {
fabe5c0e
LP
1606 k = read_config_file(argv[j], false);
1607 if (k < 0 && r == 0)
1608 r = k;
9125670f 1609 }
5008d581 1610
fba6e687 1611 } else {
fabe5c0e
LP
1612 _cleanup_strv_free_ char **files = NULL;
1613 char **f;
5008d581 1614
cf9a4abd 1615 r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs);
44143309 1616 if (r < 0) {
da927ba9 1617 log_error_errno(r, "Failed to enumerate tmpfiles.d files: %m");
44143309
KS
1618 goto finish;
1619 }
3b63d2d3 1620
772f8371 1621 STRV_FOREACH(f, files) {
fabe5c0e
LP
1622 k = read_config_file(*f, true);
1623 if (k < 0 && r == 0)
1624 r = k;
5008d581 1625 }
772f8371 1626 }
5008d581 1627
b8bb3e8f 1628 HASHMAP_FOREACH(i, globs, iterator)
21bdae12 1629 process_item(i);
b8bb3e8f 1630
3b63d2d3 1631 HASHMAP_FOREACH(i, items, iterator)
21bdae12 1632 process_item(i);
3b63d2d3 1633
5008d581 1634finish:
3b63d2d3
LP
1635 while ((i = hashmap_steal_first(items)))
1636 item_free(i);
1637
17b90525
LP
1638 while ((i = hashmap_steal_first(globs)))
1639 item_free(i);
1640
3b63d2d3 1641 hashmap_free(items);
b8bb3e8f 1642 hashmap_free(globs);
5008d581 1643
7bc040fa
LP
1644 free(arg_include_prefixes);
1645 free(arg_exclude_prefixes);
cf9a4abd 1646 free(arg_root);
a2aced4a 1647
17b90525
LP
1648 set_free_free(unix_sockets);
1649
cc56fafe 1650 mac_selinux_finish();
29003cff 1651
fabe5c0e 1652 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
5008d581 1653}