tmpfiles.c 41.1 KB
Newer Older
1 2 3 4 5
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/

/***
  This file is part of systemd.

6
  Copyright 2010 Lennart Poettering, Kay Sievers
7 8

  systemd is free software; you can redistribute it and/or modify it
9 10
  under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation; either version 2.1 of the License, or
11 12 13 14 15
  (at your option) any later version.

  systemd is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
  Lesser General Public License for more details.
17

18
  You should have received a copy of the GNU Lesser General Public License
19 20 21 22 23 24 25 26 27 28 29 30
  along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <limits.h>
#include <dirent.h>
#include <grp.h>
#include <pwd.h>
31 32 33 34 35 36 37 38
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <getopt.h>
#include <stdbool.h>
#include <time.h>
#include <sys/types.h>
#include <sys/param.h>
Lennart Poettering's avatar
Lennart Poettering committed
39 40
#include <glob.h>
#include <fnmatch.h>
41
#include <sys/capability.h>
42 43 44

#include "log.h"
#include "util.h"
45
#include "macro.h"
46
#include "missing.h"
47
#include "mkdir.h"
Kay Sievers's avatar
Kay Sievers committed
48
#include "path-util.h"
49 50
#include "strv.h"
#include "label.h"
51
#include "set.h"
Kay Sievers's avatar
Kay Sievers committed
52
#include "conf-files.h"
53
#include "capability.h"
54

Andreas Jaeger's avatar
Andreas Jaeger committed
55
/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates
56
 * them in the file system. This is intended to be used to create
57 58
 * properly owned directories beneath /tmp, /var/tmp, /run, which are
 * volatile and hence need to be recreated on bootup. */
59

60
typedef enum ItemType {
Lennart Poettering's avatar
Lennart Poettering committed
61
        /* These ones take file names */
62 63
        CREATE_FILE = 'f',
        TRUNCATE_FILE = 'F',
64
        WRITE_FILE = 'w',
65 66
        CREATE_DIRECTORY = 'd',
        TRUNCATE_DIRECTORY = 'D',
67
        CREATE_FIFO = 'p',
68 69 70
        CREATE_SYMLINK = 'L',
        CREATE_CHAR_DEVICE = 'c',
        CREATE_BLOCK_DEVICE = 'b',
Lennart Poettering's avatar
Lennart Poettering committed
71 72

        /* These ones take globs */
73
        IGNORE_PATH = 'x',
Michal Sekletar's avatar
Michal Sekletar committed
74
        IGNORE_DIRECTORY_PATH = 'X',
75
        REMOVE_PATH = 'r',
76
        RECURSIVE_REMOVE_PATH = 'R',
77
        RELABEL_PATH = 'z',
78
        RECURSIVE_RELABEL_PATH = 'Z'
79
} ItemType;
80 81

typedef struct Item {
82
        ItemType type;
83 84

        char *path;
85
        char *argument;
86 87
        uid_t uid;
        gid_t gid;
88 89 90
        mode_t mode;
        usec_t age;

91 92
        dev_t major_minor;

93 94 95 96
        bool uid_set:1;
        bool gid_set:1;
        bool mode_set:1;
        bool age_set:1;
97 98

        bool keep_first_level:1;
99 100
} Item;

Lennart Poettering's avatar
Lennart Poettering committed
101
static Hashmap *items = NULL, *globs = NULL;
102
static Set *unix_sockets = NULL;
103 104 105 106 107

static bool arg_create = false;
static bool arg_clean = false;
static bool arg_remove = false;

108 109
static const char *arg_prefix = NULL;

110 111 112 113 114
static const char conf_file_dirs[] =
        "/etc/tmpfiles.d\0"
        "/run/tmpfiles.d\0"
        "/usr/local/lib/tmpfiles.d\0"
        "/usr/lib/tmpfiles.d\0"
115
#ifdef HAVE_SPLIT_USR
116
        "/lib/tmpfiles.d\0"
117
#endif
118
        ;
119

120 121
#define MAX_DEPTH 256

122
static bool needs_glob(ItemType t) {
Michal Sekletar's avatar
Michal Sekletar committed
123
        return t == IGNORE_PATH || t == IGNORE_DIRECTORY_PATH || t == REMOVE_PATH || t == RECURSIVE_REMOVE_PATH || t == RELABEL_PATH || t == RECURSIVE_RELABEL_PATH;
Lennart Poettering's avatar
Lennart Poettering committed
124 125 126 127 128 129 130 131 132 133 134 135 136
}

static struct Item* find_glob(Hashmap *h, const char *match) {
        Item *j;
        Iterator i;

        HASHMAP_FOREACH(j, h, i)
                if (fnmatch(j->path, match, FNM_PATHNAME|FNM_PERIOD) == 0)
                        return j;

        return NULL;
}

137
static void load_unix_sockets(void) {
138
        FILE _cleanup_fclose_ *f = NULL;
139 140 141 142 143 144 145 146
        char line[LINE_MAX];

        if (unix_sockets)
                return;

        /* We maintain a cache of the sockets we found in
         * /proc/net/unix to speed things up a little. */

147 148
        unix_sockets = set_new(string_hash_func, string_compare_func);
        if (!unix_sockets)
149 150
                return;

151 152
        f = fopen("/proc/net/unix", "re");
        if (!f)
153 154
                return;

155 156
        /* Skip header */
        if (!fgets(line, sizeof(line), f))
157 158 159 160 161 162
                goto fail;

        for (;;) {
                char *p, *s;
                int k;

163
                if (!fgets(line, sizeof(line), f))
164 165 166 167
                        break;

                truncate_nl(line);

168 169 170 171 172
                p = strchr(line, ':');
                if (!p)
                        continue;

                if (strlen(p) < 37)
173 174
                        continue;

175
                p += 37;
176
                p += strspn(p, WHITESPACE);
177
                p += strcspn(p, WHITESPACE); /* skip one more word */
178 179 180 181 182
                p += strspn(p, WHITESPACE);

                if (*p != '/')
                        continue;

183 184
                s = strdup(p);
                if (!s)
185 186
                        goto fail;

187 188
                path_kill_slashes(s);

189 190
                k = set_put(unix_sockets, s);
                if (k < 0) {
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
                        free(s);

                        if (k != -EEXIST)
                                goto fail;
                }
        }

        return;

fail:
        set_free_free(unix_sockets);
        unix_sockets = NULL;
}

static bool unix_socket_alive(const char *fn) {
        assert(fn);

        load_unix_sockets();

        if (unix_sockets)
                return !!set_get(unix_sockets, (char*) fn);

        /* We don't know, so assume yes */
        return true;
}

217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
static int dir_is_mount_point(DIR *d, const char *subdir) {
        struct file_handle *h;
        int mount_id_parent, mount_id;
        int r_p, r;

        h = alloca(MAX_HANDLE_SZ);

        h->handle_bytes = MAX_HANDLE_SZ;
        r_p = name_to_handle_at(dirfd(d), ".", h, &mount_id_parent, 0);
        if (r_p < 0)
                r_p = -errno;

        h->handle_bytes = MAX_HANDLE_SZ;
        r = name_to_handle_at(dirfd(d), subdir, h, &mount_id, 0);
        if (r < 0)
                r = -errno;

        /* got no handle; make no assumptions, return error */
        if (r_p < 0 && r < 0)
                return r_p;

        /* got both handles; if they differ, it is a mount point */
        if (r_p >= 0 && r >= 0)
                return mount_id_parent != mount_id;

        /* got only one handle; assume different mount points if one
         * of both queries was not supported by the filesystem */
        if (r_p == -ENOSYS || r_p == -ENOTSUP || r == -ENOSYS || r == -ENOTSUP)
                return true;

        /* return error */
        if (r_p < 0)
                return r_p;
        return r;
}

253
static int dir_cleanup(
Michal Sekletar's avatar
Michal Sekletar committed
254
                Item *i,
255 256 257 258 259 260
                const char *p,
                DIR *d,
                const struct stat *ds,
                usec_t cutoff,
                dev_t rootdev,
                bool mountpoint,
261 262
                int maxdepth,
                bool keep_this_level)
263 264 265 266 267 268 269 270 271
{
        struct dirent *dent;
        struct timespec times[2];
        bool deleted = false;
        int r = 0;

        while ((dent = readdir(d))) {
                struct stat s;
                usec_t age;
272
                char _cleanup_free_ *sub_path = NULL;
273 274 275 276

                if (streq(dent->d_name, ".") ||
                    streq(dent->d_name, ".."))
                        continue;
277

278
                if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) {
279

280 281 282 283 284 285 286 287 288 289 290 291
                        if (errno != ENOENT) {
                                log_error("stat(%s/%s) failed: %m", p, dent->d_name);
                                r = -errno;
                        }

                        continue;
                }

                /* Stay on the same filesystem */
                if (s.st_dev != rootdev)
                        continue;

292 293 294 295 296 297
                /* Try to detect bind mounts of the same filesystem instance; they
                 * do not differ in device major/minors. This type of query is not
                 * supported on all kernels or filesystem types though. */
                if (S_ISDIR(s.st_mode) && dir_is_mount_point(d, dent->d_name) > 0)
                        continue;

298 299 300 301 302
                /* Do not delete read-only files owned by root */
                if (s.st_uid == 0 && !(s.st_mode & S_IWUSR))
                        continue;

                if (asprintf(&sub_path, "%s/%s", p, dent->d_name) < 0) {
303
                        r = log_oom();
304 305 306 307 308 309 310
                        goto finish;
                }

                /* Is there an item configured for this path? */
                if (hashmap_get(items, sub_path))
                        continue;

Lennart Poettering's avatar
Lennart Poettering committed
311 312 313
                if (find_glob(globs, sub_path))
                        continue;

314 315 316 317 318 319 320 321 322 323
                if (S_ISDIR(s.st_mode)) {

                        if (mountpoint &&
                            streq(dent->d_name, "lost+found") &&
                            s.st_uid == 0)
                                continue;

                        if (maxdepth <= 0)
                                log_warning("Reached max depth on %s.", sub_path);
                        else {
324
                                DIR _cleanup_closedir_ *sub_dir;
325 326
                                int q;

327
                                sub_dir = xopendirat(dirfd(d), dent->d_name, O_NOFOLLOW|O_NOATIME);
328 329 330 331 332 333 334 335 336
                                if (sub_dir == NULL) {
                                        if (errno != ENOENT) {
                                                log_error("opendir(%s/%s) failed: %m", p, dent->d_name);
                                                r = -errno;
                                        }

                                        continue;
                                }

Michal Sekletar's avatar
Michal Sekletar committed
337
                                q = dir_cleanup(i, sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1, false);
338 339 340 341 342

                                if (q < 0)
                                        r = q;
                        }

343 344 345 346 347 348 349 350 351 352
                        /* Note: if you are wondering why we don't
                         * support the sticky bit for excluding
                         * directories from cleaning like we do it for
                         * other file system objects: well, the sticky
                         * bit already has a meaning for directories,
                         * so we don't want to overload that. */

                        if (keep_this_level)
                                continue;

353 354 355 356 357 358
                        /* Ignore ctime, we change it when deleting */
                        age = MAX(timespec_load(&s.st_mtim),
                                  timespec_load(&s.st_atim));
                        if (age >= cutoff)
                                continue;

359
                        if (i->type != IGNORE_DIRECTORY_PATH || !streq(dent->d_name, p)) {
Michal Sekletar's avatar
Michal Sekletar committed
360
                                log_debug("rmdir '%s'\n", sub_path);
361

Michal Sekletar's avatar
Michal Sekletar committed
362 363 364 365 366
                                if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0) {
                                        if (errno != ENOENT && errno != ENOTEMPTY) {
                                                log_error("rmdir(%s): %m", sub_path);
                                                r = -errno;
                                        }
367 368 369 370
                                }
                        }

                } else {
371 372 373 374 375 376 377
                        /* Skip files for which the sticky bit is
                         * set. These are semantics we define, and are
                         * unknown elsewhere. See XDG_RUNTIME_DIR
                         * specification for details. */
                        if (s.st_mode & S_ISVTX)
                                continue;

378
                        if (mountpoint && S_ISREG(s.st_mode)) {
379 380 381 382 383 384 385 386 387
                                if (streq(dent->d_name, ".journal") &&
                                    s.st_uid == 0)
                                        continue;

                                if (streq(dent->d_name, "aquota.user") ||
                                    streq(dent->d_name, "aquota.group"))
                                        continue;
                        }

388 389 390 391
                        /* Ignore sockets that are listed in /proc/net/unix */
                        if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path))
                                continue;

392 393 394 395
                        /* Ignore device nodes */
                        if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode))
                                continue;

396 397 398 399 400
                        /* Keep files on this level around if this is
                         * requested */
                        if (keep_this_level)
                                continue;

401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
                        age = MAX3(timespec_load(&s.st_mtim),
                                   timespec_load(&s.st_atim),
                                   timespec_load(&s.st_ctim));

                        if (age >= cutoff)
                                continue;

                        log_debug("unlink '%s'\n", sub_path);

                        if (unlinkat(dirfd(d), dent->d_name, 0) < 0) {
                                if (errno != ENOENT) {
                                        log_error("unlink(%s): %m", sub_path);
                                        r = -errno;
                                }
                        }

                        deleted = true;
                }
        }

finish:
        if (deleted) {
                /* Restore original directory timestamps */
                times[0] = ds->st_atim;
                times[1] = ds->st_mtim;

                if (futimens(dirfd(d), times) < 0)
                        log_error("utimensat(%s): %m", p);
        }

        return r;
}

434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
static int item_set_perms(Item *i, const char *path) {
        /* not using i->path directly because it may be a glob */
        if (i->mode_set)
                if (chmod(path, i->mode) < 0) {
                        log_error("chmod(%s) failed: %m", path);
                        return -errno;
                }

        if (i->uid_set || i->gid_set)
                if (chown(path,
                          i->uid_set ? i->uid : (uid_t) -1,
                          i->gid_set ? i->gid : (gid_t) -1) < 0) {

                        log_error("chown(%s) failed: %m", path);
                        return -errno;
                }

451
        return label_fix(path, false, false);
452 453
}

454 455 456 457 458 459 460
static int write_one_file(Item *i, const char *path) {
        int r, e, fd, flags;
        struct stat st;

        flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND :
                i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC : 0;

461 462 463 464 465 466 467
        RUN_WITH_UMASK(0) {
                label_context_set(path, S_IFREG);
                fd = open(path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY|O_NOFOLLOW, i->mode);
                e = errno;
                label_context_clear();
                errno = e;
        }
468 469 470 471 472 473 474 475 476 477 478 479

        if (fd < 0) {
                if (i->type == WRITE_FILE && errno == ENOENT)
                        return 0;

                log_error("Failed to create file %s: %m", path);
                return -errno;
        }

        if (i->argument) {
                ssize_t n;
                size_t l;
480
                _cleanup_free_ char *unescaped;
481

482
                unescaped = cunescape(i->argument);
483 484
                if (unescaped == NULL) {
                        close_nointr_nofail(fd);
485
                        return log_oom();
486
                }
487

488 489
                l = strlen(unescaped);
                n = write(fd, unescaped, l);
490 491 492 493 494 495 496 497

                if (n < 0 || (size_t) n < l) {
                        log_error("Failed to write file %s: %s", path, n < 0 ? strerror(-n) : "Short write");
                        close_nointr_nofail(fd);
                        return n < 0 ? n : -EIO;
                }
        }

498 499
        close_nointr_nofail(fd);

500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
        if (stat(path, &st) < 0) {
                log_error("stat(%s) failed: %m", path);
                return -errno;
        }

        if (!S_ISREG(st.st_mode)) {
                log_error("%s is not a file.", path);
                return -EEXIST;
        }

        r = item_set_perms(i, path);
        if (r < 0)
                return r;

        return 0;
}

517
static int recursive_relabel_children(Item *i, const char *path) {
518
        DIR _cleanup_closedir_ *d;
519 520 521 522 523 524 525 526 527 528
        int ret = 0;

        /* This returns the first error we run into, but nevertheless
         * tries to go on */

        d = opendir(path);
        if (!d)
                return errno == ENOENT ? 0 : -errno;

        for (;;) {
529 530
                struct dirent *de;
                union dirent_storage buf;
531 532
                bool is_dir;
                int r;
533
                char _cleanup_free_ *entry_path = NULL;
534

535
                r = readdir_r(d, &buf.de, &de);
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
                if (r != 0) {
                        if (ret == 0)
                                ret = -r;
                        break;
                }

                if (!de)
                        break;

                if (streq(de->d_name, ".") || streq(de->d_name, ".."))
                        continue;

                if (asprintf(&entry_path, "%s/%s", path, de->d_name) < 0) {
                        if (ret == 0)
                                ret = -ENOMEM;
                        continue;
                }

                if (de->d_type == DT_UNKNOWN) {
                        struct stat st;

                        if (lstat(entry_path, &st) < 0) {
                                if (ret == 0 && errno != ENOENT)
                                        ret = -errno;
                                continue;
                        }

                        is_dir = S_ISDIR(st.st_mode);

                } else
                        is_dir = de->d_type == DT_DIR;

568
                r = item_set_perms(i, entry_path);
569 570 571 572 573 574 575
                if (r < 0) {
                        if (ret == 0 && r != -ENOENT)
                                ret = r;
                        continue;
                }

                if (is_dir) {
576
                        r = recursive_relabel_children(i, entry_path);
577 578 579 580 581 582 583 584 585 586 587 588
                        if (r < 0 && ret == 0)
                                ret = r;
                }
        }

        return ret;
}

static int recursive_relabel(Item *i, const char *path) {
        int r;
        struct stat st;

589
        r = item_set_perms(i, path);
590 591 592 593 594 595 596
        if (r < 0)
                return r;

        if (lstat(path, &st) < 0)
                return -errno;

        if (S_ISDIR(st.st_mode))
597
                r = recursive_relabel_children(i, path);
598 599 600 601

        return r;
}

602 603
static int glob_item(Item *i, int (*action)(Item *, const char *)) {
        int r = 0, k;
604
        glob_t _cleanup_globfree_ g = {};
605 606 607
        char **fn;

        errno = 0;
608 609
        k = glob(i->path, GLOB_NOSORT|GLOB_BRACE, NULL, &g);
        if (k != 0)
610
                if (k != GLOB_NOMATCH) {
611
                        if (errno > 0)
612 613 614 615 616 617
                                errno = EIO;

                        log_error("glob(%s) failed: %m", i->path);
                        return -errno;
                }

618 619 620
        STRV_FOREACH(fn, g.gl_pathv) {
                k = action(i, *fn);
                if (k < 0)
621
                        r = k;
622
        }
623 624 625 626

        return r;
}

627
static int create_item(Item *i) {
628
        int r, e;
629
        struct stat st;
630

631
        assert(i);
632

633 634 635
        switch (i->type) {

        case IGNORE_PATH:
Michal Sekletar's avatar
Michal Sekletar committed
636
        case IGNORE_DIRECTORY_PATH:
637 638 639
        case REMOVE_PATH:
        case RECURSIVE_REMOVE_PATH:
                return 0;
640

641
        case CREATE_FILE:
642
        case TRUNCATE_FILE:
643 644 645 646
                r = write_one_file(i, i->path);
                if (r < 0)
                        return r;
                break;
647 648
        case WRITE_FILE:
                r = glob_item(i, write_one_file);
649 650
                if (r < 0)
                        return r;
651

652 653 654 655
                break;

        case TRUNCATE_DIRECTORY:
        case CREATE_DIRECTORY:
656

657 658 659 660
                RUN_WITH_UMASK(0000) {
                        mkdir_parents_label(i->path, 0755);
                        r = mkdir(i->path, i->mode);
                }
661 662

                if (r < 0 && errno != EEXIST) {
663
                        log_error("Failed to create directory %s: %m", i->path);
664
                        return -errno;
665 666
                }

667 668
                if (stat(i->path, &st) < 0) {
                        log_error("stat(%s) failed: %m", i->path);
669
                        return -errno;
670 671 672
                }

                if (!S_ISDIR(st.st_mode)) {
673
                        log_error("%s is not a directory.", i->path);
674
                        return -EEXIST;
675 676
                }

677
                r = item_set_perms(i, i->path);
678 679
                if (r < 0)
                        return r;
680 681

                break;
682 683 684

        case CREATE_FIFO:

685 686 687
                RUN_WITH_UMASK(0000) {
                        r = mkfifo(i->path, i->mode);
                }
688 689 690

                if (r < 0 && errno != EEXIST) {
                        log_error("Failed to create fifo %s: %m", i->path);
691
                        return -errno;
692 693 694 695
                }

                if (stat(i->path, &st) < 0) {
                        log_error("stat(%s) failed: %m", i->path);
696
                        return -errno;
697 698 699 700
                }

                if (!S_ISFIFO(st.st_mode)) {
                        log_error("%s is not a fifo.", i->path);
701
                        return -EEXIST;
702 703
                }

704
                r = item_set_perms(i, i->path);
705 706
                if (r < 0)
                        return r;
707 708

                break;
709

710 711 712
        case CREATE_SYMLINK: {
                char *x;

713
                label_context_set(i->path, S_IFLNK);
714
                r = symlink(i->argument, i->path);
715 716 717 718
                e = errno;
                label_context_clear();
                errno = e;

719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741
                if (r < 0 && errno != EEXIST) {
                        log_error("symlink(%s, %s) failed: %m", i->argument, i->path);
                        return -errno;
                }

                r = readlink_malloc(i->path, &x);
                if (r < 0) {
                        log_error("readlink(%s) failed: %s", i->path, strerror(-r));
                        return -errno;
                }

                if (!streq(i->argument, x)) {
                        free(x);
                        log_error("%s is not the right symlinks.", i->path);
                        return -EEXIST;
                }

                free(x);
                break;
        }

        case CREATE_BLOCK_DEVICE:
        case CREATE_CHAR_DEVICE: {
742 743 744 745
                mode_t file_type;

                if (have_effective_cap(CAP_MKNOD) == 0) {
                        /* In a container we lack CAP_MKNOD. We
746
                        shouldn't attempt to create the device node in
747 748 749 750 751 752 753 754
                        that case to avoid noise, and we don't support
                        virtualized devices in containers anyway. */

                        log_debug("We lack CAP_MKNOD, skipping creation of device node %s.", i->path);
                        return 0;
                }

                file_type = (i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR);
755

756 757 758 759 760 761 762
                RUN_WITH_UMASK(0000) {
                        label_context_set(i->path, file_type);
                        r = mknod(i->path, i->mode | file_type, i->major_minor);
                        e = errno;
                        label_context_clear();
                        errno = e;
                }
763 764 765 766 767 768 769 770 771 772 773

                if (r < 0 && errno != EEXIST) {
                        log_error("Failed to create device node %s: %m", i->path);
                        return -errno;
                }

                if (stat(i->path, &st) < 0) {
                        log_error("stat(%s) failed: %m", i->path);
                        return -errno;
                }

774
                if ((st.st_mode & S_IFMT) != file_type) {
775 776 777 778 779 780 781 782 783 784 785
                        log_error("%s is not a device node.", i->path);
                        return -EEXIST;
                }

                r = item_set_perms(i, i->path);
                if (r < 0)
                        return r;

                break;
        }

786 787 788 789 790 791 792
        case RELABEL_PATH:

                r = glob_item(i, item_set_perms);
                if (r < 0)
                        return 0;
                break;

793 794 795 796 797
        case RECURSIVE_RELABEL_PATH:

                r = glob_item(i, recursive_relabel);
                if (r < 0)
                        return r;
798 799 800 801
        }

        log_debug("%s created successfully.", i->path);

802
        return 0;
803 804
}

805
static int remove_item_instance(Item *i, const char *instance) {
806 807 808 809 810 811 812 813 814
        int r;

        assert(i);

        switch (i->type) {

        case CREATE_FILE:
        case TRUNCATE_FILE:
        case CREATE_DIRECTORY:
815
        case CREATE_FIFO:
816 817 818
        case CREATE_SYMLINK:
        case CREATE_BLOCK_DEVICE:
        case CREATE_CHAR_DEVICE:
819
        case IGNORE_PATH:
Michal Sekletar's avatar
Michal Sekletar committed
820
        case IGNORE_DIRECTORY_PATH:
821
        case RELABEL_PATH:
822
        case RECURSIVE_RELABEL_PATH:
823
        case WRITE_FILE:
824 825 826
                break;

        case REMOVE_PATH:
Lennart Poettering's avatar
Lennart Poettering committed
827 828
                if (remove(instance) < 0 && errno != ENOENT) {
                        log_error("remove(%s): %m", instance);
829
                        return -errno;
830
                }
831 832 833 834 835

                break;

        case TRUNCATE_DIRECTORY:
        case RECURSIVE_REMOVE_PATH:
Lennart Poettering's avatar
Lennart Poettering committed
836 837
                /* FIXME: we probably should use dir_cleanup() here
                 * instead of rm_rf() so that 'x' is honoured. */
838
                r = rm_rf_dangerous(instance, false, i->type == RECURSIVE_REMOVE_PATH, false);
839
                if (r < 0 && r != -ENOENT) {
Lennart Poettering's avatar
Lennart Poettering committed
840
                        log_error("rm_rf(%s): %s", instance, strerror(-r));
841 842 843 844 845 846 847 848 849
                        return r;
                }

                break;
        }

        return 0;
}

850
static int remove_item(Item *i) {
851 852
        int r = 0;

Lennart Poettering's avatar
Lennart Poettering committed
853 854 855 856 857 858 859
        assert(i);

        switch (i->type) {

        case CREATE_FILE:
        case TRUNCATE_FILE:
        case CREATE_DIRECTORY:
860
        case CREATE_FIFO:
861 862 863
        case CREATE_SYMLINK:
        case CREATE_CHAR_DEVICE:
        case CREATE_BLOCK_DEVICE:
Lennart Poettering's avatar
Lennart Poettering committed
864
        case IGNORE_PATH:
Michal Sekletar's avatar
Michal Sekletar committed
865
        case IGNORE_DIRECTORY_PATH:
866
        case RELABEL_PATH:
867
        case RECURSIVE_RELABEL_PATH:
868
        case WRITE_FILE:
Lennart Poettering's avatar
Lennart Poettering committed
869 870 871 872
                break;

        case REMOVE_PATH:
        case TRUNCATE_DIRECTORY:
873 874 875
        case RECURSIVE_REMOVE_PATH:
                r = glob_item(i, remove_item_instance);
                break;
Lennart Poettering's avatar
Lennart Poettering committed
876 877
        }

878
        return r;
Lennart Poettering's avatar
Lennart Poettering committed
879 880
}

Michal Sekletar's avatar
Michal Sekletar committed
881
static int clean_item_instance(Item *i, const char* instance) {
882
        DIR _cleanup_closedir_ *d = NULL;
Michal Sekletar's avatar
Michal Sekletar committed
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909
        struct stat s, ps;
        bool mountpoint;
        int r;
        usec_t cutoff, n;

        assert(i);

        if (!i->age_set)
                return 0;

        n = now(CLOCK_REALTIME);
        if (n < i->age)
                return 0;

        cutoff = n - i->age;

        d = opendir(instance);
        if (!d) {
                if (errno == ENOENT || errno == ENOTDIR)
                        return 0;

                log_error("Failed to open directory %s: %m", i->path);
                return -errno;
        }

        if (fstat(dirfd(d), &s) < 0) {
                log_error("stat(%s) failed: %m", i->path);
910
                return -errno;
Michal Sekletar's avatar
Michal Sekletar committed
911 912 913 914
        }

        if (!S_ISDIR(s.st_mode)) {
                log_error("%s is not a directory.", i->path);
915
                return -ENOTDIR;
Michal Sekletar's avatar
Michal Sekletar committed
916 917 918 919
        }

        if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) {
                log_error("stat(%s/..) failed: %m", i->path);
920
                return -errno;
Michal Sekletar's avatar
Michal Sekletar committed
921 922 923 924 925
        }

        mountpoint = s.st_dev != ps.st_dev ||
                     (s.st_dev == ps.st_dev && s.st_ino == ps.st_ino);

926 927
        r = dir_cleanup(i, instance, d, &s, cutoff, s.st_dev, mountpoint,
                        MAX_DEPTH, i->keep_first_level);
Michal Sekletar's avatar
Michal Sekletar committed
928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951
        return r;
}

static int clean_item(Item *i) {
        int r = 0;

        assert(i);

        switch (i->type) {
        case CREATE_DIRECTORY:
        case TRUNCATE_DIRECTORY:
        case IGNORE_PATH:
                clean_item_instance(i, i->path);
                break;
        case IGNORE_DIRECTORY_PATH:
                r = glob_item(i, clean_item_instance);
                break;
        default:
                break;
        }

        return r;
}

952 953 954 955 956 957
static int process_item(Item *i) {
        int r, q, p;

        assert(i);

        r = arg_create ? create_item(i) : 0;
958
        q = arg_remove ? remove_item(i) : 0;
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973
        p = arg_clean ? clean_item(i) : 0;

        if (r < 0)
                return r;

        if (q < 0)
                return q;

        return p;
}

static void item_free(Item *i) {
        assert(i);

        free(i->path);
974
        free(i->argument);
975 976 977
        free(i);
}

978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
static bool item_equal(Item *a, Item *b) {
        assert(a);
        assert(b);

        if (!streq_ptr(a->path, b->path))
                return false;

        if (a->type != b->type)
                return false;

        if (a->uid_set != b->uid_set ||
            (a->uid_set && a->uid != b->uid))
            return false;

        if (a->gid_set != b->gid_set ||
            (a->gid_set && a->gid != b->gid))
            return false;

        if (a->mode_set != b->mode_set ||
            (a->mode_set && a->mode != b->mode))
            return false;

        if (a->age_set != b->age_set ||
            (a->age_set && a->age != b->age))
            return false;

1004 1005 1006 1007
        if ((a->type == CREATE_FILE ||
             a->type == TRUNCATE_FILE ||
             a->type == WRITE_FILE ||
             a->type == CREATE_SYMLINK) &&
1008
            !streq_ptr(a->argument, b->argument))
1009 1010 1011 1012 1013 1014 1015
                return false;

        if ((a->type == CREATE_CHAR_DEVICE ||
             a->type == CREATE_BLOCK_DEVICE) &&
            a->major_minor != b->major_minor)
                return false;

1016 1017 1018
        return true;
}

1019
static int parse_line(const char *fname, unsigned line, const char *buffer) {
1020 1021
        Item _cleanup_free_ *i = NULL;
        Item *existing;
1022 1023
        char _cleanup_free_
                *mode = NULL, *user = NULL, *group = NULL, *age = NULL;
1024
        char type;
1025
        Hashmap *h;
1026
        int r, n = -1;
1027 1028 1029 1030 1031

        assert(fname);
        assert(line >= 1);
        assert(buffer);

1032
        i = new0(Item, 1);
1033 1034
        if (!i)
                return log_oom();
1035

1036 1037
        r = sscanf(buffer,
                   "%c %ms %ms %ms %ms %ms %n",
1038
                   &type,
1039 1040 1041 1042
                   &i->path,
                   &mode,
                   &user,
                   &group,
1043
                   &age,
1044 1045
                   &n);
        if (r < 2) {
1046
                log_error("[%s:%u] Syntax error.", fname, line);
1047
                return -EIO;
1048 1049
        }

1050 1051 1052 1053
        if (n >= 0)  {
                n += strspn(buffer+n, WHITESPACE);
                if (buffer[n] != 0 && (buffer[n] != '-' || buffer[n+1] != 0)) {
                        i->argument = unquote(buffer+n, "\"");
1054 1055
                        if (!i->argument)
                                return log_oom();
1056 1057 1058
                }
        }

1059
        switch(type) {
1060

1061 1062 1063 1064 1065 1066
        case CREATE_FILE:
        case TRUNCATE_FILE:
        case CREATE_DIRECTORY:
        case TRUNCATE_DIRECTORY:
        case CREATE_FIFO:
        case IGNORE_PATH:
Michal Sekletar's avatar
Michal Sekletar committed
1067
        case IGNORE_DIRECTORY_PATH:
1068 1069 1070 1071 1072
        case REMOVE_PATH:
        case RECURSIVE_REMOVE_PATH:
        case RELABEL_PATH:
        case RECURSIVE_RELABEL_PATH:
                break;
1073 1074 1075 1076

        case CREATE_SYMLINK:
                if (!i->argument) {
                        log_error("[%s:%u] Symlink file requires argument.", fname, line);
1077
                        return -EBADMSG;
1078 1079 1080
                }
                break;

1081 1082 1083
        case WRITE_FILE:
                if (!i->argument) {
                        log_error("[%s:%u] Write file requires argument.", fname, line);
1084
                        return -EBADMSG;
1085 1086 1087
                }
                break;

1088 1089 1090 1091 1092 1093
        case CREATE_CHAR_DEVICE:
        case CREATE_BLOCK_DEVICE: {
                unsigned major, minor;

                if (!i->argument) {
                        log_error("[%s:%u] Device file requires argument.", fname, line);
1094
                        return -EBADMSG;
1095 1096 1097 1098
                }

                if (sscanf(i->argument, "%u:%u", &major, &minor) != 2) {
                        log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i->argument);
1099
                        return -EBADMSG;
1100 1101 1102 1103 1104 1105
                }

                i->major_minor = makedev(major, minor);
                break;
        }

1106
        default:
1107
                log_error("[%s:%u] Unknown file type '%c'.", fname, line, type);
1108
                return -EBADMSG;
1109
        }
1110

1111
        i->type = type;
1112 1113 1114

        if (!path_is_absolute(i->path)) {
                log_error("[%s:%u] Path '%s' not absolute.", fname, line, i->path);
1115
                return -EBADMSG;
1116 1117 1118 1119
        }

        path_kill_slashes(i->path);

1120 1121
        if (arg_prefix && !path_startswith(i->path, arg_prefix))
                return 0;
1122

1123
        if (user && !streq(user, "-")) {
1124 1125
                const char *u = user;

1126
                r = get_user_creds(&u, &i->uid, NULL, NULL, NULL);
1127
                if (r < 0) {
1128
                        log_error("[%s:%u] Unknown user '%s'.", fname, line, user);
1129
                        return r;
1130 1131 1132 1133 1134 1135
                }

                i->uid_set = true;
        }

        if (group && !streq(group, "-")) {
1136 1137 1138 1139
                const char *g = group;

                r = get_group_creds(&g, &i->gid);
                if (r < 0) {
1140
                        log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
1141
                        return r;
1142 1143 1144 1145 1146 1147 1148 1149 1150 1151
                }

                i->gid_set = true;
        }

        if (mode && !streq(mode, "-")) {
                unsigned m;

                if (sscanf(mode, "%o", &m) != 1) {
                        log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
1152
                        return -ENOENT;
1153 1154 1155 1156 1157
                }

                i->mode = m;
                i->mode_set = true;
        } else
1158 1159 1160
                i->mode =
                        i->type == CREATE_DIRECTORY ||
                        i->type == TRUNCATE_DIRECTORY ? 0755 : 0644;