• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 20447389715

21 Dec 2025 07:31PM UTC coverage: 72.37% (-0.1%) from 72.5%
20447389715

push

github

DaanDeMeyer
mkosi: Use initrd as exitrd

Let's speed up image builds by avoiding building
an exitrd and instead reusing the initrd image for
the same purpose.

308584 of 426400 relevant lines covered (72.37%)

1134231.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.59
/src/tmpfiles/tmpfiles.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <fcntl.h>
4
#include <fnmatch.h>
5
#include <getopt.h>
6
#include <sys/file.h>
7
#include <sysexits.h>
8
#include <time.h>
9
#include <unistd.h>
10

11
#include "sd-path.h"
12

13
#include "acl-util.h"
14
#include "alloc-util.h"
15
#include "bitfield.h"
16
#include "btrfs-util.h"
17
#include "build.h"
18
#include "capability-util.h"
19
#include "chase.h"
20
#include "chattr-util.h"
21
#include "conf-files.h"
22
#include "constants.h"
23
#include "copy.h"
24
#include "creds-util.h"
25
#include "devnum-util.h"
26
#include "dirent-util.h"
27
#include "dissect-image.h"
28
#include "env-util.h"
29
#include "errno-util.h"
30
#include "escape.h"
31
#include "extract-word.h"
32
#include "fd-util.h"
33
#include "fileio.h"
34
#include "format-util.h"
35
#include "fs-util.h"
36
#include "glob-util.h"
37
#include "hexdecoct.h"
38
#include "image-policy.h"
39
#include "io-util.h"
40
#include "label-util.h"
41
#include "log.h"
42
#include "loop-util.h"
43
#include "main-func.h"
44
#include "mkdir-label.h"
45
#include "mount-util.h"
46
#include "mountpoint-util.h"
47
#include "offline-passwd.h"
48
#include "pager.h"
49
#include "parse-argument.h"
50
#include "parse-util.h"
51
#include "path-lookup.h"
52
#include "path-util.h"
53
#include "pretty-print.h"
54
#include "rlimit-util.h"
55
#include "rm-rf.h"
56
#include "selinux-util.h"
57
#include "set.h"
58
#include "sort-util.h"
59
#include "specifier.h"
60
#include "stat-util.h"
61
#include "string-table.h"
62
#include "string-util.h"
63
#include "strv.h"
64
#include "sysctl-util.h"
65
#include "time-util.h"
66
#include "umask-util.h"
67
#include "user-util.h"
68
#include "verbs.h"
69
#include "virt.h"
70
#include "xattr-util.h"
71

72
/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates
73
 * them in the file system. This is intended to be used to create
74
 * properly owned directories beneath /tmp, /var/tmp, /run, which are
75
 * volatile and hence need to be recreated on bootup. */
76

77
typedef enum OperationMask {
78
        OPERATION_CREATE = 1 << 0,
79
        OPERATION_REMOVE = 1 << 1,
80
        OPERATION_CLEAN  = 1 << 2,
81
        OPERATION_PURGE  = 1 << 3,
82
} OperationMask;
83

84
typedef enum ItemType {
85
        /* These ones take file names */
86
        CREATE_FILE                    = 'f',
87
        TRUNCATE_FILE                  = 'F', /* deprecated: use f+ */
88
        CREATE_DIRECTORY               = 'd',
89
        TRUNCATE_DIRECTORY             = 'D',
90
        CREATE_SUBVOLUME               = 'v',
91
        CREATE_SUBVOLUME_INHERIT_QUOTA = 'q',
92
        CREATE_SUBVOLUME_NEW_QUOTA     = 'Q',
93
        CREATE_FIFO                    = 'p',
94
        CREATE_SYMLINK                 = 'L',
95
        CREATE_CHAR_DEVICE             = 'c',
96
        CREATE_BLOCK_DEVICE            = 'b',
97
        COPY_FILES                     = 'C',
98

99
        /* These ones take globs */
100
        WRITE_FILE                     = 'w',
101
        EMPTY_DIRECTORY                = 'e',
102
        SET_XATTR                      = 't',
103
        RECURSIVE_SET_XATTR            = 'T',
104
        SET_ACL                        = 'a',
105
        RECURSIVE_SET_ACL              = 'A',
106
        SET_ATTRIBUTE                  = 'h',
107
        RECURSIVE_SET_ATTRIBUTE        = 'H',
108
        IGNORE_PATH                    = 'x',
109
        IGNORE_DIRECTORY_PATH          = 'X',
110
        REMOVE_PATH                    = 'r',
111
        RECURSIVE_REMOVE_PATH          = 'R',
112
        RELABEL_PATH                   = 'z',
113
        RECURSIVE_RELABEL_PATH         = 'Z',
114
        ADJUST_MODE                    = 'm', /* legacy, 'z' is identical to this */
115
} ItemType;
116

117
typedef enum AgeBy {
118
        AGE_BY_ATIME = 1 << 0,
119
        AGE_BY_BTIME = 1 << 1,
120
        AGE_BY_CTIME = 1 << 2,
121
        AGE_BY_MTIME = 1 << 3,
122

123
        /* All file timestamp types are checked by default. */
124
        AGE_BY_DEFAULT_FILE = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_CTIME | AGE_BY_MTIME,
125
        AGE_BY_DEFAULT_DIR  = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_MTIME,
126
} AgeBy;
127

128
typedef struct Item {
129
        ItemType type;
130

131
        char *path;
132
        char *argument;
133
        void *binary_argument;        /* set if binary data, in which case it takes precedence over 'argument' */
134
        size_t binary_argument_size;
135
        char **xattrs;
136
#if HAVE_ACL
137
        acl_t acl_access;
138
        acl_t acl_access_exec;
139
        acl_t acl_default;
140
#endif
141
        uid_t uid;
142
        gid_t gid;
143
        mode_t mode;
144
        usec_t age;
145
        AgeBy age_by_file, age_by_dir;
146

147
        dev_t major_minor;
148
        unsigned attribute_value;
149
        unsigned attribute_mask;
150

151
        bool uid_set:1;
152
        bool gid_set:1;
153
        bool mode_set:1;
154
        bool uid_only_create:1;
155
        bool gid_only_create:1;
156
        bool mode_only_create:1;
157
        bool age_set:1;
158
        bool mask_perms:1;
159
        bool attribute_set:1;
160

161
        bool keep_first_level:1;
162

163
        bool append_or_force:1;
164

165
        bool allow_failure:1;
166

167
        bool try_replace:1;
168

169
        bool purge:1;
170

171
        bool ignore_if_target_missing:1;
172

173
        OperationMask done;
174
} Item;
175

176
typedef struct ItemArray {
177
        Item *items;
178
        size_t n_items;
179

180
        struct ItemArray *parent;
181
        Set *children;
182
} ItemArray;
183

184
typedef enum DirectoryType {
185
        DIRECTORY_RUNTIME,
186
        DIRECTORY_STATE,
187
        DIRECTORY_CACHE,
188
        DIRECTORY_LOGS,
189
        _DIRECTORY_TYPE_MAX,
190
} DirectoryType;
191

192
typedef enum {
193
        CREATION_NORMAL,
194
        CREATION_EXISTING,
195
        CREATION_FORCE,
196
        _CREATION_MODE_MAX,
197
        _CREATION_MODE_INVALID = -EINVAL,
198
} CreationMode;
199

200
static CatFlags arg_cat_flags = CAT_CONFIG_OFF;
201
static bool arg_dry_run = false;
202
static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
203
static OperationMask arg_operation = 0;
204
static bool arg_boot = false;
205
static bool arg_graceful = false;
206
static PagerFlags arg_pager_flags = 0;
207
static char **arg_include_prefixes = NULL;
208
static char **arg_exclude_prefixes = NULL;
209
static char *arg_root = NULL;
210
static char *arg_image = NULL;
211
static char *arg_replace = NULL;
212
static ImagePolicy *arg_image_policy = NULL;
213

214
#define MAX_DEPTH 256
215

216
typedef struct Context {
217
        OrderedHashmap *items;
218
        OrderedHashmap *globs;
219
        Set *unix_sockets;
220
        Hashmap *uid_cache;
221
        Hashmap *gid_cache;
222
} Context;
223

224
STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, strv_freep);
689✔
225
STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, strv_freep);
689✔
226
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
689✔
227
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
689✔
228
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
689✔
229

230
static const char *const creation_mode_verb_table[_CREATION_MODE_MAX] = {
231
        [CREATION_NORMAL]   = "Created",
232
        [CREATION_EXISTING] = "Found existing",
233
        [CREATION_FORCE]    = "Created replacement",
234
};
235

236
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode);
10,665✔
237

238
static void context_done(Context *c) {
689✔
239
        assert(c);
689✔
240

241
        ordered_hashmap_free(c->items);
689✔
242
        ordered_hashmap_free(c->globs);
689✔
243

244
        set_free(c->unix_sockets);
689✔
245

246
        hashmap_free(c->uid_cache);
689✔
247
        hashmap_free(c->gid_cache);
689✔
248
}
689✔
249

250
/* Different kinds of errors that mean that information is not available in the environment. */
251
static bool ERRNO_IS_NEG_NOINFO(intmax_t r) {
88,193✔
252
        return IN_SET(r,
88,193✔
253
                      -EUNATCH,    /* os-release or machine-id missing */
254
                      -ENOMEDIUM,  /* machine-id or another file empty */
255
                      -ENOPKG,     /* machine-id is uninitialized */
256
                      -ENXIO);     /* env var is unset */
257
}
258

259
static int specifier_directory(
×
260
                char specifier,
261
                const void *data,
262
                const char *root,
263
                const void *userdata,
264
                char **ret) {
265

266
        struct table_entry {
×
267
                uint64_t type;
268
                const char *suffix;
269
        };
270

271
        static const struct table_entry paths_system[] = {
×
272
                [DIRECTORY_RUNTIME] = { SD_PATH_SYSTEM_RUNTIME            },
273
                [DIRECTORY_STATE] =   { SD_PATH_SYSTEM_STATE_PRIVATE      },
274
                [DIRECTORY_CACHE] =   { SD_PATH_SYSTEM_STATE_CACHE        },
275
                [DIRECTORY_LOGS] =    { SD_PATH_SYSTEM_STATE_LOGS         },
276
        };
277

278
        static const struct table_entry paths_user[] = {
×
279
                [DIRECTORY_RUNTIME] = { SD_PATH_USER_RUNTIME              },
280
                [DIRECTORY_STATE] =   { SD_PATH_USER_STATE_PRIVATE        },
281
                [DIRECTORY_CACHE] =   { SD_PATH_USER_STATE_CACHE          },
282
                [DIRECTORY_LOGS] =    { SD_PATH_USER_STATE_PRIVATE, "log" },
283
        };
284

285
        const struct table_entry *paths;
×
286
        _cleanup_free_ char *p = NULL;
×
287
        unsigned i;
×
288
        int r;
×
289

290
        assert_cc(ELEMENTSOF(paths_system) == ELEMENTSOF(paths_user));
×
291
        paths = arg_runtime_scope == RUNTIME_SCOPE_USER ? paths_user : paths_system;
×
292

293
        i = PTR_TO_UINT(data);
×
294
        assert(i < ELEMENTSOF(paths_system));
×
295

296
        r = sd_path_lookup(paths[i].type, paths[i].suffix, &p);
×
297
        if (r < 0)
×
298
                return r;
299

300
        if (arg_root) {
×
301
                _cleanup_free_ char *j = NULL;
×
302

303
                j = path_join(arg_root, p);
×
304
                if (!j)
×
305
                        return -ENOMEM;
×
306

307
                *ret = TAKE_PTR(j);
×
308
        } else
309
                *ret = TAKE_PTR(p);
×
310

311
        return 0;
312
}
313

314
static int log_unresolvable_specifier(const char *filename, unsigned line) {
×
315
        static bool notified = false;
×
316

317
        /* In system mode, this is called when /etc is not fully initialized and some specifiers are
318
         * unresolvable. In user mode, this is called when some variables are not defined. These cases are
319
         * not considered a fatal error, so log at LOG_NOTICE only for the first time and then downgrade this
320
         * to LOG_DEBUG for the rest.
321
         *
322
         * If we're running in a chroot (--root was used or sd_booted() reports that systemd is not running),
323
         * always use LOG_DEBUG. We may be called to initialize a chroot before booting and there is no
324
         * expectation that machine-id and other files will be populated.
325
         */
326

327
        int log_level = notified || arg_root || running_in_chroot() > 0 ?
×
328
                LOG_DEBUG : LOG_NOTICE;
×
329

330
        log_syntax(NULL,
×
331
                   log_level,
332
                   filename, line, 0,
333
                   "Failed to resolve specifier: %s, skipping.",
334
                   arg_runtime_scope == RUNTIME_SCOPE_USER ? "Required $XDG_... variable not defined" : "uninitialized /etc/ detected");
335

336
        if (!notified)
×
337
                log_full(log_level,
×
338
                         "All rules containing unresolvable specifiers will be skipped.");
339

340
        notified = true;
×
341
        return 0;
×
342
}
343

344
#define log_action(would, doing, fmt, ...)              \
345
        log_full(arg_dry_run ? LOG_INFO : LOG_DEBUG,    \
346
                 fmt,                                   \
347
                 arg_dry_run ? (would) : (doing),       \
348
                 __VA_ARGS__)
349

350
static int user_config_paths(char ***ret) {
192✔
351
        _cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL;
192✔
352
        _cleanup_free_ char *runtime_config = NULL;
192✔
353
        int r;
192✔
354

355
        assert(ret);
192✔
356

357
        /* Combined user-specific and global dirs */
358
        r = user_search_dirs("/user-tmpfiles.d", &config_dirs, &data_dirs);
192✔
359
        if (r < 0)
192✔
360
                return r;
361

362
        r = xdg_user_runtime_dir("/user-tmpfiles.d", &runtime_config);
192✔
363
        if (r < 0 && !ERRNO_IS_NEG_NOINFO(r))
192✔
364
                return r;
365

366
        r = strv_consume(&config_dirs, TAKE_PTR(runtime_config));
192✔
367
        if (r < 0)
192✔
368
                return r;
369

370
        r = strv_extend_strv_consume(&config_dirs, TAKE_PTR(data_dirs), /* filter_duplicates= */ true);
192✔
371
        if (r < 0)
192✔
372
                return r;
373

374
        r = path_strv_make_absolute_cwd(config_dirs);
192✔
375
        if (r < 0)
192✔
376
                return r;
377

378
        *ret = TAKE_PTR(config_dirs);
192✔
379
        return 0;
192✔
380
}
381

382
static bool needs_purge(ItemType t) {
4,335✔
383
        return IN_SET(t,
4,335✔
384
                      CREATE_FILE,
385
                      TRUNCATE_FILE,
386
                      CREATE_DIRECTORY,
387
                      TRUNCATE_DIRECTORY,
388
                      CREATE_SUBVOLUME,
389
                      CREATE_SUBVOLUME_INHERIT_QUOTA,
390
                      CREATE_SUBVOLUME_NEW_QUOTA,
391
                      CREATE_FIFO,
392
                      CREATE_SYMLINK,
393
                      CREATE_CHAR_DEVICE,
394
                      CREATE_BLOCK_DEVICE,
395
                      COPY_FILES,
396
                      WRITE_FILE,
397
                      EMPTY_DIRECTORY);
398
}
399

400
static bool needs_glob(ItemType t) {
23,424✔
401
        return IN_SET(t,
23,424✔
402
                      WRITE_FILE,
403
                      EMPTY_DIRECTORY,
404
                      SET_XATTR,
405
                      RECURSIVE_SET_XATTR,
406
                      SET_ACL,
407
                      RECURSIVE_SET_ACL,
408
                      SET_ATTRIBUTE,
409
                      RECURSIVE_SET_ATTRIBUTE,
410
                      IGNORE_PATH,
411
                      IGNORE_DIRECTORY_PATH,
412
                      REMOVE_PATH,
413
                      RECURSIVE_REMOVE_PATH,
414
                      RELABEL_PATH,
415
                      RECURSIVE_RELABEL_PATH,
416
                      ADJUST_MODE);
417
}
418

419
static bool takes_ownership(ItemType t) {
5,827✔
420
        return IN_SET(t,
5,827✔
421
                      CREATE_FILE,
422
                      TRUNCATE_FILE,
423
                      CREATE_DIRECTORY,
424
                      TRUNCATE_DIRECTORY,
425
                      CREATE_SUBVOLUME,
426
                      CREATE_SUBVOLUME_INHERIT_QUOTA,
427
                      CREATE_SUBVOLUME_NEW_QUOTA,
428
                      CREATE_FIFO,
429
                      CREATE_SYMLINK,
430
                      CREATE_CHAR_DEVICE,
431
                      CREATE_BLOCK_DEVICE,
432
                      COPY_FILES,
433
                      WRITE_FILE,
434
                      EMPTY_DIRECTORY,
435
                      IGNORE_PATH,
436
                      IGNORE_DIRECTORY_PATH,
437
                      REMOVE_PATH,
438
                      RECURSIVE_REMOVE_PATH);
439
}
440

441
static bool supports_ignore_if_target_missing(ItemType t) {
2✔
442
        return t == CREATE_SYMLINK;
2✔
443
}
444

445
static struct Item* find_glob(OrderedHashmap *h, const char *match) {
183✔
446
        ItemArray *j;
183✔
447

448
        ORDERED_HASHMAP_FOREACH(j, h)
294✔
449
                FOREACH_ARRAY(item, j->items, j->n_items)
240✔
450
                        if (fnmatch(item->path, match, FNM_PATHNAME|FNM_PERIOD) == 0)
129✔
451
                                return item;
18✔
452
        return NULL;
165✔
453
}
454

455
static int load_unix_sockets(Context *c) {
×
456
        _cleanup_set_free_ Set *sockets = NULL;
×
457
        _cleanup_fclose_ FILE *f = NULL;
×
458
        int r;
×
459

460
        if (c->unix_sockets)
×
461
                return 0;
462

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

465
        f = fopen("/proc/net/unix", "re");
×
466
        if (!f)
×
467
                return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
×
468
                                      "Failed to open %s, ignoring: %m", "/proc/net/unix");
469

470
        /* Skip header */
471
        r = read_line(f, LONG_LINE_MAX, NULL);
×
472
        if (r < 0)
×
473
                return log_warning_errno(r, "Failed to skip /proc/net/unix header line: %m");
×
474
        if (r == 0)
×
475
                return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Premature end of file reading /proc/net/unix.");
×
476

477
        for (;;) {
×
478
                _cleanup_free_ char *line = NULL;
×
479
                char *p;
×
480

481
                r = read_line(f, LONG_LINE_MAX, &line);
×
482
                if (r < 0)
×
483
                        return log_warning_errno(r, "Failed to read /proc/net/unix line, ignoring: %m");
×
484
                if (r == 0) /* EOF */
×
485
                        break;
486

487
                p = strchr(line, ':');
×
488
                if (!p)
×
489
                        continue;
×
490

491
                if (strlen(p) < 37)
×
492
                        continue;
×
493

494
                p += 37;
×
495
                p += strspn(p, WHITESPACE);
×
496
                p += strcspn(p, WHITESPACE); /* skip one more word */
×
497
                p += strspn(p, WHITESPACE);
×
498

499
                if (!path_is_absolute(p))
×
500
                        continue;
×
501

502
                r = set_put_strdup_full(&sockets, &path_hash_ops_free, p);
×
503
                if (r < 0)
×
504
                        return log_warning_errno(r, "Failed to add AF_UNIX socket to set, ignoring: %m");
×
505
        }
506

507
        c->unix_sockets = TAKE_PTR(sockets);
×
508
        return 1;
×
509
}
510

511
static bool unix_socket_alive(Context *c, const char *fn) {
×
512
        assert(c);
×
513
        assert(fn);
×
514

515
        if (load_unix_sockets(c) < 0)
×
516
                return true;     /* We don't know, so assume yes */
517

518
        return set_contains(c->unix_sockets, fn);
×
519
}
520

521
/* Accessors for the argument in binary format */
522
static const void* item_binary_argument(const Item *i) {
1,763✔
523
        assert(i);
1,763✔
524
        return i->binary_argument ?: i->argument;
1,763✔
525
}
526

527
static size_t item_binary_argument_size(const Item *i) {
1,529✔
528
        assert(i);
1,529✔
529
        return i->binary_argument ? i->binary_argument_size : strlen_ptr(i->argument);
1,529✔
530
}
531

532
static DIR* xopendirat_nomod(int dirfd, const char *path) {
1,410✔
533
        DIR *dir;
1,410✔
534

535
        dir = xopendirat(dirfd, path, O_NOFOLLOW|O_NOATIME);
1,410✔
536
        if (dir)
1,410✔
537
                return dir;
538

539
        if (!IN_SET(errno, ENOENT, ELOOP))
490✔
540
                log_debug_errno(errno, "Cannot open %sdirectory \"%s\" with O_NOATIME: %m", dirfd == AT_FDCWD ? "" : "sub", path);
11✔
541
        if (!ERRNO_IS_PRIVILEGE(errno))
490✔
542
                return NULL;
543

544
        dir = xopendirat(dirfd, path, O_NOFOLLOW);
×
545
        if (!dir)
×
546
                log_debug_errno(errno, "Cannot open %sdirectory \"%s\" with or without O_NOATIME: %m", dirfd == AT_FDCWD ? "" : "sub", path);
×
547

548
        return dir;
549
}
550

551
static DIR* opendir_nomod(const char *path) {
1,373✔
552
        return xopendirat_nomod(AT_FDCWD, path);
1,373✔
553
}
554

555
static int opendir_and_stat(
531✔
556
                const char *path,
557
                DIR **ret,
558
                struct statx *ret_sx,
559
                bool *ret_mountpoint) {
560

561
        _cleanup_closedir_ DIR *d = NULL;
531✔
562
        struct statx sx1;
531✔
563
        int r;
531✔
564

565
        assert(path);
531✔
566
        assert(ret);
531✔
567
        assert(ret_sx);
531✔
568
        assert(ret_mountpoint);
531✔
569

570
        /* Do opendir() and statx() on the directory.
571
         * Return 1 if successful, 0 if file doesn't exist or is not a directory,
572
         * negative errno otherwise.
573
         */
574

575
        d = opendir_nomod(path);
531✔
576
        if (!d) {
531✔
577
                bool ignore = IN_SET(errno, ENOENT, ENOTDIR);
487✔
578
                r = log_full_errno(ignore ? LOG_DEBUG : LOG_ERR,
487✔
579
                                   errno, "Failed to open directory %s: %m", path);
580
                if (!ignore)
487✔
581
                        return r;
582

583
                *ret = NULL;
487✔
584
                *ret_sx = (struct statx) {};
487✔
585
                *ret_mountpoint = false;
487✔
586
                return 0;
487✔
587
        }
588

589
        if (statx(dirfd(d), "", AT_EMPTY_PATH, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, &sx1) < 0)
44✔
590
                return log_error_errno(errno, "statx(%s) failed: %m", path);
×
591

592
        if (FLAGS_SET(sx1.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT))
44✔
593
                *ret_mountpoint = FLAGS_SET(sx1.stx_attributes, STATX_ATTR_MOUNT_ROOT);
44✔
594
        else {
595
                struct statx sx2;
×
596
                if (statx(dirfd(d), "..", 0, STATX_INO, &sx2) < 0)
×
597
                        return log_error_errno(errno, "statx(%s/..) failed: %m", path);
×
598

599
                *ret_mountpoint = !statx_mount_same(&sx1, &sx2);
×
600
        }
601

602
        *ret = TAKE_PTR(d);
44✔
603
        *ret_sx = sx1;
44✔
604
        return 1;
44✔
605
}
606

607
static bool needs_cleanup(
165✔
608
                nsec_t atime,
609
                nsec_t btime,
610
                nsec_t ctime,
611
                nsec_t mtime,
612
                nsec_t cutoff,
613
                const char *sub_path,
614
                AgeBy age_by,
615
                bool is_dir) {
616

617
        if (FLAGS_SET(age_by, AGE_BY_MTIME) && mtime != NSEC_INFINITY && mtime >= cutoff) {
165✔
618
                /* Follows spelling in stat(1). */
619
                log_debug("%s \"%s\": modify time %s is too new.",
34✔
620
                          is_dir ? "Directory" : "File",
621
                          sub_path,
622
                          FORMAT_TIMESTAMP_STYLE(mtime / NSEC_PER_USEC, TIMESTAMP_US));
623

624
                return false;
24✔
625
        }
626

627
        if (FLAGS_SET(age_by, AGE_BY_ATIME) && atime != NSEC_INFINITY && atime >= cutoff) {
141✔
628
                log_debug("%s \"%s\": access time %s is too new.",
117✔
629
                          is_dir ? "Directory" : "File",
630
                          sub_path,
631
                          FORMAT_TIMESTAMP_STYLE(atime / NSEC_PER_USEC, TIMESTAMP_US));
632

633
                return false;
63✔
634
        }
635

636
        /*
637
         * Note: Unless explicitly specified by the user, "ctime" is ignored
638
         * by default for directories, because we change it when deleting.
639
         */
640
        if (FLAGS_SET(age_by, AGE_BY_CTIME) && ctime != NSEC_INFINITY && ctime >= cutoff) {
78✔
641
                log_debug("%s \"%s\": change time %s is too new.",
14✔
642
                          is_dir ? "Directory" : "File",
643
                          sub_path,
644
                          FORMAT_TIMESTAMP_STYLE(ctime / NSEC_PER_USEC, TIMESTAMP_US));
645

646
                return false;
7✔
647
        }
648

649
        if (FLAGS_SET(age_by, AGE_BY_BTIME) && btime != NSEC_INFINITY && btime >= cutoff) {
71✔
650
                log_debug("%s \"%s\": birth time %s is too new.",
8✔
651
                          is_dir ? "Directory" : "File",
652
                          sub_path,
653
                          FORMAT_TIMESTAMP_STYLE(btime / NSEC_PER_USEC, TIMESTAMP_US));
654

655
                return false;
5✔
656
        }
657

658
        return true;
659
}
660

661
static int dir_cleanup(
81✔
662
                Context *c,
663
                Item *i,
664
                const char *p,
665
                DIR *d,
666
                nsec_t self_atime_nsec,
667
                nsec_t self_mtime_nsec,
668
                nsec_t cutoff_nsec,
669
                dev_t rootdev_major,
670
                dev_t rootdev_minor,
671
                bool mountpoint,
672
                int maxdepth,
673
                bool keep_this_level,
674
                AgeBy age_by_file,
675
                AgeBy age_by_dir) {
676

677
        bool deleted = false;
81✔
678
        int r = 0;
81✔
679

680
        assert(c);
81✔
681
        assert(i);
81✔
682
        assert(d);
81✔
683

684
        FOREACH_DIRENT_ALL(de, d, break) {
431✔
685
                _cleanup_free_ char *sub_path = NULL;
350✔
686
                nsec_t atime_nsec, mtime_nsec, ctime_nsec, btime_nsec;
350✔
687

688
                if (dot_or_dot_dot(de->d_name))
350✔
689
                        continue;
162✔
690

691
                /* If statx() is supported, use it. It's preferable over fstatat() since it tells us
692
                 * explicitly where we are looking at a mount point, for free as side information. Determining
693
                 * the same information without statx() is hard, see the complexity of path_is_mount_point(),
694
                 * and also much slower as it requires a number of syscalls instead of just one. Hence, when
695
                 * we have modern statx() we use it instead of fstat() and do proper mount point checks,
696
                 * while on older kernels's well do traditional st_dev based detection of mount points.
697
                 *
698
                 * Using statx() for detecting mount points also has the benefit that we handle weird file
699
                 * systems such as overlayfs better where each file is originating from a different
700
                 * st_dev. */
701

702
                struct statx sx;
188✔
703
                if (statx(dirfd(d), de->d_name,
188✔
704
                          AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT,
705
                          STATX_TYPE|STATX_MODE|STATX_UID|STATX_ATIME|STATX_MTIME|STATX_CTIME|STATX_BTIME,
706
                          &sx) < 0) {
707
                        if (errno == ENOENT)
×
708
                                continue;
×
709

710
                        /* FUSE, NFS mounts, SELinux might return EACCES */
711
                        log_full_errno(errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
×
712
                                       "statx(%s/%s) failed: %m", p, de->d_name);
713
                        continue;
×
714
                }
715

716
                if (FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) {
188✔
717
                        /* Yay, we have the mount point API, use it */
718
                        if (FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT)) {
188✔
719
                                log_debug("Ignoring \"%s/%s\": different mount points.", p, de->d_name);
×
720
                                continue;
×
721
                        }
722
                } else {
723
                        /* So we might have statx() but the STATX_ATTR_MOUNT_ROOT flag is not supported, fall
724
                         * back to traditional stx_dev checking. */
725
                        if (sx.stx_dev_major != rootdev_major ||
×
726
                            sx.stx_dev_minor != rootdev_minor) {
×
727
                                log_debug("Ignoring \"%s/%s\": different filesystem.", p, de->d_name);
×
728
                                continue;
×
729
                        }
730

731
                        /* Try to detect bind mounts of the same filesystem instance; they do not differ in
732
                         * device major/minors. This type of query is not supported on all kernels or
733
                         * filesystem types though. */
734
                        if (S_ISDIR(sx.stx_mode)) {
×
735
                                int q;
×
736

737
                                q = is_mount_point_at(dirfd(d), de->d_name, 0);
×
738
                                if (q < 0)
×
739
                                        log_debug_errno(q, "Failed to determine whether \"%s/%s\" is a mount point, ignoring: %m", p, de->d_name);
×
740
                                else if (q > 0) {
×
741
                                        log_debug("Ignoring \"%s/%s\": different mount of the same filesystem.", p, de->d_name);
×
742
                                        continue;
×
743
                                }
744
                        }
745
                }
746

747
                atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : 0;
188✔
748
                mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : 0;
188✔
749
                ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? statx_timestamp_load_nsec(&sx.stx_ctime) : 0;
188✔
750
                btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? statx_timestamp_load_nsec(&sx.stx_btime) : 0;
188✔
751

752
                sub_path = path_join(p, de->d_name);
188✔
753
                if (!sub_path) {
188✔
754
                        r = log_oom();
×
755
                        goto finish;
×
756
                }
757

758
                /* Is there an item configured for this path? */
759
                if (ordered_hashmap_get(c->items, sub_path)) {
188✔
760
                        log_debug("Ignoring \"%s\": a separate entry exists.", sub_path);
5✔
761
                        continue;
5✔
762
                }
763

764
                if (find_glob(c->globs, sub_path)) {
183✔
765
                        log_debug("Ignoring \"%s\": a separate glob exists.", sub_path);
18✔
766
                        continue;
18✔
767
                }
768

769
                if (S_ISDIR(sx.stx_mode)) {
165✔
770
                        _cleanup_closedir_ DIR *sub_dir = NULL;
×
771

772
                        if (mountpoint &&
37✔
773
                            streq(de->d_name, "lost+found") &&
×
774
                            sx.stx_uid == 0) {
×
775
                                log_debug("Ignoring directory \"%s\".", sub_path);
×
776
                                continue;
×
777
                        }
778

779
                        if (maxdepth <= 0)
37✔
780
                                log_warning("Reached max depth on \"%s\".", sub_path);
×
781
                        else {
782
                                int q;
37✔
783

784
                                sub_dir = xopendirat_nomod(dirfd(d), de->d_name);
37✔
785
                                if (!sub_dir) {
37✔
786
                                        if (errno != ENOENT)
×
787
                                                r = log_warning_errno(errno, "Opening directory \"%s\" failed, ignoring: %m", sub_path);
×
788

789
                                        continue;
×
790
                                }
791

792
                                if (!arg_dry_run &&
70✔
793
                                    flock(dirfd(sub_dir), LOCK_EX|LOCK_NB) < 0) {
33✔
794
                                        log_debug_errno(errno, "Couldn't acquire shared BSD lock on directory \"%s\", skipping: %m", sub_path);
×
795
                                        continue;
×
796
                                }
797

798
                                q = dir_cleanup(c, i,
37✔
799
                                                sub_path, sub_dir,
800
                                                atime_nsec, mtime_nsec, cutoff_nsec,
801
                                                rootdev_major, rootdev_minor,
802
                                                false, maxdepth-1, false,
803
                                                age_by_file, age_by_dir);
804
                                if (q < 0)
37✔
805
                                        r = q;
×
806
                        }
807

808
                        /* Note: if you are wondering why we don't support the sticky bit for excluding
809
                         * directories from cleaning like we do it for other file system objects: well, the
810
                         * sticky bit already has a meaning for directories, so we don't want to overload
811
                         * that. */
812

813
                        if (keep_this_level) {
37✔
814
                                log_debug("Keeping directory \"%s\".", sub_path);
×
815
                                continue;
×
816
                        }
817

818
                        /*
819
                         * Check the file timestamps of an entry against the
820
                         * given cutoff time; delete if it is older.
821
                         */
822
                        if (!needs_cleanup(atime_nsec, btime_nsec, ctime_nsec, mtime_nsec,
37✔
823
                                           cutoff_nsec, sub_path, age_by_dir, true))
824
                                continue;
25✔
825

826
                        log_action("Would remove", "Removing", "%s directory \"%s\"", sub_path);
16✔
827
                        if (!arg_dry_run &&
20✔
828
                            unlinkat(dirfd(d), de->d_name, AT_REMOVEDIR) < 0 &&
8✔
829
                            !IN_SET(errno, ENOENT, ENOTEMPTY))
×
830
                                r = log_warning_errno(errno, "Failed to remove directory \"%s\", ignoring: %m", sub_path);
×
831

832
                } else {
833
                        _cleanup_close_ int fd = -EBADF; /* This file descriptor is defined here so that the
350✔
834
                                                          * lock that is taken below is only dropped _after_
835
                                                          * the unlink operation has finished. */
836

837
                        /* Skip files for which the sticky bit is set. These are semantics we define, and are
838
                         * unknown elsewhere. See XDG_RUNTIME_DIR specification for details. */
839
                        if (sx.stx_mode & S_ISVTX) {
128✔
840
                                log_debug("Skipping \"%s\": sticky bit set.", sub_path);
×
841
                                continue;
×
842
                        }
843

844
                        if (mountpoint &&
128✔
845
                            S_ISREG(sx.stx_mode) &&
×
846
                            sx.stx_uid == 0 &&
×
847
                            STR_IN_SET(de->d_name,
×
848
                                       ".journal",
849
                                       "aquota.user",
850
                                       "aquota.group")) {
851
                                log_debug("Skipping \"%s\".", sub_path);
×
852
                                continue;
×
853
                        }
854

855
                        /* Ignore sockets that are listed in /proc/net/unix */
856
                        if (S_ISSOCK(sx.stx_mode) && unix_socket_alive(c, sub_path)) {
128✔
857
                                log_debug("Skipping \"%s\": live socket.", sub_path);
×
858
                                continue;
×
859
                        }
860

861
                        /* Ignore device nodes */
862
                        if (S_ISCHR(sx.stx_mode) || S_ISBLK(sx.stx_mode)) {
128✔
863
                                log_debug("Skipping \"%s\": a device.", sub_path);
×
864
                                continue;
×
865
                        }
866

867
                        /* Keep files on this level if this was requested */
868
                        if (keep_this_level) {
128✔
869
                                log_debug("Keeping \"%s\".", sub_path);
×
870
                                continue;
×
871
                        }
872

873
                        if (!needs_cleanup(atime_nsec, btime_nsec, ctime_nsec, mtime_nsec,
128✔
874
                                           cutoff_nsec, sub_path, age_by_file, false))
875
                                continue;
74✔
876

877
                        if (!arg_dry_run) {
54✔
878
                                fd = xopenat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME|O_NONBLOCK|O_NOCTTY);
34✔
879
                                if (fd < 0 && !IN_SET(fd, -ENOENT, -ELOOP))
34✔
880
                                        log_warning_errno(fd, "Opening file \"%s\" failed, proceeding without lock: %m", sub_path);
×
881
                                if (fd >= 0 && flock(fd, LOCK_EX|LOCK_NB) < 0 && errno == EAGAIN) {
34✔
882
                                        log_debug_errno(errno, "Couldn't acquire shared BSD lock on file \"%s\", skipping: %m", sub_path);
×
883
                                        continue;
×
884
                                }
885
                        }
886

887
                        log_action("Would remove", "Removing", "%s \"%s\"", sub_path);
70✔
888
                        if (!arg_dry_run &&
88✔
889
                            unlinkat(dirfd(d), de->d_name, 0) < 0 &&
34✔
890
                            errno != ENOENT)
×
891
                                r = log_warning_errno(errno, "Failed to remove \"%s\", ignoring: %m", sub_path);
×
892

893
                        deleted = true;
54✔
894
                }
895
        }
896

897
finish:
81✔
898
        if (deleted && (self_atime_nsec < NSEC_INFINITY || self_mtime_nsec < NSEC_INFINITY)) {
81✔
899
                struct timespec ts[2];
28✔
900

901
                log_action("Would restore", "Restoring",
37✔
902
                           "%s access and modification time on \"%s\": %s, %s",
903
                           p,
904
                           FORMAT_TIMESTAMP_STYLE(self_atime_nsec / NSEC_PER_USEC, TIMESTAMP_US),
905
                           FORMAT_TIMESTAMP_STYLE(self_mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
906

907
                timespec_store_nsec(ts + 0, self_atime_nsec);
28✔
908
                timespec_store_nsec(ts + 1, self_mtime_nsec);
28✔
909

910
                /* Restore original directory timestamps */
911
                if (!arg_dry_run &&
45✔
912
                    futimens(dirfd(d), ts) < 0)
17✔
913
                        log_warning_errno(errno, "Failed to revert timestamps of '%s', ignoring: %m", p);
28✔
914
        }
915

916
        return r;
81✔
917
}
918

919
static bool hardlinks_protected(void) {
×
920
        static int cached = -1;
×
921
        int r;
×
922

923
        /* Check whether the fs.protected_hardlinks sysctl is on. If we can't determine it we assume its off,
924
         * as that's what the kernel default is.
925
         * Note that we ship 50-default.conf where it is enabled, but better be safe than sorry. */
926

927
        if (cached >= 0)
×
928
                return cached;
×
929

930
        _cleanup_free_ char *value = NULL;
×
931

932
        r = sysctl_read("fs/protected_hardlinks", &value);
×
933
        if (r < 0) {
×
934
                log_debug_errno(r, "Failed to read fs.protected_hardlinks sysctl, assuming disabled: %m");
×
935
                return false;
×
936
        }
937

938
        cached = parse_boolean(value);
×
939
        if (cached < 0)
×
940
                log_debug_errno(cached, "Failed to parse fs.protected_hardlinks sysctl, assuming disabled: %m");
×
941
        return cached > 0;
×
942
}
943

944
static bool hardlink_vulnerable(const struct stat *st) {
14,139✔
945
        assert(st);
14,139✔
946

947
        return !S_ISDIR(st->st_mode) && st->st_nlink > 1 && !hardlinks_protected();
14,139✔
948
}
949

950
static mode_t process_mask_perms(mode_t mode, mode_t current) {
304✔
951

952
        if ((current & 0111) == 0)
304✔
953
                mode &= ~0111;
205✔
954
        if ((current & 0222) == 0)
304✔
955
                mode &= ~0222;
×
956
        if ((current & 0444) == 0)
304✔
957
                mode &= ~0444;
×
958
        if (!S_ISDIR(current))
304✔
959
                mode &= ~07000; /* remove sticky/sgid/suid bit, unless directory */
205✔
960

961
        return mode;
304✔
962
}
963

964
static int fd_set_perms(
18,452✔
965
                Context *c,
966
                Item *i,
967
                int fd,
968
                const char *path,
969
                const struct stat *st,
970
                CreationMode creation) {
971

972
        bool do_chown, do_chmod;
18,452✔
973
        struct stat stbuf;
18,452✔
974
        mode_t new_mode;
18,452✔
975
        uid_t new_uid;
18,452✔
976
        gid_t new_gid;
18,452✔
977
        int r;
18,452✔
978

979
        assert(c);
18,452✔
980
        assert(i);
18,452✔
981
        assert(fd >= 0);
18,452✔
982
        assert(path);
18,452✔
983

984
        if (!i->mode_set && !i->uid_set && !i->gid_set)
18,452✔
985
                goto shortcut;
5,054✔
986

987
        if (!st) {
13,398✔
988
                if (fstat(fd, &stbuf) < 0)
2,449✔
989
                        return log_error_errno(errno, "fstat(%s) failed: %m", path);
×
990
                st = &stbuf;
991
        }
992

993
        if (hardlink_vulnerable(st))
13,398✔
994
                return log_error_errno(SYNTHETIC_ERRNO(EPERM),
×
995
                                       "Refusing to set permissions on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.",
996
                                       path);
997
        new_uid = i->uid_set && (creation != CREATION_EXISTING || !i->uid_only_create) ? i->uid : st->st_uid;
13,398✔
998
        new_gid = i->gid_set && (creation != CREATION_EXISTING || !i->gid_only_create) ? i->gid : st->st_gid;
13,398✔
999

1000
        /* Do we need a chown()? */
1001
        do_chown = (new_uid != st->st_uid) || (new_gid != st->st_gid);
13,398✔
1002

1003
        /* Calculate the mode to apply */
1004
        new_mode = i->mode_set && (creation != CREATION_EXISTING || !i->mode_only_create) ?
26,226✔
1005
                (i->mask_perms ? process_mask_perms(i->mode, st->st_mode) : i->mode) :
26,532✔
1006
                (st->st_mode & 07777);
264✔
1007

1008
        do_chmod = ((new_mode ^ st->st_mode) & 07777) != 0;
13,398✔
1009

1010
        if (do_chmod && do_chown) {
13,398✔
1011
                /* Before we issue the chmod() let's reduce the access mode to the common bits of the old and
1012
                 * the new mode. That way there's no time window where the file exists under the old owner
1013
                 * with more than the old access modes — and not under the new owner with more than the new
1014
                 * access modes either. */
1015

1016
                if (S_ISLNK(st->st_mode))
715✔
1017
                        log_debug("Skipping temporary mode fix for symlink %s.", path);
×
1018
                else {
1019
                        mode_t m = new_mode & st->st_mode; /* Mask new mode by old mode */
715✔
1020

1021
                        if (((m ^ st->st_mode) & 07777) == 0)
715✔
1022
                                log_debug("\"%s\" matches temporary mode %o already.", path, m);
704✔
1023
                        else {
1024
                                log_action("Would temporarily change", "Temporarily changing",
11✔
1025
                                           "%s \"%s\" to mode %o", path, m);
1026
                                if (!arg_dry_run) {
11✔
1027
                                        r = fchmod_opath(fd, m);
11✔
1028
                                        if (r < 0)
11✔
1029
                                                return log_error_errno(r, "fchmod() of %s failed: %m", path);
×
1030
                                }
1031
                        }
1032
                }
1033
        }
1034

1035
        if (do_chown) {
13,398✔
1036
                log_action("Would change", "Changing",
2,712✔
1037
                           "%s \"%s\" to owner "UID_FMT":"GID_FMT, path, new_uid, new_gid);
1038

1039
                if (!arg_dry_run &&
2,744✔
1040
                    fchownat(fd, "",
1,371✔
1041
                             new_uid != st->st_uid ? new_uid : UID_INVALID,
1,371✔
1042
                             new_gid != st->st_gid ? new_gid : GID_INVALID,
1,371✔
1043
                             AT_EMPTY_PATH) < 0)
1044
                        return log_error_errno(errno, "fchownat() of %s failed: %m", path);
×
1045
        }
1046

1047
        /* Now, apply the final mode. We do this in two cases: when the user set a mode explicitly, or after a
1048
         * chown(), since chown()'s mangle the access mode in regards to sgid/suid in some conditions. */
1049
        if (do_chmod || do_chown) {
13,398✔
1050
                if (S_ISLNK(st->st_mode))
2,676✔
1051
                        log_debug("Skipping mode fix for symlink %s.", path);
×
1052
                else {
1053
                        log_action("Would change", "Changing", "%s \"%s\" to mode %o", path, new_mode);
5,314✔
1054
                        if (!arg_dry_run) {
2,676✔
1055
                                r = fchmod_opath(fd, new_mode);
2,674✔
1056
                                if (r < 0)
2,674✔
1057
                                        return log_error_errno(r, "fchmod() of %s failed: %m", path);
2✔
1058
                        }
1059
                }
1060
        }
1061

1062
shortcut:
13,396✔
1063
        if (arg_dry_run) {
18,450✔
1064
                log_debug("Would relabel \"%s\"", path);
5✔
1065
                return 0;
5✔
1066
        }
1067

1068
        log_debug("Relabelling \"%s\"", path);
18,445✔
1069
        return label_fix_full(fd, /* inode_path= */ NULL, /* label_path= */ path, 0);
18,445✔
1070
}
1071

1072
static int path_open_parent_safe(const char *path, bool allow_failure) {
16,341✔
1073
        _cleanup_free_ char *dn = NULL;
16,341✔
1074
        int r, fd;
16,341✔
1075

1076
        if (!path_is_normalized(path))
16,341✔
1077
                return log_full_errno(allow_failure ? LOG_INFO : LOG_ERR,
×
1078
                                      SYNTHETIC_ERRNO(EINVAL),
1079
                                      "Failed to open parent of '%s': path not normalized%s.",
1080
                                      path,
1081
                                      allow_failure ? ", ignoring" : "");
1082

1083
        r = path_extract_directory(path, &dn);
16,341✔
1084
        if (r < 0)
16,341✔
1085
                return log_full_errno(allow_failure ? LOG_INFO : LOG_ERR,
×
1086
                                      r,
1087
                                      "Unable to determine parent directory of '%s'%s: %m",
1088
                                      path,
1089
                                      allow_failure ? ", ignoring" : "");
1090

1091
        r = chase(dn, arg_root, allow_failure ? CHASE_SAFE : CHASE_SAFE|CHASE_WARN, NULL, &fd);
32,436✔
1092
        if (r == -ENOLINK) /* Unsafe symlink: already covered by CHASE_WARN */
16,341✔
1093
                return r;
1094
        if (r < 0)
16,336✔
1095
                return log_full_errno(allow_failure ? LOG_INFO : LOG_ERR,
×
1096
                                      r,
1097
                                      "Failed to open path '%s'%s: %m",
1098
                                      dn,
1099
                                      allow_failure ? ", ignoring" : "");
1100

1101
        return fd;
16,336✔
1102
}
1103

1104
static int path_open_safe(const char *path) {
2,799✔
1105
        int r, fd;
2,799✔
1106

1107
        /* path_open_safe() returns a file descriptor opened with O_PATH after
1108
         * verifying that the path doesn't contain unsafe transitions, except
1109
         * for its final component as the function does not follow symlink. */
1110

1111
        assert(path);
2,799✔
1112

1113
        if (!path_is_normalized(path))
2,799✔
1114
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to open invalid path '%s'.", path);
×
1115

1116
        r = chase(path, arg_root, CHASE_SAFE|CHASE_WARN|CHASE_NOFOLLOW, NULL, &fd);
2,799✔
1117
        if (r == -ENOLINK)
2,799✔
1118
                return r; /* Unsafe symlink: already covered by CHASE_WARN */
1119
        if (r < 0)
2,799✔
1120
                return log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_ERR, r,
×
1121
                                      "Failed to open path %s%s: %m", path,
1122
                                      r == -ENOENT ? ", ignoring" : "");
1123

1124
        return fd;
2,799✔
1125
}
1126

1127
static int path_set_perms(
2,306✔
1128
                Context *c,
1129
                Item *i,
1130
                const char *path,
1131
                CreationMode creation) {
1132

1133
        _cleanup_close_ int fd = -EBADF;
2,306✔
1134

1135
        assert(c);
2,306✔
1136
        assert(i);
2,306✔
1137
        assert(path);
2,306✔
1138

1139
        fd = path_open_safe(path);
2,306✔
1140
        if (fd == -ENOENT)
2,306✔
1141
                return 0;
1142
        if (fd < 0)
2,306✔
1143
                return fd;
1144

1145
        return fd_set_perms(c, i, fd, path, /* st= */ NULL, creation);
2,306✔
1146
}
1147

1148
static int parse_xattrs_from_arg(Item *i) {
×
1149
        const char *p;
×
1150
        int r;
×
1151

1152
        assert(i);
×
1153

1154
        assert_se(p = i->argument);
×
1155
        for (;;) {
×
1156
                _cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL;
×
1157

1158
                r = extract_first_word(&p, &xattr, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE);
×
1159
                if (r < 0)
×
1160
                        log_warning_errno(r, "Failed to parse extended attribute '%s', ignoring: %m", p);
×
1161
                if (r <= 0)
×
1162
                        break;
1163

1164
                r = split_pair(xattr, "=", &name, &value);
×
1165
                if (r < 0) {
×
1166
                        log_warning_errno(r, "Failed to parse extended attribute, ignoring: %s", xattr);
×
1167
                        continue;
×
1168
                }
1169

1170
                if (isempty(name) || isempty(value)) {
×
1171
                        log_warning("Malformed extended attribute found, ignoring: %s", xattr);
×
1172
                        continue;
×
1173
                }
1174

1175
                if (strv_push_pair(&i->xattrs, name, value) < 0)
×
1176
                        return log_oom();
×
1177

1178
                name = value = NULL;
×
1179
        }
1180

1181
        return 0;
×
1182
}
1183

1184
static int fd_set_xattrs(
×
1185
                Context *c,
1186
                Item *i,
1187
                int fd,
1188
                const char *path,
1189
                const struct stat *st,
1190
                CreationMode creation) {
1191

1192
        int r;
×
1193

1194
        assert(c);
×
1195
        assert(i);
×
1196
        assert(fd >= 0);
×
1197
        assert(path);
×
1198

1199
        STRV_FOREACH_PAIR(name, value, i->xattrs) {
×
1200
                log_action("Would set", "Setting",
×
1201
                           "%s extended attribute '%s=%s' on %s", *name, *value, path);
1202

1203
                if (!arg_dry_run) {
×
1204
                        r = xsetxattr(fd, /* path= */ NULL, AT_EMPTY_PATH, *name, *value);
×
1205
                        if (r < 0)
×
1206
                                return log_error_errno(r, "Failed to set extended attribute %s=%s on '%s': %m",
×
1207
                                                       *name, *value, path);
1208
                }
1209
        }
1210
        return 0;
1211
}
1212

1213
static int path_set_xattrs(
×
1214
                Context *c,
1215
                Item *i,
1216
                const char *path,
1217
                CreationMode creation) {
1218

1219
        _cleanup_close_ int fd = -EBADF;
×
1220

1221
        assert(c);
×
1222
        assert(i);
×
1223
        assert(path);
×
1224

1225
        fd = path_open_safe(path);
×
1226
        if (fd == -ENOENT)
×
1227
                return 0;
1228
        if (fd < 0)
×
1229
                return fd;
1230

1231
        return fd_set_xattrs(c, i, fd, path, /* st= */ NULL, creation);
×
1232
}
1233

1234
static int parse_acls_from_arg(Item *item) {
2,517✔
1235
#if HAVE_ACL
1236
        int r;
2,517✔
1237

1238
        assert(item);
2,517✔
1239

1240
        /* If append_or_force (= modify) is set, we will not modify the acl
1241
         * afterwards, so the mask can be added now if necessary. */
1242

1243
        r = parse_acl(item->argument, &item->acl_access, &item->acl_access_exec,
5,034✔
1244
                      &item->acl_default, !item->append_or_force);
2,517✔
1245
        if (r < 0)
2,517✔
1246
                log_full_errno(arg_graceful && IN_SET(r, -EINVAL, -ENOENT, -ESRCH) ? LOG_DEBUG : LOG_WARNING,
×
1247
                               r, "Failed to parse ACL \"%s\", ignoring: %m", item->argument);
1248
#else
1249
        log_warning("ACLs are not supported, ignoring.");
1250
#endif
1251

1252
        return 0;
2,517✔
1253
}
1254

1255
#if HAVE_ACL
1256
static int parse_acl_cond_exec(
300✔
1257
                const char *path,
1258
                const struct stat *st,
1259
                acl_t cond_exec,
1260
                acl_t access, /* could be empty (NULL) */
1261
                bool append,
1262
                acl_t *ret) {
1263

1264
        acl_entry_t entry;
300✔
1265
        acl_permset_t permset;
300✔
1266
        bool has_exec;
300✔
1267
        int r;
300✔
1268

1269
        assert(path);
300✔
1270
        assert(st);
300✔
1271
        assert(cond_exec);
300✔
1272
        assert(ret);
300✔
1273

1274
        r = dlopen_libacl();
300✔
1275
        if (r < 0)
300✔
1276
                return r;
300✔
1277

1278
        if (!S_ISDIR(st->st_mode)) {
300✔
1279
                _cleanup_(acl_freep) acl_t old = NULL;
201✔
1280

1281
                old = sym_acl_get_file(path, ACL_TYPE_ACCESS);
201✔
1282
                if (!old)
201✔
1283
                        return -errno;
×
1284

1285
                has_exec = false;
201✔
1286

1287
                for (r = sym_acl_get_entry(old, ACL_FIRST_ENTRY, &entry);
201✔
1288
                     r > 0;
796✔
1289
                     r = sym_acl_get_entry(old, ACL_NEXT_ENTRY, &entry)) {
595✔
1290

1291
                        acl_tag_t tag;
609✔
1292

1293
                        if (sym_acl_get_tag_type(entry, &tag) < 0)
609✔
1294
                                return -errno;
×
1295

1296
                        if (tag == ACL_MASK)
609✔
1297
                                continue;
8✔
1298

1299
                        /* If not appending, skip ACL definitions */
1300
                        if (!append && IN_SET(tag, ACL_USER, ACL_GROUP))
603✔
1301
                                continue;
2✔
1302

1303
                        if (sym_acl_get_permset(entry, &permset) < 0)
601✔
1304
                                return -errno;
×
1305

1306
                        r = sym_acl_get_perm(permset, ACL_EXECUTE);
601✔
1307
                        if (r < 0)
601✔
1308
                                return -errno;
×
1309
                        if (r > 0) {
601✔
1310
                                has_exec = true;
14✔
1311
                                break;
14✔
1312
                        }
1313
                }
1314
                if (r < 0)
201✔
1315
                        return -errno;
×
1316

1317
                /* Check if we're about to set the execute bit in acl_access */
1318
                if (!has_exec && access) {
201✔
1319
                        for (r = sym_acl_get_entry(access, ACL_FIRST_ENTRY, &entry);
×
1320
                             r > 0;
×
1321
                             r = sym_acl_get_entry(access, ACL_NEXT_ENTRY, &entry)) {
×
1322

1323
                                if (sym_acl_get_permset(entry, &permset) < 0)
×
1324
                                        return -errno;
×
1325

1326
                                r = sym_acl_get_perm(permset, ACL_EXECUTE);
×
1327
                                if (r < 0)
×
1328
                                        return -errno;
×
1329
                                if (r > 0) {
×
1330
                                        has_exec = true;
1331
                                        break;
1332
                                }
1333
                        }
1334
                        if (r < 0)
×
1335
                                return -errno;
×
1336
                }
1337
        } else
1338
                has_exec = true;
1339

1340
        _cleanup_(acl_freep) acl_t parsed = access ? sym_acl_dup(access) : sym_acl_init(0);
600✔
1341
        if (!parsed)
300✔
1342
                return -errno;
×
1343

1344
        for (r = sym_acl_get_entry(cond_exec, ACL_FIRST_ENTRY, &entry);
300✔
1345
             r > 0;
896✔
1346
             r = sym_acl_get_entry(cond_exec, ACL_NEXT_ENTRY, &entry)) {
596✔
1347

1348
                acl_entry_t parsed_entry;
596✔
1349

1350
                if (sym_acl_create_entry(&parsed, &parsed_entry) < 0)
596✔
1351
                        return -errno;
×
1352

1353
                if (sym_acl_copy_entry(parsed_entry, entry) < 0)
596✔
1354
                        return -errno;
×
1355

1356
                /* We substituted 'X' with 'x' in parse_acl(), so drop execute bit here if not applicable. */
1357
                if (!has_exec) {
596✔
1358
                        if (sym_acl_get_permset(parsed_entry, &permset) < 0)
371✔
1359
                                return -errno;
×
1360

1361
                        if (sym_acl_delete_perm(permset, ACL_EXECUTE) < 0)
371✔
1362
                                return -errno;
×
1363
                }
1364
        }
1365
        if (r < 0)
300✔
1366
                return -errno;
×
1367

1368
        if (!append) { /* want_mask = true */
300✔
1369
                r = calc_acl_mask_if_needed(&parsed);
3✔
1370
                if (r < 0)
3✔
1371
                        return r;
1372
        }
1373

1374
        *ret = TAKE_PTR(parsed);
300✔
1375

1376
        return 0;
300✔
1377
}
1378

1379
static int path_set_acl(
1,011✔
1380
                Context *c,
1381
                const char *path,
1382
                const char *pretty,
1383
                acl_type_t type,
1384
                acl_t acl,
1385
                bool modify) {
1386

1387
        _cleanup_(acl_free_charpp) char *t = NULL;
1,011✔
1388
        _cleanup_(acl_freep) acl_t dup = NULL;
1,011✔
1389
        int r;
1,011✔
1390

1391
        assert(c);
1,011✔
1392

1393
        r = dlopen_libacl();
1,011✔
1394
        if (r < 0)
1,011✔
1395
                return r;
1396

1397
        /* Returns 0 for success, positive error if already warned, negative error otherwise. */
1398

1399
        if (modify) {
1,011✔
1400
                r = acls_for_file(path, type, acl, &dup);
1,008✔
1401
                if (r < 0)
1,008✔
1402
                        return r;
1403

1404
                r = calc_acl_mask_if_needed(&dup);
1,008✔
1405
                if (r < 0)
1,008✔
1406
                        return r;
1407
        } else {
1408
                dup = sym_acl_dup(acl);
3✔
1409
                if (!dup)
3✔
1410
                        return -errno;
×
1411

1412
                /* the mask was already added earlier if needed */
1413
        }
1414

1415
        r = add_base_acls_if_needed(&dup, path);
1,011✔
1416
        if (r < 0)
1,011✔
1417
                return r;
1418

1419
        t = sym_acl_to_any_text(dup, NULL, ',', TEXT_ABBREVIATE);
1,011✔
1420
        log_action("Would set", "Setting",
2,478✔
1421
                   "%s %s ACL %s on %s",
1422
                   type == ACL_TYPE_ACCESS ? "access" : "default",
1423
                   strna(t), pretty);
1424

1425
        if (!arg_dry_run &&
2,021✔
1426
            sym_acl_set_file(path, type, dup) < 0) {
1,010✔
1427
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
1428
                        /* No error if filesystem doesn't support ACLs. Return negative. */
1429
                        return -errno;
×
1430
                else
1431
                        /* Return positive to indicate we already warned */
1432
                        return -log_error_errno(errno,
×
1433
                                                "Setting %s ACL \"%s\" on %s failed: %m",
1434
                                                type == ACL_TYPE_ACCESS ? "access" : "default",
1435
                                                strna(t), pretty);
1436
        }
1437
        return 0;
1438
}
1439
#endif
1440

1441
static int fd_set_acls(
741✔
1442
                Context *c,
1443
                Item *item,
1444
                int fd,
1445
                const char *path,
1446
                const struct stat *st,
1447
                CreationMode creation) {
1448

1449
        int r = 0;
741✔
1450
#if HAVE_ACL
1451
        _cleanup_(acl_freep) acl_t access_with_exec_parsed = NULL;
741✔
1452
        struct stat stbuf;
741✔
1453

1454
        assert(c);
741✔
1455
        assert(item);
741✔
1456
        assert(fd >= 0);
741✔
1457
        assert(path);
741✔
1458

1459
        if (!st) {
741✔
1460
                if (fstat(fd, &stbuf) < 0)
445✔
1461
                        return log_error_errno(errno, "fstat(%s) failed: %m", path);
×
1462
                st = &stbuf;
1463
        }
1464

1465
        if (hardlink_vulnerable(st))
741✔
1466
                return log_error_errno(SYNTHETIC_ERRNO(EPERM),
×
1467
                                       "Refusing to set ACLs on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.",
1468
                                       path);
1469

1470
        if (!inode_type_can_acl(st->st_mode)) {
741✔
1471
                log_debug("Skipping ACL fix for '%s' (inode type does not support ACLs).", path);
×
1472
                return 0;
×
1473
        }
1474

1475
        if (item->acl_access_exec) {
741✔
1476
                r = parse_acl_cond_exec(FORMAT_PROC_FD_PATH(fd), st,
300✔
1477
                                        item->acl_access_exec,
1478
                                        item->acl_access,
1479
                                        item->append_or_force,
300✔
1480
                                        &access_with_exec_parsed);
1481
                if (r < 0)
300✔
1482
                        return log_error_errno(r, "Failed to parse conditionalized execute bit for \"%s\": %m", path);
×
1483

1484
                r = path_set_acl(c, FORMAT_PROC_FD_PATH(fd), path, ACL_TYPE_ACCESS, access_with_exec_parsed, item->append_or_force);
300✔
1485
        } else if (item->acl_access)
441✔
1486
                r = path_set_acl(c, FORMAT_PROC_FD_PATH(fd), path, ACL_TYPE_ACCESS, item->acl_access, item->append_or_force);
195✔
1487

1488
        /* set only default acls to folders */
1489
        if (r == 0 && item->acl_default && S_ISDIR(st->st_mode))
741✔
1490
                r = path_set_acl(c, FORMAT_PROC_FD_PATH(fd), path, ACL_TYPE_DEFAULT, item->acl_default, item->append_or_force);
516✔
1491

1492
        if (ERRNO_IS_NOT_SUPPORTED(r)) {
741✔
1493
                log_debug_errno(r, "ACLs not supported by file system at %s", path);
×
1494
                return 0;
×
1495
        }
1496
        if (r > 0)
741✔
1497
                return -r; /* already warned in path_set_acl */
×
1498
        if (r < 0)
741✔
1499
                return log_error_errno(r, "ACL operation on \"%s\" failed: %m", path);
×
1500
#endif
1501
        return r;
1502
}
1503

1504
static int path_set_acls(
445✔
1505
                Context *c,
1506
                Item *item,
1507
                const char *path,
1508
                CreationMode creation) {
1509

1510
        int r = 0;
445✔
1511
#if HAVE_ACL
1512
        _cleanup_close_ int fd = -EBADF;
445✔
1513

1514
        assert(c);
445✔
1515
        assert(item);
445✔
1516
        assert(path);
445✔
1517

1518
        fd = path_open_safe(path);
445✔
1519
        if (fd == -ENOENT)
445✔
1520
                return 0;
1521
        if (fd < 0)
445✔
1522
                return fd;
1523

1524
        r = fd_set_acls(c, item, fd, path, /* st= */ NULL, creation);
445✔
1525
#endif
1526
        return r;
1527
}
1528

1529
static int parse_attribute_from_arg(Item *item) {
1,077✔
1530
        static const struct {
1,077✔
1531
                char character;
1532
                unsigned value;
1533
        } attributes[] = {
1534
                { 'A', FS_NOATIME_FL },      /* do not update atime */
1535
                { 'S', FS_SYNC_FL },         /* Synchronous updates */
1536
                { 'D', FS_DIRSYNC_FL },      /* dirsync behaviour (directories only) */
1537
                { 'a', FS_APPEND_FL },       /* writes to file may only append */
1538
                { 'c', FS_COMPR_FL },        /* Compress file */
1539
                { 'd', FS_NODUMP_FL },       /* do not dump file */
1540
                { 'e', FS_EXTENT_FL },       /* Extents */
1541
                { 'i', FS_IMMUTABLE_FL },    /* Immutable file */
1542
                { 'j', FS_JOURNAL_DATA_FL }, /* Reserved for ext3 */
1543
                { 's', FS_SECRM_FL },        /* Secure deletion */
1544
                { 'u', FS_UNRM_FL },         /* Undelete */
1545
                { 't', FS_NOTAIL_FL },       /* file tail should not be merged */
1546
                { 'T', FS_TOPDIR_FL },       /* Top of directory hierarchies */
1547
                { 'C', FS_NOCOW_FL },        /* Do not cow file */
1548
                { 'P', FS_PROJINHERIT_FL },  /* Inherit the quota project ID */
1549
        };
1550

1551
        enum {
1,077✔
1552
                MODE_ADD,
1553
                MODE_DEL,
1554
                MODE_SET
1555
        } mode = MODE_ADD;
1,077✔
1556

1557
        unsigned value = 0, mask = 0;
1,077✔
1558
        const char *p;
1,077✔
1559

1560
        assert(item);
1,077✔
1561

1562
        p = item->argument;
1,077✔
1563
        if (p) {
1,077✔
1564
                if (*p == '+') {
1,077✔
1565
                        mode = MODE_ADD;
1,077✔
1566
                        p++;
1,077✔
1567
                } else if (*p == '-') {
×
1568
                        mode = MODE_DEL;
×
1569
                        p++;
×
1570
                } else  if (*p == '=') {
×
1571
                        mode = MODE_SET;
×
1572
                        p++;
×
1573
                }
1574
        }
1575

1576
        if (isempty(p) && mode != MODE_SET)
1,077✔
1577
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1578
                                       "Setting file attribute on '%s' needs an attribute specification.",
1579
                                       item->path);
1580

1581
        for (; p && *p ; p++) {
2,154✔
1582
                unsigned i, v;
1583

1584
                for (i = 0; i < ELEMENTSOF(attributes); i++)
15,078✔
1585
                        if (*p == attributes[i].character)
15,078✔
1586
                                break;
1587

1588
                if (i >= ELEMENTSOF(attributes))
1,077✔
1589
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1590
                                               "Unknown file attribute '%c' on '%s'.", *p, item->path);
1591

1592
                v = attributes[i].value;
1,077✔
1593

1594
                SET_FLAG(value, v, IN_SET(mode, MODE_ADD, MODE_SET));
1,077✔
1595

1596
                mask |= v;
1,077✔
1597
        }
1598

1599
        if (mode == MODE_SET)
1,077✔
1600
                mask |= CHATTR_ALL_FL;
×
1601

1602
        assert(mask != 0);
1,077✔
1603

1604
        item->attribute_mask = mask;
1,077✔
1605
        item->attribute_value = value;
1,077✔
1606
        item->attribute_set = true;
1,077✔
1607

1608
        return 0;
1,077✔
1609
}
1610

1611
static int fd_set_attribute(
48✔
1612
                Context *c,
1613
                Item *item,
1614
                int fd,
1615
                const char *path,
1616
                const struct stat *st,
1617
                CreationMode creation) {
1618

1619
        struct stat stbuf;
48✔
1620
        unsigned f;
48✔
1621
        int r;
48✔
1622

1623
        assert(c);
48✔
1624
        assert(item);
48✔
1625
        assert(fd >= 0);
48✔
1626
        assert(path);
48✔
1627

1628
        if (!item->attribute_set || item->attribute_mask == 0)
48✔
1629
                return 0;
48✔
1630

1631
        if (!st) {
48✔
1632
                if (fstat(fd, &stbuf) < 0)
48✔
1633
                        return log_error_errno(errno, "fstat(%s) failed: %m", path);
×
1634
                st = &stbuf;
1635
        }
1636

1637
        /* Issuing the file attribute ioctls on device nodes is not safe, as that will be delivered to the
1638
         * drivers, not the file system containing the device node. */
1639
        if (!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode))
48✔
1640
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1641
                                       "Setting file flags is only supported on regular files and directories, cannot set on '%s'.",
1642
                                       path);
1643

1644
        f = item->attribute_value & item->attribute_mask;
48✔
1645

1646
        /* Mask away directory-specific flags */
1647
        if (!S_ISDIR(st->st_mode))
48✔
1648
                f &= ~FS_DIRSYNC_FL;
×
1649

1650
        log_action("Would try to set", "Trying to set",
96✔
1651
                   "%s file attributes 0x%08x on %s",
1652
                   f & item->attribute_mask,
1653
                   path);
1654

1655
        if (!arg_dry_run) {
48✔
1656
                _cleanup_close_ int procfs_fd = -EBADF;
48✔
1657

1658
                procfs_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOATIME);
48✔
1659
                if (procfs_fd < 0)
48✔
1660
                        return log_error_errno(procfs_fd, "Failed to reopen '%s': %m", path);
×
1661

1662
                unsigned previous, current;
48✔
1663
                r = chattr_full(procfs_fd, NULL, f, item->attribute_mask, &previous, &current, CHATTR_FALLBACK_BITWISE);
48✔
1664
                if (r == -ENOANO)
48✔
1665
                        log_warning("Cannot set file attributes for '%s', maybe due to incompatibility in specified attributes, "
×
1666
                                    "previous=0x%08x, current=0x%08x, expected=0x%08x, ignoring.",
1667
                                    path, previous, current, (previous & ~item->attribute_mask) | (f & item->attribute_mask));
1668
                else if (r < 0)
48✔
1669
                        log_full_errno(ERRNO_IS_IOCTL_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r,
48✔
1670
                                       "Cannot set file attributes for '%s', value=0x%08x, mask=0x%08x, ignoring: %m",
1671
                                       path, item->attribute_value, item->attribute_mask);
1672
        }
1673

1674
        return 0;
1675
}
1676

1677
static int path_set_attribute(
48✔
1678
                Context *c,
1679
                Item *item,
1680
                const char *path,
1681
                CreationMode creation) {
1682

1683
        _cleanup_close_ int fd = -EBADF;
48✔
1684

1685
        assert(c);
48✔
1686
        assert(item);
48✔
1687

1688
        if (!item->attribute_set || item->attribute_mask == 0)
48✔
1689
                return 0;
1690

1691
        fd = path_open_safe(path);
48✔
1692
        if (fd == -ENOENT)
48✔
1693
                return 0;
1694
        if (fd < 0)
48✔
1695
                return fd;
1696

1697
        return fd_set_attribute(c, item, fd, path, /* st= */ NULL, creation);
48✔
1698
}
1699

1700
static int write_argument_data(Item *i, int fd, const char *path) {
266✔
1701
        int r;
266✔
1702

1703
        assert(i);
266✔
1704
        assert(fd >= 0);
266✔
1705
        assert(path);
266✔
1706

1707
        if (item_binary_argument_size(i) == 0)
266✔
1708
                return 0;
1709

1710
        assert(item_binary_argument(i));
258✔
1711

1712
        log_action("Would write", "Writing", "%s to \"%s\"", path);
484✔
1713

1714
        if (!arg_dry_run) {
258✔
1715
                r = loop_write(fd, item_binary_argument(i), item_binary_argument_size(i));
255✔
1716
                if (r < 0)
255✔
1717
                        return log_error_errno(r, "Failed to write file \"%s\": %m", path);
×
1718
        }
1719

1720
        return 0;
1721
}
1722

1723
static int write_one_file(Context *c, Item *i, const char *path, CreationMode creation) {
18✔
1724
        _cleanup_close_ int fd = -EBADF, dir_fd = -EBADF;
18✔
1725
        _cleanup_free_ char *bn = NULL;
18✔
1726
        int r;
18✔
1727

1728
        assert(c);
18✔
1729
        assert(i);
18✔
1730
        assert(path);
18✔
1731
        assert(i->type == WRITE_FILE);
18✔
1732

1733
        r = path_extract_filename(path, &bn);
18✔
1734
        if (r < 0)
18✔
1735
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
1736
        if (r == O_DIRECTORY)
18✔
1737
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for writing, is a directory.", path);
×
1738

1739
        /* Validate the path and keep the fd on the directory for opening the file so we're sure that it
1740
         * can't be changed behind our back. */
1741
        dir_fd = path_open_parent_safe(path, i->allow_failure);
18✔
1742
        if (dir_fd < 0)
18✔
1743
                return dir_fd;
1744

1745
        /* Follow symlinks. Open with O_PATH in dry-run mode to make sure we don't use the path inadvertently. */
1746
        int flags = O_NONBLOCK | O_CLOEXEC | O_WRONLY | O_NOCTTY | i->append_or_force * O_APPEND | arg_dry_run * O_PATH;
18✔
1747
        fd = openat(dir_fd, bn, flags, i->mode);
18✔
1748
        if (fd < 0) {
18✔
1749
                if (errno == ENOENT) {
×
1750
                        log_debug_errno(errno, "Not writing missing file \"%s\": %m", path);
×
1751
                        return 0;
×
1752
                }
1753

1754
                if (i->allow_failure)
×
1755
                        return log_debug_errno(errno, "Failed to open file \"%s\", ignoring: %m", path);
×
1756

1757
                return log_error_errno(errno, "Failed to open file \"%s\": %m", path);
×
1758
        }
1759

1760
        /* 'w' is allowed to write into any kind of files. */
1761

1762
        r = write_argument_data(i, fd, path);
18✔
1763
        if (r < 0)
18✔
1764
                return r;
1765

1766
        return fd_set_perms(c, i, fd, path, NULL, creation);
18✔
1767
}
1768

1769
static int create_file(
776✔
1770
                Context *c,
1771
                Item *i,
1772
                const char *path) {
1773

1774
        _cleanup_close_ int fd = -EBADF, dir_fd = -EBADF;
776✔
1775
        _cleanup_free_ char *bn = NULL;
776✔
1776
        struct stat stbuf, *st = NULL;
776✔
1777
        CreationMode creation;
776✔
1778
        int r = 0;
776✔
1779

1780
        assert(c);
776✔
1781
        assert(i);
776✔
1782
        assert(path);
776✔
1783
        assert(i->type == CREATE_FILE);
776✔
1784

1785
        /* 'f' operates on regular files exclusively. */
1786

1787
        r = path_extract_filename(path, &bn);
776✔
1788
        if (r < 0)
776✔
1789
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
1790
        if (r == O_DIRECTORY)
776✔
1791
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for writing, is a directory.", path);
×
1792

1793
        if (arg_dry_run) {
776✔
1794
                log_info("Would create file %s", path);
4✔
1795
                return 0;
4✔
1796

1797
                /* The opening of the directory below would fail if it doesn't exist,
1798
                 * so log and exit before even trying to do that. */
1799
        }
1800

1801
        /* Validate the path and keep the fd on the directory for opening the file so we're sure that it
1802
         * can't be changed behind our back. */
1803
        dir_fd = path_open_parent_safe(path, i->allow_failure);
772✔
1804
        if (dir_fd < 0)
772✔
1805
                return dir_fd;
1806

1807
        WITH_UMASK(0000) {
1,540✔
1808
                mac_selinux_create_file_prepare(path, S_IFREG);
770✔
1809
                fd = RET_NERRNO(openat(dir_fd, bn, O_CREAT|O_EXCL|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode));
770✔
1810
                mac_selinux_create_file_clear();
770✔
1811
        }
1812

1813
        if (fd < 0) {
770✔
1814
                /* Even on a read-only filesystem, open(2) returns EEXIST if the file already exists. It
1815
                 * returns EROFS only if it needs to create the file. */
1816
                if (fd != -EEXIST)
642✔
1817
                        return log_error_errno(fd, "Failed to create file %s: %m", path);
1✔
1818

1819
                /* Re-open the file. At that point it must exist since open(2) failed with EEXIST. We still
1820
                 * need to check if the perms/mode need to be changed. For read-only filesystems, we let
1821
                 * fd_set_perms() report the error if the perms need to be modified. */
1822
                fd = openat(dir_fd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH, i->mode);
641✔
1823
                if (fd < 0)
641✔
1824
                        return log_error_errno(errno, "Failed to reopen file %s: %m", path);
×
1825

1826
                if (fstat(fd, &stbuf) < 0)
641✔
1827
                        return log_error_errno(errno, "stat(%s) failed: %m", path);
×
1828

1829
                if (!S_ISREG(stbuf.st_mode))
641✔
1830
                        return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
5✔
1831
                                               "%s exists and is not a regular file.",
1832
                                               path);
1833

1834
                st = &stbuf;
1835
                creation = CREATION_EXISTING;
1836
        } else {
1837
                r = write_argument_data(i, fd, path);
128✔
1838
                if (r < 0)
128✔
1839
                        return r;
1840

1841
                creation = CREATION_NORMAL;
1842
        }
1843

1844
        return fd_set_perms(c, i, fd, path, st, creation);
764✔
1845
}
1846

1847
static int truncate_file(
247✔
1848
                Context *c,
1849
                Item *i,
1850
                const char *path) {
1851

1852
        _cleanup_close_ int fd = -EBADF, dir_fd = -EBADF;
247✔
1853
        _cleanup_free_ char *bn = NULL;
247✔
1854
        struct stat stbuf, *st = NULL;
247✔
1855
        CreationMode creation;
247✔
1856
        bool erofs = false;
247✔
1857
        int r = 0;
247✔
1858

1859
        assert(c);
247✔
1860
        assert(i);
247✔
1861
        assert(path);
247✔
1862
        assert(i->type == TRUNCATE_FILE || (i->type == CREATE_FILE && i->append_or_force));
247✔
1863

1864
        /* We want to operate on regular file exclusively especially since O_TRUNC is unspecified if the file
1865
         * is neither a regular file nor a fifo nor a terminal device. Therefore we first open the file and
1866
         * make sure it's a regular one before truncating it. */
1867

1868
        r = path_extract_filename(path, &bn);
247✔
1869
        if (r < 0)
247✔
1870
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
1871
        if (r == O_DIRECTORY)
247✔
1872
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for truncation, is a directory.", path);
×
1873

1874
        /* Validate the path and keep the fd on the directory for opening the file so we're sure that it
1875
         * can't be changed behind our back. */
1876
        dir_fd = path_open_parent_safe(path, i->allow_failure);
247✔
1877
        if (dir_fd < 0)
247✔
1878
                return dir_fd;
1879

1880
        if (arg_dry_run) {
246✔
1881
                log_info("Would truncate %s", path);
×
1882
                return 0;
×
1883
        }
1884

1885
        creation = CREATION_EXISTING;
246✔
1886
        fd = RET_NERRNO(openat(dir_fd, bn, O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode));
246✔
1887
        if (fd == -ENOENT) {
222✔
1888
                creation = CREATION_NORMAL; /* Didn't work without O_CREATE, try again with */
217✔
1889

1890
                WITH_UMASK(0000) {
434✔
1891
                        mac_selinux_create_file_prepare(path, S_IFREG);
217✔
1892
                        fd = RET_NERRNO(openat(dir_fd, bn, O_CREAT|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode));
217✔
1893
                        mac_selinux_create_file_clear();
217✔
1894
                }
1895
        }
1896

1897
        if (fd < 0) {
246✔
1898
                if (fd != -EROFS)
6✔
1899
                        return log_error_errno(fd, "Failed to open/create file %s: %m", path);
1✔
1900

1901
                /* On a read-only filesystem, we don't want to fail if the target is already empty and the
1902
                 * perms are set. So we still proceed with the sanity checks and let the remaining operations
1903
                 * fail with EROFS if they try to modify the target file. */
1904

1905
                fd = openat(dir_fd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH, i->mode);
5✔
1906
                if (fd < 0) {
5✔
1907
                        if (errno == ENOENT)
1✔
1908
                                return log_error_errno(SYNTHETIC_ERRNO(EROFS),
1✔
1909
                                                       "Cannot create file %s on a read-only file system.",
1910
                                                       path);
1911

1912
                        return log_error_errno(errno, "Failed to reopen file %s: %m", path);
×
1913
                }
1914

1915
                erofs = true;
1916
                creation = CREATION_EXISTING;
1917
        }
1918

1919
        if (fstat(fd, &stbuf) < 0)
244✔
1920
                return log_error_errno(errno, "stat(%s) failed: %m", path);
×
1921

1922
        if (!S_ISREG(stbuf.st_mode))
244✔
1923
                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
1924
                                       "%s exists and is not a regular file.",
1925
                                       path);
1926

1927
        if (stbuf.st_size > 0) {
244✔
1928
                if (ftruncate(fd, 0) < 0) {
15✔
1929
                        r = erofs ? -EROFS : -errno;
2✔
1930
                        return log_error_errno(r, "Failed to truncate file %s: %m", path);
2✔
1931
                }
1932
        } else
1933
                st = &stbuf;
1934

1935
        log_debug("\"%s\" has been created.", path);
242✔
1936

1937
        if (item_binary_argument(i)) {
242✔
1938
                r = write_argument_data(i, fd, path);
120✔
1939
                if (r < 0)
120✔
1940
                        return r;
1941
        }
1942

1943
        return fd_set_perms(c, i, fd, path, st, creation);
242✔
1944
}
1945

1946
static int copy_files(Context *c, Item *i) {
3,797✔
1947
        _cleanup_close_ int dfd = -EBADF, fd = -EBADF;
3,797✔
1948
        _cleanup_free_ char *bn = NULL;
3,797✔
1949
        struct stat st, a;
3,797✔
1950
        int r;
3,797✔
1951

1952
        log_action("Would copy", "Copying", "%s tree \"%s\" to \"%s\"", i->argument, i->path);
7,455✔
1953
        if (arg_dry_run)
3,797✔
1954
                return 0;
1955

1956
        r = path_extract_filename(i->path, &bn);
3,795✔
1957
        if (r < 0)
3,795✔
1958
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path);
×
1959

1960
        /* Validate the path and use the returned directory fd for copying the target so we're sure that the
1961
         * path can't be changed behind our back. */
1962
        dfd = path_open_parent_safe(i->path, i->allow_failure);
3,795✔
1963
        if (dfd < 0)
3,795✔
1964
                return dfd;
1965

1966
        r = copy_tree_at(AT_FDCWD, i->argument,
3,799✔
1967
                         dfd, bn,
1968
                         i->uid_set ? i->uid : UID_INVALID,
3,795✔
1969
                         i->gid_set ? i->gid : GID_INVALID,
3,795✔
1970
                         COPY_REFLINK | ((i->append_or_force) ? COPY_MERGE : COPY_MERGE_EMPTY) | COPY_MAC_CREATE | COPY_HARDLINKS,
3,795✔
1971
                         NULL, NULL);
1972

1973
        fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
3,795✔
1974
        if (fd < 0) {
3,795✔
1975
                if (r < 0) /* Look at original error first */
×
1976
                        return log_error_errno(r, "Failed to copy files to %s: %m", i->path);
×
1977

1978
                return log_error_errno(errno, "Failed to openat(%s): %m", i->path);
×
1979
        }
1980

1981
        if (fstat(fd, &st) < 0)
3,795✔
1982
                return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
×
1983

1984
        if (stat(i->argument, &a) < 0)
3,795✔
1985
                return log_error_errno(errno, "Failed to stat(%s): %m", i->argument);
×
1986

1987
        if (((st.st_mode ^ a.st_mode) & S_IFMT) != 0) {
3,795✔
1988
                log_debug("Can't copy to %s, file exists already and is of different type", i->path);
×
1989
                return 0;
×
1990
        }
1991

1992
        return fd_set_perms(c, i, fd, i->path, &st, _CREATION_MODE_INVALID);
3,795✔
1993
}
1994

1995
static int create_directory_or_subvolume(
8,137✔
1996
                const char *path,
1997
                mode_t mode,
1998
                bool subvol,
1999
                bool allow_failure,
2000
                struct stat *ret_st,
2001
                CreationMode *ret_creation) {
2002

2003
        _cleanup_free_ char *bn = NULL;
8,137✔
2004
        _cleanup_close_ int pfd = -EBADF;
8,137✔
2005
        CreationMode creation;
8,137✔
2006
        struct stat st;
8,137✔
2007
        int r, fd;
8,137✔
2008

2009
        assert(path);
8,137✔
2010

2011
        r = path_extract_filename(path, &bn);
8,137✔
2012
        if (r < 0)
8,137✔
2013
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
2014

2015
        pfd = path_open_parent_safe(path, allow_failure);
8,137✔
2016
        if (pfd < 0)
8,137✔
2017
                return pfd;
2018

2019
        if (subvol) {
8,135✔
2020
                r = getenv_bool("SYSTEMD_TMPFILES_FORCE_SUBVOL");
861✔
2021
                if (r < 0) {
861✔
2022
                        if (r != -ENXIO) /* env var is unset */
861✔
2023
                                log_warning_errno(r, "Cannot parse value of $SYSTEMD_TMPFILES_FORCE_SUBVOL, ignoring.");
×
2024
                        r = btrfs_is_subvol(empty_to_root(arg_root)) > 0;
861✔
2025
                }
2026
                if (r == 0)
861✔
2027
                        /* Don't create a subvolume unless the root directory is one, too. We do this under
2028
                         * the assumption that if the root directory is just a plain directory (i.e. very
2029
                         * lightweight), we shouldn't try to split it up into subvolumes (i.e. more
2030
                         * heavy-weight). Thus, chroot() environments and suchlike will get a full brtfs
2031
                         * subvolume set up below their tree only if they specifically set up a btrfs
2032
                         * subvolume for the root dir too. */
2033
                        subvol = false;
2034
                else {
2035
                        log_action("Would create", "Creating", "%s btrfs subvolume %s", path);
×
2036
                        if (!arg_dry_run)
×
2037
                                WITH_UMASK((~mode) & 0777)
×
2038
                                        r = btrfs_subvol_make(pfd, bn);
×
2039
                        else
2040
                                r = 0;
2041
                }
2042
        } else
2043
                r = 0;
2044

2045
        if (!subvol || ERRNO_IS_NEG_NOT_SUPPORTED(r)) {
×
2046
                log_action("Would create", "Creating", "%s directory \"%s\"", path);
15,965✔
2047
                if (!arg_dry_run)
8,135✔
2048
                        WITH_UMASK(0000)
16,270✔
2049
                                r = mkdirat_label(pfd, bn, mode);
8,135✔
2050
        }
2051

2052
        if (arg_dry_run)
8,135✔
2053
                return 0;
2054

2055
        creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
8,135✔
2056

2057
        fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH);
8,135✔
2058
        if (fd < 0) {
8,135✔
2059
                /* We couldn't open it because it is not actually a directory? */
2060
                if (errno == ENOTDIR)
×
2061
                        return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "\"%s\" already exists and is not a directory.", path);
×
2062

2063
                /* Then look at the original error */
2064
                if (r < 0)
×
2065
                        return log_full_errno(allow_failure ? LOG_INFO : LOG_ERR,
×
2066
                                              r,
2067
                                              "Failed to create directory or subvolume \"%s\"%s: %m",
2068
                                              path,
2069
                                              allow_failure ? ", ignoring" : "");
2070

2071
                return log_error_errno(errno, "Failed to open directory/subvolume we just created '%s': %m", path);
×
2072
        }
2073

2074
        if (fstat(fd, &st) < 0)
8,135✔
2075
                return log_error_errno(errno, "Failed to fstat(%s): %m", path);
×
2076

2077
        assert(S_ISDIR(st.st_mode)); /* we used O_DIRECTORY above */
8,135✔
2078

2079
        log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), path);
8,135✔
2080

2081
        if (ret_st)
8,135✔
2082
                *ret_st = st;
8,135✔
2083
        if (ret_creation)
8,135✔
2084
                *ret_creation = creation;
8,135✔
2085

2086
        return fd;
2087
}
2088

2089
static int create_directory(
7,287✔
2090
                Context *c,
2091
                Item *i,
2092
                const char *path) {
2093

2094
        _cleanup_close_ int fd = -EBADF;
7,287✔
2095
        CreationMode creation;
7,287✔
2096
        struct stat st;
7,287✔
2097

2098
        assert(c);
7,287✔
2099
        assert(i);
7,287✔
2100
        assert(IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY));
7,287✔
2101

2102
        if (arg_dry_run) {
7,287✔
2103
                log_info("Would create directory %s", path);
11✔
2104
                return 0;
11✔
2105
        }
2106

2107
        fd = create_directory_or_subvolume(path, i->mode, /* subvol= */ false, i->allow_failure, &st, &creation);
7,276✔
2108
        if (fd == -EEXIST)
7,276✔
2109
                return 0;
2110
        if (fd < 0)
7,276✔
2111
                return fd;
2112

2113
        return fd_set_perms(c, i, fd, path, &st, creation);
7,274✔
2114
}
2115

2116
static int create_subvolume(
861✔
2117
                Context *c,
2118
                Item *i,
2119
                const char *path) {
2120

2121
        _cleanup_close_ int fd = -EBADF;
861✔
2122
        CreationMode creation;
861✔
2123
        struct stat st;
861✔
2124
        int r, q = 0;
861✔
2125

2126
        assert(c);
861✔
2127
        assert(i);
861✔
2128
        assert(IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA));
861✔
2129

2130
        if (arg_dry_run) {
861✔
2131
                log_info("Would create subvolume %s", path);
×
2132
                return 0;
×
2133
        }
2134

2135
        fd = create_directory_or_subvolume(path, i->mode, /* subvol= */ true, i->allow_failure, &st, &creation);
861✔
2136
        if (fd == -EEXIST)
861✔
2137
                return 0;
2138
        if (fd < 0)
861✔
2139
                return fd;
2140

2141
        if (creation == CREATION_NORMAL &&
861✔
2142
            IN_SET(i->type, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)) {
3✔
2143
                r = btrfs_subvol_auto_qgroup_fd(fd, 0, i->type == CREATE_SUBVOLUME_NEW_QUOTA);
3✔
2144
                if (r == -ENOTTY)
3✔
2145
                        log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (unsupported fs or dir not a subvolume): %m", i->path);
3✔
2146
                else if (r == -EROFS)
×
2147
                        log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (fs is read-only).", i->path);
×
2148
                else if (r == -ENOTCONN)
×
2149
                        log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (quota support is disabled).", i->path);
×
2150
                else if (r < 0)
×
2151
                        q = log_error_errno(r, "Failed to adjust quota for subvolume \"%s\": %m", i->path);
×
2152
                else if (r > 0)
×
2153
                        log_debug("Adjusted quota for subvolume \"%s\".", i->path);
×
2154
                else if (r == 0)
×
2155
                        log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path);
×
2156
        }
2157

2158
        r = fd_set_perms(c, i, fd, path, &st, creation);
861✔
2159
        if (q < 0) /* prefer the quota change error from above */
861✔
2160
                return q;
×
2161

2162
        return r;
2163
}
2164

2165
static int empty_directory(
5✔
2166
                Context *c,
2167
                Item *i,
2168
                const char *path,
2169
                CreationMode creation) {
2170

2171
        _cleanup_close_ int fd = -EBADF;
5✔
2172
        struct stat st;
5✔
2173
        int r;
5✔
2174

2175
        assert(c);
5✔
2176
        assert(i);
5✔
2177
        assert(i->type == EMPTY_DIRECTORY);
5✔
2178

2179
        r = chase(path, arg_root, CHASE_SAFE|CHASE_WARN, NULL, &fd);
5✔
2180
        if (r == -ENOLINK) /* Unsafe symlink: already covered by CHASE_WARN */
5✔
2181
                return r;
2182
        if (r == -ENOENT) {
5✔
2183
                /* Option "e" operates only on existing objects. Do not print errors about non-existent files
2184
                 * or directories */
2185
                log_debug_errno(r, "Skipping missing directory: %s", path);
×
2186
                return 0;
×
2187
        }
2188
        if (r < 0)
5✔
2189
                return log_error_errno(r, "Failed to open directory '%s': %m", path);
×
2190

2191
        if (fstat(fd, &st) < 0)
5✔
2192
                return log_error_errno(errno, "Failed to fstat(%s): %m", path);
×
2193
        if (!S_ISDIR(st.st_mode)) {
5✔
2194
                log_warning("'%s' already exists and is not a directory.", path);
1✔
2195
                return 0;
1✔
2196
        }
2197

2198
        return fd_set_perms(c, i, fd, path, &st, creation);
4✔
2199
}
2200

2201
static int create_device(
1,894✔
2202
                Context *c,
2203
                Item *i,
2204
                mode_t file_type) {
2205

2206
        _cleanup_close_ int dfd = -EBADF, fd = -EBADF;
1,894✔
2207
        _cleanup_free_ char *bn = NULL;
1,894✔
2208
        CreationMode creation;
1,894✔
2209
        struct stat st;
1,894✔
2210
        int r;
1,894✔
2211

2212
        assert(c);
1,894✔
2213
        assert(i);
1,894✔
2214
        assert(IN_SET(i->type, CREATE_BLOCK_DEVICE, CREATE_CHAR_DEVICE));
1,894✔
2215
        assert(IN_SET(file_type, S_IFBLK, S_IFCHR));
1,894✔
2216

2217
        r = path_extract_filename(i->path, &bn);
1,894✔
2218
        if (r < 0)
1,894✔
2219
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path);
×
2220
        if (r == O_DIRECTORY)
1,894✔
2221
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR),
×
2222
                                       "Cannot open path '%s' for creating device node, is a directory.", i->path);
2223

2224
        if (arg_dry_run) {
1,894✔
2225
                log_info("Would create device node %s", i->path);
4✔
2226
                return 0;
4✔
2227
        }
2228

2229
        /* Validate the path and use the returned directory fd for copying the target so we're sure that the
2230
         * path can't be changed behind our back. */
2231
        dfd = path_open_parent_safe(i->path, i->allow_failure);
1,890✔
2232
        if (dfd < 0)
1,890✔
2233
                return dfd;
2234

2235
        WITH_UMASK(0000) {
3,780✔
2236
                mac_selinux_create_file_prepare(i->path, file_type);
1,890✔
2237
                r = RET_NERRNO(mknodat(dfd, bn, i->mode | file_type, i->major_minor));
1,890✔
2238
                mac_selinux_create_file_clear();
1,890✔
2239
        }
2240
        creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
1,890✔
2241

2242
        /* Try to open the inode via O_PATH, regardless if we could create it or not. Maybe everything is in
2243
         * order anyway and we hence can ignore the error to create the device node */
2244
        fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
1,890✔
2245
        if (fd < 0) {
1,890✔
2246
                /* OK, so opening the inode failed, let's look at the original error then. */
2247

2248
                if (r < 0) {
×
2249
                        if (ERRNO_IS_PRIVILEGE(r))
×
2250
                                goto handle_privilege;
×
2251

2252
                        return log_error_errno(r, "Failed to create device node '%s': %m", i->path);
×
2253
                }
2254

2255
                return log_error_errno(errno, "Failed to open device node '%s' we just created: %m", i->path);
×
2256
        }
2257

2258
        if (fstat(fd, &st) < 0)
1,890✔
2259
                return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
×
2260

2261
        if (((st.st_mode ^ file_type) & S_IFMT) != 0) {
1,890✔
2262

2263
                if (i->append_or_force) {
×
2264
                        fd = safe_close(fd);
×
2265

2266
                        WITH_UMASK(0000) {
×
2267
                                mac_selinux_create_file_prepare(i->path, file_type);
×
2268
                                r = mknodat_atomic(dfd, bn, i->mode | file_type, i->major_minor);
×
2269
                                mac_selinux_create_file_clear();
×
2270
                        }
2271
                        if (ERRNO_IS_PRIVILEGE(r))
×
2272
                                goto handle_privilege;
×
2273
                        if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) {
×
2274
                                r = rm_rf_child(dfd, bn, REMOVE_PHYSICAL);
×
2275
                                if (r < 0)
×
2276
                                        return log_error_errno(r, "rm -rf %s failed: %m", i->path);
×
2277

2278
                                mac_selinux_create_file_prepare(i->path, file_type);
×
2279
                                r = RET_NERRNO(mknodat(dfd, bn, i->mode | file_type, i->major_minor));
×
2280
                                mac_selinux_create_file_clear();
×
2281
                        }
2282
                        if (r < 0)
×
2283
                                return log_error_errno(r, "Failed to create device node '%s': %m", i->path);
×
2284

2285
                        fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
×
2286
                        if (fd < 0)
×
2287
                                return log_error_errno(errno, "Failed to open device node we just created '%s': %m", i->path);
×
2288

2289
                        /* Validate type before change ownership below */
2290
                        if (fstat(fd, &st) < 0)
×
2291
                                return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
×
2292

2293
                        if (((st.st_mode ^ file_type) & S_IFMT) != 0)
×
2294
                                return log_error_errno(SYNTHETIC_ERRNO(EBADF),
×
2295
                                                       "Device node we just created is not a device node, refusing.");
2296

2297
                        creation = CREATION_FORCE;
2298
                } else {
2299
                        log_warning("\"%s\" already exists and is not a device node.", i->path);
×
2300
                        return 0;
×
2301
                }
2302
        }
2303

2304
        log_debug("%s %s device node \"%s\" %u:%u.",
3,780✔
2305
                  creation_mode_verb_to_string(creation),
2306
                  i->type == CREATE_BLOCK_DEVICE ? "block" : "char",
2307
                  i->path, major(i->mode), minor(i->mode));
2308

2309
        return fd_set_perms(c, i, fd, i->path, &st, creation);
1,890✔
2310

2311
handle_privilege:
×
2312
        log_debug_errno(r,
1,894✔
2313
                        "We lack permissions, possibly because of cgroup configuration; "
2314
                        "skipping creation of device node '%s'.", i->path);
2315
        return 0;
2316
}
2317

2318
static int create_fifo(Context *c, Item *i) {
4✔
2319
        _cleanup_close_ int pfd = -EBADF, fd = -EBADF;
4✔
2320
        _cleanup_free_ char *bn = NULL;
4✔
2321
        CreationMode creation;
4✔
2322
        struct stat st;
4✔
2323
        int r;
4✔
2324

2325
        assert(c);
4✔
2326
        assert(i);
4✔
2327
        assert(i->type == CREATE_FIFO);
4✔
2328

2329
        r = path_extract_filename(i->path, &bn);
4✔
2330
        if (r < 0)
4✔
2331
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path);
×
2332
        if (r == O_DIRECTORY)
4✔
2333
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR),
×
2334
                                       "Cannot open path '%s' for creating FIFO, is a directory.", i->path);
2335

2336
        if (arg_dry_run) {
4✔
2337
                log_info("Would create fifo %s", i->path);
1✔
2338
                return 0;
1✔
2339
        }
2340

2341
        pfd = path_open_parent_safe(i->path, i->allow_failure);
3✔
2342
        if (pfd < 0)
3✔
2343
                return pfd;
2344

2345
        WITH_UMASK(0000) {
6✔
2346
                mac_selinux_create_file_prepare(i->path, S_IFIFO);
3✔
2347
                r = RET_NERRNO(mkfifoat(pfd, bn, i->mode));
3✔
2348
                mac_selinux_create_file_clear();
3✔
2349
        }
2350

2351
        creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
3✔
2352

2353
        /* Open the inode via O_PATH, regardless if we managed to create it or not. Maybe it is already the FIFO we want */
2354
        fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
3✔
2355
        if (fd < 0) {
3✔
2356
                if (r < 0)
×
2357
                        return log_error_errno(r, "Failed to create FIFO %s: %m", i->path); /* original error! */
×
2358

2359
                return log_error_errno(errno, "Failed to open FIFO we just created %s: %m", i->path);
×
2360
        }
2361

2362
        if (fstat(fd, &st) < 0)
3✔
2363
                return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
×
2364

2365
        if (!S_ISFIFO(st.st_mode)) {
3✔
2366

2367
                if (i->append_or_force) {
2✔
2368
                        fd = safe_close(fd);
1✔
2369

2370
                        WITH_UMASK(0000) {
2✔
2371
                                mac_selinux_create_file_prepare(i->path, S_IFIFO);
1✔
2372
                                r = mkfifoat_atomic(pfd, bn, i->mode);
1✔
2373
                                mac_selinux_create_file_clear();
1✔
2374
                        }
2375
                        if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) {
1✔
2376
                                r = rm_rf_child(pfd, bn, REMOVE_PHYSICAL);
×
2377
                                if (r < 0)
×
2378
                                        return log_error_errno(r, "rm -rf %s failed: %m", i->path);
×
2379

2380
                                mac_selinux_create_file_prepare(i->path, S_IFIFO);
×
2381
                                r = RET_NERRNO(mkfifoat(pfd, bn, i->mode));
×
2382
                                mac_selinux_create_file_clear();
×
2383
                        }
2384
                        if (r < 0)
1✔
2385
                                return log_error_errno(r, "Failed to create FIFO %s: %m", i->path);
×
2386

2387
                        fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
1✔
2388
                        if (fd < 0)
1✔
2389
                                return log_error_errno(errno, "Failed to open FIFO we just created '%s': %m", i->path);
×
2390

2391
                        /* Validate type before change ownership below */
2392
                        if (fstat(fd, &st) < 0)
1✔
2393
                                return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
×
2394

2395
                        if (!S_ISFIFO(st.st_mode))
1✔
2396
                                return log_error_errno(SYNTHETIC_ERRNO(EBADF),
×
2397
                                                       "FIFO inode we just created is not a FIFO, refusing.");
2398

2399
                        creation = CREATION_FORCE;
2400
                } else {
2401
                        log_warning("\"%s\" already exists and is not a FIFO.", i->path);
1✔
2402
                        return 0;
1✔
2403
                }
2404
        }
2405

2406
        log_debug("%s fifo \"%s\".", creation_mode_verb_to_string(creation), i->path);
2✔
2407

2408
        return fd_set_perms(c, i, fd, i->path, &st, creation);
2✔
2409
}
2410

2411
static int create_symlink(Context *c, Item *i) {
1,482✔
2412
        _cleanup_close_ int pfd = -EBADF, fd = -EBADF;
1,482✔
2413
        _cleanup_free_ char *bn = NULL;
1,482✔
2414
        CreationMode creation;
1,482✔
2415
        struct stat st;
1,482✔
2416
        bool good = false;
1,482✔
2417
        int r;
1,482✔
2418

2419
        assert(c);
1,482✔
2420
        assert(i);
1,482✔
2421

2422
        if (i->ignore_if_target_missing) {
1,482✔
2423
                r = chase(i->argument, arg_root, CHASE_SAFE|CHASE_PREFIX_ROOT|CHASE_NOFOLLOW, /* ret_path= */ NULL, /* ret_fd= */ NULL);
2✔
2424
                if (r == -ENOENT) {
2✔
2425
                        /* Silently skip over lines where the source file is missing. */
2426
                        log_info("Symlink source path '%s/%s' does not exist, skipping line.",
1✔
2427
                                 empty_to_root(arg_root), skip_leading_slash(i->argument));
2428
                        return 0;
1✔
2429
                }
2430
                if (r < 0)
1✔
2431
                        return log_error_errno(r, "Failed to check if symlink source path '%s/%s' exists: %m",
×
2432
                                               empty_to_root(arg_root), skip_leading_slash(i->argument));
2433
        }
2434

2435
        r = path_extract_filename(i->path, &bn);
1,481✔
2436
        if (r < 0)
1,481✔
2437
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path);
×
2438
        if (r == O_DIRECTORY)
1,481✔
2439
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR),
×
2440
                                       "Cannot open path '%s' for creating symlink, is a directory.", i->path);
2441

2442
        if (arg_dry_run) {
1,481✔
2443
                log_info("Would create symlink %s -> %s", i->path, i->argument);
2✔
2444
                return 0;
2✔
2445
        }
2446

2447
        pfd = path_open_parent_safe(i->path, i->allow_failure);
1,479✔
2448
        if (pfd < 0)
1,479✔
2449
                return pfd;
2450

2451
        mac_selinux_create_file_prepare(i->path, S_IFLNK);
1,479✔
2452
        r = RET_NERRNO(symlinkat(i->argument, pfd, bn));
1,479✔
2453
        mac_selinux_create_file_clear();
1,479✔
2454

2455
        creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
1,479✔
2456

2457
        fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
1,479✔
2458
        if (fd < 0) {
1,479✔
2459
                if (r < 0)
×
2460
                        return log_error_errno(r, "Failed to create symlink '%s': %m", i->path); /* original error! */
×
2461

2462
                return log_error_errno(errno, "Failed to open symlink we just created '%s': %m", i->path);
×
2463
        }
2464

2465
        if (fstat(fd, &st) < 0)
1,479✔
2466
                return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
×
2467

2468
        if (S_ISLNK(st.st_mode)) {
1,479✔
2469
                _cleanup_free_ char *x = NULL;
×
2470

2471
                r = readlinkat_malloc(fd, "", &x);
1,478✔
2472
                if (r < 0)
1,478✔
2473
                        return log_error_errno(r, "readlinkat(%s) failed: %m", i->path);
×
2474

2475
                good = streq(x, i->argument);
1,478✔
2476
        } else
2477
                good = false;
2478

2479
        if (!good) {
1,478✔
2480
                if (!i->append_or_force) {
496✔
2481
                        log_debug("\"%s\" is not a symlink or does not point to the correct path.", i->path);
492✔
2482
                        return 0;
492✔
2483
                }
2484

2485
                fd = safe_close(fd);
4✔
2486

2487
                r = symlinkat_atomic_full(i->argument, pfd, bn, SYMLINK_LABEL);
4✔
2488
                if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) {
4✔
2489
                        r = rm_rf_child(pfd, bn, REMOVE_PHYSICAL);
1✔
2490
                        if (r < 0)
1✔
2491
                                return log_error_errno(r, "rm -rf %s failed: %m", i->path);
×
2492

2493
                        r = symlinkat_atomic_full(i->argument, pfd, bn, SYMLINK_LABEL);
1✔
2494
                }
2495
                if (r < 0)
4✔
2496
                        return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path);
×
2497

2498
                fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
4✔
2499
                if (fd < 0)
4✔
2500
                        return log_error_errno(errno, "Failed to open symlink we just created '%s': %m", i->path);
×
2501

2502
                /* Validate type before change ownership below */
2503
                if (fstat(fd, &st) < 0)
4✔
2504
                        return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
×
2505

2506
                if (!S_ISLNK(st.st_mode))
4✔
2507
                        return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Symlink we just created is not a symlink, refusing.");
×
2508

2509
                creation = CREATION_FORCE;
2510
        }
2511

2512
        log_debug("%s symlink \"%s\".", creation_mode_verb_to_string(creation), i->path);
987✔
2513
        return fd_set_perms(c, i, fd, i->path, &st, creation);
987✔
2514
}
2515

2516
typedef int (*action_t)(Context *c, Item *i, const char *path, CreationMode creation);
2517
typedef int (*fdaction_t)(Context *c, Item *i, int fd, const char *path, const struct stat *st, CreationMode creation);
2518

2519
static int item_do(
605✔
2520
                Context *c,
2521
                Item *i,
2522
                int fd,
2523
                const char *path,
2524
                CreationMode creation,
2525
                fdaction_t action) {
2526

2527
        struct stat st;
605✔
2528
        int r;
605✔
2529

2530
        assert(c);
605✔
2531
        assert(i);
605✔
2532
        assert(fd >= 0);
605✔
2533
        assert(path);
605✔
2534
        assert(action);
605✔
2535

2536
        if (fstat(fd, &st) < 0) {
605✔
2537
                r = log_error_errno(errno, "fstat() on file failed: %m");
×
2538
                goto finish;
×
2539
        }
2540

2541
        /* This returns the first error we run into, but nevertheless tries to go on */
2542
        r = action(c, i, fd, path, &st, creation);
605✔
2543

2544
        if (S_ISDIR(st.st_mode)) {
605✔
2545
                _cleanup_closedir_ DIR *d = NULL;
200✔
2546

2547
                /* The passed 'fd' was opened with O_PATH. We need to convert it into a 'regular' fd before
2548
                 * reading the directory content. */
2549
                d = opendir(FORMAT_PROC_FD_PATH(fd));
200✔
2550
                if (!d) {
200✔
2551
                        RET_GATHER(r, log_error_errno(errno, "Failed to opendir() '%s': %m", FORMAT_PROC_FD_PATH(fd)));
×
2552
                        goto finish;
×
2553
                }
2554

2555
                FOREACH_DIRENT_ALL(de, d, RET_GATHER(r, -errno); goto finish) {
1,005✔
2556
                        _cleanup_close_ int de_fd = -EBADF;
805✔
2557
                        _cleanup_free_ char *de_path = NULL;
805✔
2558

2559
                        if (dot_or_dot_dot(de->d_name))
805✔
2560
                                continue;
400✔
2561

2562
                        de_fd = openat(fd, de->d_name, O_NOFOLLOW|O_CLOEXEC|O_PATH);
405✔
2563
                        if (de_fd < 0) {
405✔
2564
                                if (errno != ENOENT)
×
2565
                                        RET_GATHER(r, log_error_errno(errno, "Failed to open file '%s': %m", de->d_name));
×
2566
                                continue;
×
2567
                        }
2568

2569
                        de_path = path_join(path, de->d_name);
405✔
2570
                        if (!de_path) {
405✔
2571
                                r = log_oom();
×
2572
                                goto finish;
×
2573
                        }
2574

2575
                        /* Pass ownership of dirent fd over */
2576
                        RET_GATHER(r, item_do(c, i, TAKE_FD(de_fd), de_path, CREATION_EXISTING, action));
405✔
2577
                }
2578
        }
2579

2580
finish:
405✔
2581
        safe_close(fd);
605✔
2582
        return r;
605✔
2583
}
2584

2585
static int glob_item(Context *c, Item *i, action_t action) {
5,902✔
2586
        _cleanup_strv_free_ char **paths = NULL;
5,902✔
2587
        int r;
5,902✔
2588

2589
        assert(c);
5,902✔
2590
        assert(i);
5,902✔
2591
        assert(action);
5,902✔
2592

2593
        r = safe_glob_full(i->path, GLOB_NOSORT|GLOB_BRACE, opendir_nomod, &paths);
5,902✔
2594
        if (r == -ENOENT)
5,902✔
2595
                return 0;
2596
        if (r < 0)
2,857✔
2597
                return log_error_errno(r, "Failed to glob '%s': %m", i->path);
×
2598

2599
        r = 0;
2,857✔
2600
        STRV_FOREACH(fn, paths)
5,721✔
2601
                /* We pass CREATION_EXISTING here, since if we are globbing for it, it always has to exist */
2602
                RET_GATHER(r, action(c, i, *fn, CREATION_EXISTING));
2,864✔
2603

2604
        return r;
2605
}
2606

2607
static int glob_item_recursively(
248✔
2608
                Context *c,
2609
                Item *i,
2610
                fdaction_t action) {
2611

2612
        _cleanup_strv_free_ char **paths = NULL;
248✔
2613
        int r;
248✔
2614

2615
        assert(c);
248✔
2616
        assert(i);
248✔
2617
        assert(action);
248✔
2618

2619
        r = safe_glob_full(i->path, GLOB_NOSORT|GLOB_BRACE, opendir_nomod, &paths);
248✔
2620
        if (r == -ENOENT)
248✔
2621
                return 0;
2622
        if (r < 0)
200✔
2623
                return log_error_errno(r, "Failed to glob '%s': %m", i->path);
×
2624

2625
        r = 0;
200✔
2626
        STRV_FOREACH(fn, paths) {
400✔
2627
                _cleanup_close_ int fd = -EBADF;
200✔
2628

2629
                /* Make sure we won't trigger/follow file object (such as device nodes, automounts, ...)
2630
                 * pointed out by 'fn' with O_PATH. Note, when O_PATH is used, flags other than
2631
                 * O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW are ignored. */
2632

2633
                fd = open(*fn, O_CLOEXEC|O_NOFOLLOW|O_PATH);
200✔
2634
                if (fd < 0) {
200✔
2635
                        RET_GATHER(r, log_error_errno(errno, "Failed to open '%s': %m", *fn));
×
2636
                        continue;
×
2637
                }
2638

2639
                RET_GATHER(r, item_do(c, i, TAKE_FD(fd), *fn, CREATION_EXISTING, action));
200✔
2640
        }
2641

2642
        return r;
2643
}
2644

2645
static int rm_if_wrong_type_safe(
×
2646
                mode_t mode,
2647
                int parent_fd,
2648
                const struct stat *parent_st, /* Only used if follow_links below is true. */
2649
                const char *name,
2650
                int flags) {
2651
        _cleanup_free_ char *parent_name = NULL;
×
2652
        bool follow_links = !FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW);
×
2653
        struct stat st;
×
2654
        int r;
×
2655

2656
        assert(name);
×
2657
        assert((mode & ~S_IFMT) == 0);
×
2658
        assert(!follow_links || parent_st);
×
2659
        assert((flags & ~AT_SYMLINK_NOFOLLOW) == 0);
×
2660

2661
        if (mode == 0)
×
2662
                return 0;
2663

2664
        if (!filename_is_valid(name))
×
2665
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "\"%s\" is not a valid filename.", name);
×
2666

2667
        r = fstatat_harder(parent_fd, name, &st, flags, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE);
×
2668
        if (r < 0) {
×
2669
                (void) fd_get_path(parent_fd, &parent_name);
×
2670
                return log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_ERR, r,
×
2671
                                      "Failed to stat \"%s/%s\": %m", parent_name ?: "...", name);
2672
        }
2673

2674
        /* Fail before removing anything if this is an unsafe transition. */
2675
        if (follow_links && unsafe_transition(parent_st, &st)) {
×
2676
                (void) fd_get_path(parent_fd, &parent_name);
×
2677
                return log_error_errno(SYNTHETIC_ERRNO(ENOLINK),
×
2678
                                       "Unsafe transition from \"%s\" to \"%s\".", parent_name ?: "...", name);
2679
        }
2680

2681
        if ((st.st_mode & S_IFMT) == mode)
×
2682
                return 0;
2683

2684
        (void) fd_get_path(parent_fd, &parent_name);
×
2685
        log_notice("Wrong file type 0o%o; rm -rf \"%s/%s\"", st.st_mode & S_IFMT, parent_name ?: "...", name);
×
2686

2687
        /* If the target of the symlink was the wrong type, the link needs to be removed instead of the
2688
         * target, so make sure it is identified as a link and not a directory. */
2689
        if (follow_links) {
×
2690
                r = fstatat_harder(parent_fd, name, &st, AT_SYMLINK_NOFOLLOW, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE);
×
2691
                if (r < 0)
×
2692
                        return log_error_errno(r, "Failed to stat \"%s/%s\": %m", parent_name ?: "...", name);
×
2693
        }
2694

2695
        /* Do not remove mount points. */
2696
        r = is_mount_point_at(parent_fd, name, follow_links ? AT_SYMLINK_FOLLOW : 0);
×
2697
        if (r < 0)
×
2698
                (void) log_warning_errno(r, "Failed to check if  \"%s/%s\" is a mount point: %m; continuing.",
×
2699
                                         parent_name ?: "...", name);
2700
        else if (r > 0)
×
2701
                return log_error_errno(SYNTHETIC_ERRNO(EBUSY),
×
2702
                                "Not removing  \"%s/%s\" because it is a mount point.", parent_name ?: "...", name);
2703

2704
        log_action("Would remove", "Removing", "%s %s/%s", parent_name ?: "...", name);
×
2705
        if (!arg_dry_run) {
×
2706
                if ((st.st_mode & S_IFMT) == S_IFDIR) {
×
2707
                        _cleanup_close_ int child_fd = -EBADF;
×
2708

2709
                        child_fd = openat(parent_fd, name, O_NOCTTY | O_CLOEXEC | O_DIRECTORY);
×
2710
                        if (child_fd < 0)
×
2711
                                return log_error_errno(errno, "Failed to open \"%s/%s\": %m", parent_name ?: "...", name);
×
2712

2713
                        r = rm_rf_children(TAKE_FD(child_fd), REMOVE_ROOT|REMOVE_SUBVOLUME|REMOVE_PHYSICAL, &st);
×
2714
                        if (r < 0)
×
2715
                                return log_error_errno(r, "Failed to remove contents of \"%s/%s\": %m", parent_name ?: "...", name);
×
2716

2717
                        r = unlinkat_harder(parent_fd, name, AT_REMOVEDIR, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE);
×
2718
                } else
2719
                        r = unlinkat_harder(parent_fd, name, 0, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE);
×
2720
                if (r < 0)
×
2721
                        return log_error_errno(r, "Failed to remove \"%s/%s\": %m", parent_name ?: "...", name);
×
2722
        }
2723

2724
        /* This is covered by the log_notice "Wrong file type...".
2725
         * It is logged earlier because it gives context to other error messages that might follow. */
2726
        return -ENOENT;
2727
}
2728

2729
/* If child_mode is non-zero, rm_if_wrong_type_safe will be executed for the last path component. */
2730
static int mkdir_parents_rm_if_wrong_type(mode_t child_mode, const char *path) {
×
2731
        _cleanup_close_ int parent_fd = -EBADF;
×
2732
        struct stat parent_st;
×
2733
        size_t path_len;
×
2734
        int r;
×
2735

2736
        assert(path);
×
2737
        assert((child_mode & ~S_IFMT) == 0);
×
2738

2739
        path_len = strlen(path);
×
2740

2741
        if (!is_path(path))
×
2742
                /* rm_if_wrong_type_safe already logs errors. */
2743
                return rm_if_wrong_type_safe(child_mode, AT_FDCWD, NULL, path, AT_SYMLINK_NOFOLLOW);
×
2744

2745
        if (child_mode != 0 && endswith(path, "/"))
×
2746
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2747
                                "Trailing path separators are only allowed if child_mode is not set; got \"%s\"", path);
2748

2749
        /* Get the parent_fd and stat. */
2750
        parent_fd = openat(AT_FDCWD, path_is_absolute(path) ? "/" : ".", O_NOCTTY | O_CLOEXEC | O_DIRECTORY);
×
2751
        if (parent_fd < 0)
×
2752
                return log_error_errno(errno, "Failed to open root: %m");
×
2753

2754
        if (fstat(parent_fd, &parent_st) < 0)
×
2755
                return log_error_errno(errno, "Failed to stat root: %m");
×
2756

2757
        /* Check every parent directory in the path, except the last component */
2758
        for (const char *e = path;;) {
×
2759
                _cleanup_close_ int next_fd = -EBADF;
×
2760
                char t[path_len + 1];
×
2761
                const char *s;
×
2762

2763
                /* Find the start of the next path component. */
2764
                s = e + strspn(e, "/");
×
2765
                /* Find the end of the next path component. */
2766
                e = s + strcspn(s, "/");
×
2767

2768
                /* Copy the path component to t so it can be a null terminated string. */
2769
                *mempcpy_typesafe(t, s, e - s) = 0;
×
2770

2771
                /* Is this the last component? If so, then check the type */
2772
                if (*e == 0)
×
2773
                        return rm_if_wrong_type_safe(child_mode, parent_fd, &parent_st, t, AT_SYMLINK_NOFOLLOW);
×
2774

2775
                r = rm_if_wrong_type_safe(S_IFDIR, parent_fd, &parent_st, t, 0);
×
2776
                /* Remove dangling symlinks. */
2777
                if (r == -ENOENT)
×
2778
                        r = rm_if_wrong_type_safe(S_IFDIR, parent_fd, &parent_st, t, AT_SYMLINK_NOFOLLOW);
×
2779
                if (r == -ENOENT) {
×
2780
                        if (!arg_dry_run) {
×
2781
                                WITH_UMASK(0000)
×
2782
                                        r = mkdirat_label(parent_fd, t, 0755);
×
2783
                                if (r < 0) {
×
2784
                                        _cleanup_free_ char *parent_name = NULL;
×
2785

2786
                                        (void) fd_get_path(parent_fd, &parent_name);
×
2787
                                        return log_error_errno(r, "Failed to mkdir \"%s\" at \"%s\": %m", t, strnull(parent_name));
×
2788
                                }
2789
                        }
2790
                } else if (r < 0)
×
2791
                        /* rm_if_wrong_type_safe already logs errors. */
2792
                        return r;
2793

2794
                next_fd = RET_NERRNO(openat(parent_fd, t, O_NOCTTY | O_CLOEXEC | O_DIRECTORY));
×
2795
                if (next_fd < 0) {
×
2796
                        _cleanup_free_ char *parent_name = NULL;
×
2797

2798
                        (void) fd_get_path(parent_fd, &parent_name);
×
2799
                        return log_error_errno(next_fd, "Failed to open \"%s\" at \"%s\": %m", t, strnull(parent_name));
×
2800
                }
2801
                r = RET_NERRNO(fstat(next_fd, &parent_st));
×
2802
                if (r < 0) {
×
2803
                        _cleanup_free_ char *parent_name = NULL;
×
2804

2805
                        (void) fd_get_path(parent_fd, &parent_name);
×
2806
                        return log_error_errno(r, "Failed to stat \"%s\" at \"%s\": %m", t, strnull(parent_name));
×
2807
                }
2808

2809
                close_and_replace(parent_fd, next_fd);
×
2810
        }
2811
}
2812

2813
static int mkdir_parents_item(Item *i, mode_t child_mode) {
16,348✔
2814
        int r;
16,348✔
2815

2816
        if (i->try_replace) {
16,348✔
2817
                r = mkdir_parents_rm_if_wrong_type(child_mode, i->path);
×
2818
                if (r < 0 && r != -ENOENT)
×
2819
                        return r;
×
2820
        } else
2821
                WITH_UMASK(0000)
32,696✔
2822
                        if (!arg_dry_run)
16,348✔
2823
                                (void) mkdir_parents_label(i->path, 0755);
16,348✔
2824

2825
        return 0;
2826
}
2827

2828
static int create_item(Context *c, Item *i) {
23,327✔
2829
        int r;
23,327✔
2830

2831
        assert(c);
23,327✔
2832
        assert(i);
23,327✔
2833

2834
        log_debug("Running create action for entry %c %s", (char) i->type, i->path);
23,327✔
2835

2836
        switch (i->type) {
23,327✔
2837

2838
        case IGNORE_PATH:
2839
        case IGNORE_DIRECTORY_PATH:
2840
        case REMOVE_PATH:
2841
        case RECURSIVE_REMOVE_PATH:
2842
                return 0;
2843

2844
        case TRUNCATE_FILE:
1,023✔
2845
        case CREATE_FILE:
2846
                r = mkdir_parents_item(i, S_IFREG);
1,023✔
2847
                if (r < 0)
1,023✔
2848
                        return r;
2849

2850
                if ((i->type == CREATE_FILE && i->append_or_force) || i->type == TRUNCATE_FILE)
1,023✔
2851
                        r = truncate_file(c, i, i->path);
247✔
2852
                else
2853
                        r = create_file(c, i, i->path);
776✔
2854
                if (r < 0)
1,023✔
2855
                        return r;
15✔
2856
                break;
2857

2858
        case COPY_FILES:
3,797✔
2859
                r = mkdir_parents_item(i, 0);
3,797✔
2860
                if (r < 0)
3,797✔
2861
                        return r;
2862

2863
                r = copy_files(c, i);
3,797✔
2864
                if (r < 0)
3,797✔
2865
                        return r;
×
2866
                break;
2867

2868
        case WRITE_FILE:
20✔
2869
                r = glob_item(c, i, write_one_file);
20✔
2870
                if (r < 0)
20✔
2871
                        return r;
×
2872

2873
                break;
2874

2875
        case CREATE_DIRECTORY:
7,287✔
2876
        case TRUNCATE_DIRECTORY:
2877
                r = mkdir_parents_item(i, S_IFDIR);
7,287✔
2878
                if (r < 0)
7,287✔
2879
                        return r;
2880

2881
                r = create_directory(c, i, i->path);
7,287✔
2882
                if (r < 0)
7,287✔
2883
                        return r;
2✔
2884
                break;
2885

2886
        case CREATE_SUBVOLUME:
861✔
2887
        case CREATE_SUBVOLUME_INHERIT_QUOTA:
2888
        case CREATE_SUBVOLUME_NEW_QUOTA:
2889
                r = mkdir_parents_item(i, S_IFDIR);
861✔
2890
                if (r < 0)
861✔
2891
                        return r;
2892

2893
                r = create_subvolume(c, i, i->path);
861✔
2894
                if (r < 0)
861✔
2895
                        return r;
×
2896
                break;
2897

2898
        case EMPTY_DIRECTORY:
4✔
2899
                r = glob_item(c, i, empty_directory);
4✔
2900
                if (r < 0)
4✔
2901
                        return r;
×
2902
                break;
2903

2904
        case CREATE_FIFO:
4✔
2905
                r = mkdir_parents_item(i, S_IFIFO);
4✔
2906
                if (r < 0)
4✔
2907
                        return r;
2908

2909
                r = create_fifo(c, i);
4✔
2910
                if (r < 0)
4✔
2911
                        return r;
×
2912
                break;
2913

2914
        case CREATE_SYMLINK:
1,482✔
2915
                r = mkdir_parents_item(i, S_IFLNK);
1,482✔
2916
                if (r < 0)
1,482✔
2917
                        return r;
2918

2919
                r = create_symlink(c, i);
1,482✔
2920
                if (r < 0)
1,482✔
2921
                        return r;
×
2922

2923
                break;
2924

2925
        case CREATE_BLOCK_DEVICE:
1,894✔
2926
        case CREATE_CHAR_DEVICE:
2927
                if (have_effective_cap(CAP_MKNOD) <= 0) {
1,894✔
2928
                        /* In a container we lack CAP_MKNOD. We shouldn't attempt to create the device node in that
2929
                         * case to avoid noise, and we don't support virtualized devices in containers anyway. */
2930

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

2935
                r = mkdir_parents_item(i, i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR);
3,786✔
2936
                if (r < 0)
1,894✔
2937
                        return r;
2938

2939
                r = create_device(c, i, i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR);
3,786✔
2940
                if (r < 0)
1,894✔
2941
                        return r;
×
2942

2943
                break;
2944

2945
        case ADJUST_MODE:
4,019✔
2946
        case RELABEL_PATH:
2947
                r = glob_item(c, i, path_set_perms);
4,019✔
2948
                if (r < 0)
4,019✔
2949
                        return r;
×
2950
                break;
2951

2952
        case RECURSIVE_RELABEL_PATH:
125✔
2953
                r = glob_item_recursively(c, i, fd_set_perms);
125✔
2954
                if (r < 0)
125✔
2955
                        return r;
×
2956
                break;
2957

2958
        case SET_XATTR:
×
2959
                r = glob_item(c, i, path_set_xattrs);
×
2960
                if (r < 0)
×
2961
                        return r;
×
2962
                break;
2963

2964
        case RECURSIVE_SET_XATTR:
×
2965
                r = glob_item_recursively(c, i, fd_set_xattrs);
×
2966
                if (r < 0)
×
2967
                        return r;
×
2968
                break;
2969

2970
        case SET_ACL:
742✔
2971
                r = glob_item(c, i, path_set_acls);
742✔
2972
                if (r < 0)
742✔
2973
                        return r;
×
2974
                break;
2975

2976
        case RECURSIVE_SET_ACL:
123✔
2977
                r = glob_item_recursively(c, i, fd_set_acls);
123✔
2978
                if (r < 0)
123✔
2979
                        return r;
×
2980
                break;
2981

2982
        case SET_ATTRIBUTE:
369✔
2983
                r = glob_item(c, i, path_set_attribute);
369✔
2984
                if (r < 0)
369✔
2985
                        return r;
×
2986
                break;
2987

2988
        case RECURSIVE_SET_ATTRIBUTE:
×
2989
                r = glob_item_recursively(c, i, fd_set_attribute);
×
2990
                if (r < 0)
×
2991
                        return r;
×
2992
                break;
2993
        }
2994

2995
        return 0;
2996
}
2997

2998
static int remove_recursive(
493✔
2999
                Context *c,
3000
                Item *i,
3001
                const char *instance,
3002
                bool remove_instance) {
3003

3004
        _cleanup_closedir_ DIR *d = NULL;
493✔
3005
        struct statx sx;
493✔
3006
        bool mountpoint;
493✔
3007
        int r;
493✔
3008

3009
        r = opendir_and_stat(instance, &d, &sx, &mountpoint);
493✔
3010
        if (r < 0)
493✔
3011
                return r;
3012
        if (r == 0) {
493✔
3013
                if (remove_instance) {
483✔
3014
                        log_action("Would remove", "Removing", "%s file \"%s\".", instance);
13✔
3015
                        if (!arg_dry_run &&
14✔
3016
                            remove(instance) < 0 &&
5✔
3017
                            errno != ENOENT)
2✔
3018
                                return log_error_errno(errno, "rm %s: %m", instance);
×
3019
                }
3020
                return 0;
483✔
3021
        }
3022

3023
        r = dir_cleanup(c, i, instance, d,
20✔
3024
                        /* self_atime_nsec= */ NSEC_INFINITY,
3025
                        /* self_mtime_nsec= */ NSEC_INFINITY,
3026
                        /* cutoff_nsec= */ NSEC_INFINITY,
3027
                        sx.stx_dev_major, sx.stx_dev_minor,
10✔
3028
                        mountpoint,
3029
                        MAX_DEPTH,
3030
                        /* keep_this_level= */ false,
3031
                        /* age_by_file= */ 0,
3032
                        /* age_by_dir= */ 0);
3033
        if (r < 0)
10✔
3034
                return r;
3035

3036
        if (remove_instance) {
10✔
3037
                log_action("Would remove", "Removing", "%s directory \"%s\".", instance);
11✔
3038
                if (!arg_dry_run) {
9✔
3039
                        r = RET_NERRNO(rmdir(instance));
496✔
3040
                        if (r < 0) {
3✔
3041
                                bool fatal = !IN_SET(r, -ENOENT, -ENOTEMPTY);
3✔
3042
                                log_full_errno(fatal ? LOG_ERR : LOG_DEBUG, r, "Failed to remove %s: %m", instance);
3✔
3043
                                if (fatal)
3✔
3044
                                        return r;
×
3045
                        }
3046
                }
3047
        }
3048
        return 0;
3049
}
3050

3051
static int purge_item_instance(Context *c, Item *i, const char *instance, CreationMode creation) {
10✔
3052
        return remove_recursive(c, i, instance, /* remove_instance= */ true);
10✔
3053
}
3054

3055
static int purge_item(Context *c, Item *i) {
15✔
3056
        assert(i);
15✔
3057

3058
        if (!needs_purge(i->type))
15✔
3059
                return 0;
3060

3061
        if (!i->purge)
15✔
3062
                return 0;
3063

3064
        log_debug("Running purge action for entry %c %s", (char) i->type, i->path);
10✔
3065

3066
        if (needs_glob(i->type))
10✔
3067
                return glob_item(c, i, purge_item_instance);
×
3068

3069
        return purge_item_instance(c, i, i->path, CREATION_EXISTING);
10✔
3070
}
3071

3072
static int remove_item_instance(
16✔
3073
                Context *c,
3074
                Item *i,
3075
                const char *instance,
3076
                CreationMode creation) {
3077

3078
        assert(c);
16✔
3079
        assert(i);
16✔
3080

3081
        switch (i->type) {
16✔
3082

3083
        case REMOVE_PATH:
3084
                log_action("Would remove", "Removing", "%s \"%s\".", instance);
8✔
3085
                if (!arg_dry_run &&
15✔
3086
                    remove(instance) < 0 &&
7✔
3087
                    errno != ENOENT)
×
3088
                        return log_error_errno(errno, "rm %s: %m", instance);
×
3089

3090
                return 0;
3091

3092
        case RECURSIVE_REMOVE_PATH:
8✔
3093
                return remove_recursive(c, i, instance, /* remove_instance= */ true);
8✔
3094

3095
        default:
×
3096
                assert_not_reached();
×
3097
        }
3098
}
3099

3100
static int remove_item(Context *c, Item *i) {
17,856✔
3101
        assert(c);
17,856✔
3102
        assert(i);
17,856✔
3103

3104
        log_debug("Running remove action for entry %c %s", (char) i->type, i->path);
17,856✔
3105

3106
        switch (i->type) {
17,856✔
3107

3108
        case TRUNCATE_DIRECTORY:
475✔
3109
                return remove_recursive(c, i, i->path, /* remove_instance= */ false);
475✔
3110

3111
        case REMOVE_PATH:
726✔
3112
        case RECURSIVE_REMOVE_PATH:
3113
                return glob_item(c, i, remove_item_instance);
726✔
3114

3115
        default:
3116
                return 0;
3117
        }
3118
}
3119

3120
static char *age_by_to_string(AgeBy ab, bool is_dir) {
44✔
3121
        static const char ab_map[] = { 'a', 'b', 'c', 'm' };
44✔
3122
        size_t j = 0;
44✔
3123
        char *ret;
44✔
3124

3125
        ret = new(char, ELEMENTSOF(ab_map) + 1);
44✔
3126
        if (!ret)
44✔
3127
                return NULL;
3128

3129
        for (size_t i = 0; i < ELEMENTSOF(ab_map); i++)
220✔
3130
                if (BIT_SET(ab, i))
176✔
3131
                        ret[j++] = is_dir ? ascii_toupper(ab_map[i]) : ab_map[i];
110✔
3132

3133
        ret[j] = 0;
44✔
3134
        return ret;
44✔
3135
}
3136

3137
static int clean_item_instance(
50✔
3138
                Context *c,
3139
                Item *i,
3140
                const char* instance,
3141
                CreationMode creation) {
3142

3143
        assert(i);
50✔
3144

3145
        if (!i->age_set)
50✔
3146
                return 0;
50✔
3147

3148
        usec_t n = now(CLOCK_REALTIME);
38✔
3149
        if (n < i->age)
38✔
3150
                return 0;
3151

3152
        usec_t cutoff = n - i->age;
38✔
3153

3154
        _cleanup_closedir_ DIR *d = NULL;
50✔
3155
        struct statx sx;
38✔
3156
        bool mountpoint;
38✔
3157
        int r;
38✔
3158

3159
        r = opendir_and_stat(instance, &d, &sx, &mountpoint);
38✔
3160
        if (r <= 0)
38✔
3161
                return r;
3162

3163
        if (DEBUG_LOGGING) {
34✔
3164
                _cleanup_free_ char *ab_f = NULL, *ab_d = NULL;
22✔
3165

3166
                ab_f = age_by_to_string(i->age_by_file, false);
22✔
3167
                if (!ab_f)
22✔
3168
                        return log_oom();
×
3169

3170
                ab_d = age_by_to_string(i->age_by_dir, true);
22✔
3171
                if (!ab_d)
22✔
3172
                        return log_oom();
×
3173

3174
                log_debug("Cleanup threshold for %s \"%s\" is %s; age-by: %s%s",
44✔
3175
                          mountpoint ? "mount point" : "directory",
3176
                          instance,
3177
                          FORMAT_TIMESTAMP_STYLE(cutoff, TIMESTAMP_US),
3178
                          ab_f, ab_d);
3179
        }
3180

3181
        return dir_cleanup(c, i, instance, d,
34✔
3182
                           statx_timestamp_load_nsec(&sx.stx_atime),
3183
                           statx_timestamp_load_nsec(&sx.stx_mtime),
3184
                           cutoff * NSEC_PER_USEC,
34✔
3185
                           sx.stx_dev_major, sx.stx_dev_minor,
34✔
3186
                           mountpoint,
3187
                           MAX_DEPTH, i->keep_first_level,
34✔
3188
                           i->age_by_file, i->age_by_dir);
3189
}
3190

3191
static int clean_item(Context *c, Item *i) {
50✔
3192
        assert(c);
50✔
3193
        assert(i);
50✔
3194

3195
        log_debug("Running clean action for entry %c %s", (char) i->type, i->path);
50✔
3196

3197
        switch (i->type) {
50✔
3198

3199
        case CREATE_DIRECTORY:
24✔
3200
        case TRUNCATE_DIRECTORY:
3201
        case CREATE_SUBVOLUME:
3202
        case CREATE_SUBVOLUME_INHERIT_QUOTA:
3203
        case CREATE_SUBVOLUME_NEW_QUOTA:
3204
        case COPY_FILES:
3205
                clean_item_instance(c, i, i->path, CREATION_EXISTING);
24✔
3206
                return 0;
24✔
3207

3208
        case EMPTY_DIRECTORY:
22✔
3209
        case IGNORE_PATH:
3210
        case IGNORE_DIRECTORY_PATH:
3211
                return glob_item(c, i, clean_item_instance);
22✔
3212

3213
        default:
3214
                return 0;
3215
        }
3216
}
3217

3218
static int process_item(
76,084✔
3219
                Context *c,
3220
                Item *i,
3221
                OperationMask operation) {
3222

3223
        OperationMask todo;
76,084✔
3224
        _cleanup_free_ char *_path = NULL;
76,084✔
3225
        const char *path;
76,084✔
3226
        int r;
76,084✔
3227

3228
        assert(c);
76,084✔
3229
        assert(i);
76,084✔
3230

3231
        todo = operation & ~i->done;
76,084✔
3232
        if (todo == 0) /* Everything already done? */
76,084✔
3233
                return 0;
3234

3235
        i->done |= operation;
41,248✔
3236

3237
        path = i->path;
41,248✔
3238
        if (string_is_glob(path)) {
41,248✔
3239
                /* We can't easily check whether a glob matches any autofs path, so let's do the check only
3240
                 * for the non-glob part. */
3241

3242
                r = glob_non_glob_prefix(path, &_path);
3,355✔
3243
                if (r < 0 && r != -ENOENT)
3,355✔
3244
                        return log_debug_errno(r, "Failed to deglob path: %m");
×
3245
                if (r >= 0)
3,355✔
3246
                        path = _path;
3,355✔
3247
        }
3248

3249
        r = chase(path, arg_root, CHASE_NO_AUTOFS|CHASE_NONEXISTENT|CHASE_WARN, NULL, NULL);
41,248✔
3250
        if (r == -EREMOTE) {
41,248✔
3251
                log_notice_errno(r, "Skipping %s", i->path); /* We log the configured path, to not confuse the user. */
×
3252
                return 0;
×
3253
        }
3254
        if (r < 0)
41,248✔
3255
                log_debug_errno(r, "Failed to determine whether '%s' is below autofs, ignoring: %m", i->path);
×
3256

3257
        r = FLAGS_SET(operation, OPERATION_CREATE) ? create_item(c, i) : 0;
41,248✔
3258
        /* Failure can only be tolerated for create */
3259
        if (i->allow_failure)
41,248✔
3260
                r = 0;
964✔
3261

3262
        RET_GATHER(r, FLAGS_SET(operation, OPERATION_REMOVE) ? remove_item(c, i) : 0);
41,248✔
3263
        RET_GATHER(r, FLAGS_SET(operation, OPERATION_CLEAN) ? clean_item(c, i) : 0);
41,248✔
3264
        RET_GATHER(r, FLAGS_SET(operation, OPERATION_PURGE) ? purge_item(c, i) : 0);
41,248✔
3265

3266
        return r;
3267
}
3268

3269
static int process_item_array(
69,933✔
3270
                Context *c,
3271
                ItemArray *array,
3272
                OperationMask operation) {
3273

3274
        int r = 0;
69,933✔
3275

3276
        assert(c);
69,933✔
3277
        assert(array);
69,933✔
3278

3279
        /* Create any parent first. */
3280
        if (FLAGS_SET(operation, OPERATION_CREATE) && array->parent)
69,933✔
3281
                r = process_item_array(c, array->parent, operation & OPERATION_CREATE);
16,437✔
3282

3283
        /* Clean up all children first */
3284
        if ((operation & (OPERATION_REMOVE|OPERATION_CLEAN|OPERATION_PURGE)) && !set_isempty(array->children)) {
69,933✔
3285
                ItemArray *cc;
6,045✔
3286

3287
                SET_FOREACH(cc, array->children)
20,827✔
3288
                        RET_GATHER(r, process_item_array(c, cc, operation & (OPERATION_REMOVE|OPERATION_CLEAN|OPERATION_PURGE)));
14,782✔
3289
        }
3290

3291
        FOREACH_ARRAY(item, array->items, array->n_items)
146,017✔
3292
                RET_GATHER(r, process_item(c, item, operation));
76,084✔
3293

3294
        return r;
69,933✔
3295
}
3296

3297
static void item_free_contents(Item *i) {
87,404✔
3298
        assert(i);
87,404✔
3299
        free(i->path);
87,404✔
3300
        free(i->argument);
87,404✔
3301
        free(i->binary_argument);
87,404✔
3302
        strv_free(i->xattrs);
87,404✔
3303

3304
#if HAVE_ACL
3305
        if (i->acl_access)
87,404✔
3306
                sym_acl_free(i->acl_access);
1,436✔
3307

3308
        if (i->acl_access_exec)
87,404✔
3309
                sym_acl_free(i->acl_access_exec);
363✔
3310

3311
        if (i->acl_default)
87,404✔
3312
                sym_acl_free(i->acl_default);
2,154✔
3313
#endif
3314
}
87,404✔
3315

3316
static ItemArray* item_array_free(ItemArray *a) {
22,057✔
3317
        if (!a)
22,057✔
3318
                return NULL;
3319

3320
        FOREACH_ARRAY(item, a->items, a->n_items)
45,468✔
3321
                item_free_contents(item);
23,411✔
3322

3323
        set_free(a->children);
22,057✔
3324
        free(a->items);
22,057✔
3325
        return mfree(a);
22,057✔
3326
}
3327

3328
static int item_compare(const Item *a, const Item *b) {
1,856✔
3329
        /* Make sure that the ownership taking item is put first, so
3330
         * that we first create the node, and then can adjust it */
3331

3332
        if (takes_ownership(a->type) && !takes_ownership(b->type))
1,856✔
3333
                return -1;
3334
        if (!takes_ownership(a->type) && takes_ownership(b->type))
1,856✔
3335
                return 1;
3336

3337
        return CMP(a->type, b->type);
1,856✔
3338
}
3339

3340
static bool item_compatible(const Item *a, const Item *b) {
1,611✔
3341
        assert(a);
1,611✔
3342
        assert(b);
1,611✔
3343
        assert(streq(a->path, b->path));
1,611✔
3344

3345
        if (takes_ownership(a->type) && takes_ownership(b->type))
1,611✔
3346
                /* check if the items are the same */
3347
                return memcmp_nn(item_binary_argument(a), item_binary_argument_size(a),
504✔
3348
                                 item_binary_argument(b), item_binary_argument_size(b)) == 0 &&
485✔
3349

3350
                        a->uid_set == b->uid_set &&
485✔
3351
                        a->uid == b->uid &&
485✔
3352
                        a->uid_only_create == b->uid_only_create &&
485✔
3353

3354
                        a->gid_set == b->gid_set &&
485✔
3355
                        a->gid == b->gid &&
485✔
3356
                        a->gid_only_create == b->gid_only_create &&
485✔
3357

3358
                        a->mode_set == b->mode_set &&
485✔
3359
                        a->mode == b->mode &&
485✔
3360
                        a->mode_only_create == b->mode_only_create &&
485✔
3361

3362
                        a->age_set == b->age_set &&
485✔
3363
                        a->age == b->age &&
485✔
3364

3365
                        a->age_by_file == b->age_by_file &&
485✔
3366
                        a->age_by_dir == b->age_by_dir &&
485✔
3367

3368
                        a->mask_perms == b->mask_perms &&
485✔
3369

3370
                        a->keep_first_level == b->keep_first_level &&
1,493✔
3371

3372
                        a->major_minor == b->major_minor;
485✔
3373

3374
        return true;
3375
}
3376

3377
static bool should_include_path(const char *path) {
63,902✔
3378
        STRV_FOREACH(prefix, arg_exclude_prefixes)
82,547✔
3379
                if (path_startswith(path, *prefix)) {
20,967✔
3380
                        log_debug("Entry \"%s\" matches exclude prefix \"%s\", skipping.",
2,322✔
3381
                                  path, *prefix);
3382
                        return false;
2,322✔
3383
                }
3384

3385
        STRV_FOREACH(prefix, arg_include_prefixes)
98,870✔
3386
                if (path_startswith(path, *prefix)) {
41,934✔
3387
                        log_debug("Entry \"%s\" matches include prefix \"%s\".", path, *prefix);
4,644✔
3388
                        return true;
4,644✔
3389
                }
3390

3391
        /* no matches, so we should include this path only if we have no allow list at all */
3392
        if (strv_isempty(arg_include_prefixes))
56,936✔
3393
                return true;
3394

3395
        log_debug("Entry \"%s\" does not match any include prefix, skipping.", path);
37,290✔
3396
        return false;
3397
}
3398

3399
static int specifier_expansion_from_arg(const Specifier *specifier_table, Item *i) {
24,290✔
3400
        int r;
24,290✔
3401

3402
        assert(i);
24,290✔
3403

3404
        if (!i->argument)
24,290✔
3405
                return 0;
3406

3407
        switch (i->type) {
5,775✔
3408
        case COPY_FILES:
2,646✔
3409
        case CREATE_SYMLINK:
3410
        case CREATE_FILE:
3411
        case TRUNCATE_FILE:
3412
        case WRITE_FILE: {
3413
                _cleanup_free_ char *unescaped = NULL, *resolved = NULL;
2,646✔
3414
                ssize_t l;
2,646✔
3415

3416
                l = cunescape(i->argument, 0, &unescaped);
2,646✔
3417
                if (l < 0)
2,646✔
3418
                        return log_error_errno(l, "Failed to unescape parameter to write: %s", i->argument);
×
3419

3420
                r = specifier_printf(unescaped, PATH_MAX-1, specifier_table, arg_root, NULL, &resolved);
2,646✔
3421
                if (r < 0)
2,646✔
3422
                        return r;
3423

3424
                return free_and_replace(i->argument, resolved);
2,646✔
3425
        }
3426
        case SET_XATTR:
×
3427
        case RECURSIVE_SET_XATTR:
3428
                STRV_FOREACH(xattr, i->xattrs) {
×
3429
                        _cleanup_free_ char *resolved = NULL;
×
3430

3431
                        r = specifier_printf(*xattr, SIZE_MAX, specifier_table, arg_root, NULL, &resolved);
×
3432
                        if (r < 0)
×
3433
                                return r;
×
3434

3435
                        free_and_replace(*xattr, resolved);
×
3436
                }
3437
                return 0;
3438

3439
        default:
3440
                return 0;
3441
        }
3442
}
3443

3444
static int patch_var_run(const char *fname, unsigned line, char **path) {
63,903✔
3445
        const char *k;
63,903✔
3446
        char *n;
63,903✔
3447

3448
        assert(path);
63,903✔
3449
        assert(*path);
63,903✔
3450

3451
        /* Optionally rewrites lines referencing /var/run/, to use /run/ instead. Why bother? tmpfiles merges
3452
         * lines in some cases and detects conflicts in others. If files/directories are specified through
3453
         * two equivalent lines this is problematic as neither case will be detected. Ideally we'd detect
3454
         * these cases by resolving symlinks early, but that's precisely not what we can do here as this code
3455
         * very likely is running very early on, at a time where the paths in question are not available yet,
3456
         * or even more importantly, our own tmpfiles rules might create the paths that are intermediary to
3457
         * the listed paths. We can't really cover the generic case, but the least we can do is cover the
3458
         * specific case of /var/run vs. /run, as /var/run is a legacy name for /run only, and we explicitly
3459
         * document that and require that on systemd systems the former is a symlink to the latter. Moreover
3460
         * files below this path are by far the primary use case for tmpfiles.d/. */
3461

3462
        k = path_startswith(*path, "/var/run/");
63,903✔
3463
        if (isempty(k)) /* Don't complain about paths other than under /var/run,
63,903✔
3464
                         * and not about /var/run itself either. */
3465
                return 0;
63,903✔
3466

3467
        n = path_join("/run", k);
×
3468
        if (!n)
×
3469
                return log_oom();
×
3470

3471
        /* Also log about this briefly. We do so at LOG_NOTICE level, as we fixed up the situation
3472
         * automatically, hence there's no immediate need for action by the user. However, in the interest of
3473
         * making things less confusing to the user, let's still inform the user that these snippets should
3474
         * really be updated. */
3475
        log_syntax(NULL, LOG_NOTICE, fname, line, 0,
×
3476
                   "Line references path below legacy directory /var/run/, updating %s → %s; please update the tmpfiles.d/ drop-in file accordingly.",
3477
                   *path, n);
3478

3479
        free_and_replace(*path, n);
×
3480

3481
        return 0;
×
3482
}
3483

3484
static int find_uid(const char *user, uid_t *ret_uid, Hashmap **cache) {
8,752✔
3485
        int r;
8,752✔
3486

3487
        assert(user);
8,752✔
3488
        assert(ret_uid);
8,752✔
3489

3490
        /* First: parse as numeric UID string */
3491
        r = parse_uid(user, ret_uid);
8,752✔
3492
        if (r >= 0)
8,752✔
3493
                return r;
3494

3495
        /* Second: pass to NSS if we are running "online" */
3496
        if (!arg_root)
8,738✔
3497
                return get_user_creds(&user, ret_uid, NULL, NULL, NULL, 0);
8,738✔
3498

3499
        /* Third, synthesize "root" unconditionally */
3500
        if (streq(user, "root")) {
×
3501
                *ret_uid = 0;
×
3502
                return 0;
×
3503
        }
3504

3505
        /* Fourth: use fgetpwent() to read /etc/passwd directly, if we are "offline" */
3506
        return name_to_uid_offline(arg_root, user, ret_uid, cache);
×
3507
}
3508

3509
static int find_gid(const char *group, gid_t *ret_gid, Hashmap **cache) {
10,198✔
3510
        int r;
10,198✔
3511

3512
        assert(group);
10,198✔
3513
        assert(ret_gid);
10,198✔
3514

3515
        /* First: parse as numeric GID string */
3516
        r = parse_gid(group, ret_gid);
10,198✔
3517
        if (r >= 0)
10,198✔
3518
                return r;
3519

3520
        /* Second: pass to NSS if we are running "online" */
3521
        if (!arg_root)
10,184✔
3522
                return get_group_creds(&group, ret_gid, 0);
10,184✔
3523

3524
        /* Third, synthesize "root" unconditionally */
3525
        if (streq(group, "root")) {
×
3526
                *ret_gid = 0;
×
3527
                return 0;
×
3528
        }
3529

3530
        /* Fourth: use fgetgrent() to read /etc/group directly, if we are "offline" */
3531
        return name_to_gid_offline(arg_root, group, ret_gid, cache);
×
3532
}
3533

3534
static int parse_age_by_from_arg(const char *age_by_str, Item *item) {
151✔
3535
        AgeBy ab_f = 0, ab_d = 0;
151✔
3536

3537
        static const struct {
151✔
3538
                char age_by_chr;
3539
                AgeBy age_by_flag;
3540
        } age_by_types[] = {
3541
                { 'a', AGE_BY_ATIME },
3542
                { 'b', AGE_BY_BTIME },
3543
                { 'c', AGE_BY_CTIME },
3544
                { 'm', AGE_BY_MTIME },
3545
        };
3546

3547
        assert(age_by_str);
151✔
3548
        assert(item);
151✔
3549

3550
        if (isempty(age_by_str))
151✔
3551
                return -EINVAL;
3552

3553
        for (const char *s = age_by_str; *s != 0; s++) {
321✔
3554
                size_t i;
176✔
3555

3556
                /* Ignore whitespace. */
3557
                if (strchr(WHITESPACE, *s))
176✔
3558
                        continue;
11✔
3559

3560
                for (i = 0; i < ELEMENTSOF(age_by_types); i++) {
227✔
3561
                        /* Check lower-case for files, upper-case for directories. */
3562
                        if (*s == age_by_types[i].age_by_chr) {
224✔
3563
                                ab_f |= age_by_types[i].age_by_flag;
152✔
3564
                                break;
152✔
3565
                        } else if (*s == ascii_toupper(age_by_types[i].age_by_chr)) {
72✔
3566
                                ab_d |= age_by_types[i].age_by_flag;
10✔
3567
                                break;
10✔
3568
                        }
3569
                }
3570

3571
                /* Invalid character. */
3572
                if (i >= ELEMENTSOF(age_by_types))
3573
                        return -EINVAL;
3574
        }
3575

3576
        /* No match. */
3577
        if (ab_f == 0 && ab_d == 0)
145✔
3578
                return -EINVAL;
3579

3580
        item->age_by_file = ab_f > 0 ? ab_f : AGE_BY_DEFAULT_FILE;
144✔
3581
        item->age_by_dir = ab_d > 0 ? ab_d : AGE_BY_DEFAULT_DIR;
144✔
3582

3583
        return 0;
144✔
3584
}
3585

3586
static bool is_duplicated_item(ItemArray *existing, const Item *i) {
1,357✔
3587
        assert(existing);
1,357✔
3588
        assert(i);
1,357✔
3589

3590
        FOREACH_ARRAY(e, existing->items, existing->n_items) {
2,965✔
3591
                if (item_compatible(e, i))
1,611✔
3592
                        continue;
1,592✔
3593

3594
                /* Only multiple 'w+' lines for the same path are allowed. */
3595
                if (e->type != WRITE_FILE || !e->append_or_force ||
19✔
3596
                    i->type != WRITE_FILE || !i->append_or_force)
16✔
3597
                        return true;
3598
        }
3599

3600
        return false;
3601
}
3602

3603
static int parse_line(
63,993✔
3604
                const char *fname,
3605
                unsigned line,
3606
                const char *buffer,
3607
                bool *invalid_config,
3608
                void *context) {
3609

3610
        Context *c = ASSERT_PTR(context);
63,993✔
3611
        _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
63,993✔
3612
        _cleanup_(item_free_contents) Item i = {
×
3613
                /* The "age-by" argument considers all file timestamp types by default. */
3614
                .age_by_file = AGE_BY_DEFAULT_FILE,
3615
                .age_by_dir = AGE_BY_DEFAULT_DIR,
3616
        };
3617
        ItemArray *existing;
63,993✔
3618
        OrderedHashmap *h;
63,993✔
3619
        bool append_or_force = false, boot = false, allow_failure = false, try_replace = false,
63,993✔
3620
                unbase64 = false, from_cred = false, missing_user_or_group = false, purge = false,
63,993✔
3621
                ignore_if_target_missing = false;
63,993✔
3622
        int r;
63,993✔
3623

3624
        assert(fname);
63,993✔
3625
        assert(line >= 1);
63,993✔
3626
        assert(buffer);
63,993✔
3627

3628
        const Specifier specifier_table[] = {
63,993✔
3629
                { 'h', specifier_user_home,       NULL },
3630

3631
                { 'C', specifier_directory,       UINT_TO_PTR(DIRECTORY_CACHE)   },
3632
                { 'L', specifier_directory,       UINT_TO_PTR(DIRECTORY_LOGS)    },
3633
                { 'S', specifier_directory,       UINT_TO_PTR(DIRECTORY_STATE)   },
3634
                { 't', specifier_directory,       UINT_TO_PTR(DIRECTORY_RUNTIME) },
3635

3636
                COMMON_SYSTEM_SPECIFIERS,
3637
                COMMON_CREDS_SPECIFIERS(arg_runtime_scope),
63,993✔
3638
                COMMON_TMP_SPECIFIERS,
3639
                {}
3640
        };
3641

3642
        r = extract_many_words(
63,993✔
3643
                        &buffer,
3644
                        NULL,
3645
                        EXTRACT_UNQUOTE | EXTRACT_CUNESCAPE,
3646
                        &action,
3647
                        &path,
3648
                        &mode,
3649
                        &user,
3650
                        &group,
3651
                        &age);
3652
        if (r < 0) {
63,993✔
3653
                if (IN_SET(r, -EINVAL, -EBADSLT))
×
3654
                        /* invalid quoting and such or an unknown specifier */
3655
                        *invalid_config = true;
×
3656
                return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to parse line: %m");
×
3657
        } else if (r < 2) {
63,993✔
3658
                *invalid_config = true;
×
3659
                return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Syntax error.");
×
3660
        }
3661

3662
        if (!empty_or_dash(buffer)) {
63,993✔
3663
                i.argument = strdup(buffer);
14,048✔
3664
                if (!i.argument)
14,048✔
3665
                        return log_oom();
×
3666
        }
3667

3668
        if (isempty(action)) {
63,993✔
3669
                *invalid_config = true;
×
3670
                return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3671
                                  "Command too short '%s'.", action);
3672
        }
3673

3674
        for (int pos = 1; action[pos]; pos++)
84,098✔
3675
                if (action[pos] == '!' && !boot)
20,105✔
3676
                        boot = true;
3677
                else if (action[pos] == '+' && !append_or_force)
10,808✔
3678
                        append_or_force = true;
3679
                else if (action[pos] == '-' && !allow_failure)
7,194✔
3680
                        allow_failure = true;
3681
                else if (action[pos] == '=' && !try_replace)
5,758✔
3682
                        try_replace = true;
3683
                else if (action[pos] == '~' && !unbase64)
5,758✔
3684
                        unbase64 = true;
3685
                else if (action[pos] == '^' && !from_cred)
5,758✔
3686
                        from_cred = true;
3687
                else if (action[pos] == '$' && !purge)
4,322✔
3688
                        purge = true;
3689
                else if (action[pos] == '?' && !ignore_if_target_missing)
2✔
3690
                        ignore_if_target_missing = true;
3691
                else {
3692
                        *invalid_config = true;
×
3693
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3694
                                          "Unknown modifiers in command: %s", action);
3695
                }
3696

3697
        if (boot && !arg_boot) {
63,993✔
3698
                log_syntax(NULL, LOG_DEBUG, fname, line, 0,
90✔
3699
                           "Ignoring entry %s \"%s\" because --boot is not specified.", action, path);
3700
                return 0;
90✔
3701
        }
3702

3703
        i.type = action[0];
63,903✔
3704
        i.append_or_force = append_or_force;
63,903✔
3705
        i.allow_failure = allow_failure;
63,903✔
3706
        i.try_replace = try_replace;
63,903✔
3707
        i.purge = purge;
63,903✔
3708
        i.ignore_if_target_missing = ignore_if_target_missing;
63,903✔
3709

3710
        r = specifier_printf(path, PATH_MAX-1, specifier_table, arg_root, NULL, &i.path);
63,903✔
3711
        if (ERRNO_IS_NEG_NOINFO(r))
63,903✔
3712
                return log_unresolvable_specifier(fname, line);
×
3713
        if (r < 0) {
63,903✔
3714
                if (IN_SET(r, -EINVAL, -EBADSLT))
×
3715
                        *invalid_config = true;
×
3716
                return log_syntax(NULL, LOG_ERR, fname, line, r,
×
3717
                                  "Failed to replace specifiers in '%s': %m", path);
3718
        }
3719

3720
        r = patch_var_run(fname, line, &i.path);
63,903✔
3721
        if (r < 0)
63,903✔
3722
                return r;
3723

3724
        if (!path_is_absolute(i.path)) {
63,903✔
3725
                *invalid_config = true;
×
3726
                return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3727
                                  "Path '%s' not absolute.", i.path);
3728
        }
3729

3730
        path_simplify(i.path);
63,903✔
3731

3732
        switch (i.type) {
63,903✔
3733

3734
        case CREATE_DIRECTORY:
36,581✔
3735
        case CREATE_SUBVOLUME:
3736
        case CREATE_SUBVOLUME_INHERIT_QUOTA:
3737
        case CREATE_SUBVOLUME_NEW_QUOTA:
3738
        case EMPTY_DIRECTORY:
3739
        case TRUNCATE_DIRECTORY:
3740
        case CREATE_FIFO:
3741
        case IGNORE_PATH:
3742
        case IGNORE_DIRECTORY_PATH:
3743
        case REMOVE_PATH:
3744
        case RECURSIVE_REMOVE_PATH:
3745
        case ADJUST_MODE:
3746
        case RELABEL_PATH:
3747
        case RECURSIVE_RELABEL_PATH:
3748
                if (i.argument)
36,581✔
3749
                        log_syntax(NULL, LOG_WARNING, fname, line, 0,
1✔
3750
                                   "%c lines don't take argument fields, ignoring.", (char) i.type);
3751
                break;
3752

3753
        case CREATE_FILE:
3754
        case TRUNCATE_FILE:
3755
                break;
3756

3757
        case CREATE_SYMLINK:
4,317✔
3758
                if (unbase64) {
4,317✔
3759
                        *invalid_config = true;
×
3760
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3761
                                          "base64 decoding not supported for symlink targets.");
3762
                }
3763
                break;
3764

3765
        case WRITE_FILE:
21✔
3766
                if (!i.argument) {
21✔
3767
                        *invalid_config = true;
1✔
3768
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
1✔
3769
                                          "Write file requires argument.");
3770
                }
3771
                break;
3772

3773
        case COPY_FILES:
12,550✔
3774
                if (unbase64) {
12,550✔
3775
                        *invalid_config = true;
×
3776
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3777
                                          "base64 decoding not supported for copy sources.");
3778
                }
3779
                break;
3780

3781
        case CREATE_CHAR_DEVICE:
2,839✔
3782
        case CREATE_BLOCK_DEVICE:
3783
                if (unbase64) {
2,839✔
3784
                        *invalid_config = true;
×
3785
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3786
                                          "base64 decoding not supported for device node creation.");
3787
                }
3788

3789
                if (!i.argument) {
2,839✔
3790
                        *invalid_config = true;
×
3791
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3792
                                          "Device file requires argument.");
3793
                }
3794

3795
                r = parse_devnum(i.argument, &i.major_minor);
2,839✔
3796
                if (r < 0) {
2,839✔
3797
                        *invalid_config = true;
×
3798
                        return log_syntax(NULL, LOG_ERR, fname, line, r,
×
3799
                                          "Can't parse device file major/minor '%s'.", i.argument);
3800
                }
3801

3802
                break;
3803

3804
        case SET_XATTR:
×
3805
        case RECURSIVE_SET_XATTR:
3806
                if (unbase64) {
×
3807
                        *invalid_config = true;
×
3808
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3809
                                          "base64 decoding not supported for extended attributes.");
3810
                }
3811
                if (!i.argument) {
×
3812
                        *invalid_config = true;
×
3813
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3814
                                          "Set extended attribute requires argument.");
3815
                }
3816
                r = parse_xattrs_from_arg(&i);
×
3817
                if (r < 0)
×
3818
                        return r;
3819
                break;
3820

3821
        case SET_ACL:
2,517✔
3822
        case RECURSIVE_SET_ACL:
3823
                if (unbase64) {
2,517✔
3824
                        *invalid_config = true;
×
3825
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3826
                                          "base64 decoding not supported for ACLs.");
3827
                }
3828
                if (!i.argument) {
2,517✔
3829
                        *invalid_config = true;
×
3830
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3831
                                          "Set ACLs requires argument.");
3832
                }
3833
                r = parse_acls_from_arg(&i);
2,517✔
3834
                if (r < 0)
2,517✔
3835
                        return r;
3836
                break;
3837

3838
        case SET_ATTRIBUTE:
1,077✔
3839
        case RECURSIVE_SET_ATTRIBUTE:
3840
                if (unbase64) {
1,077✔
3841
                        *invalid_config = true;
×
3842
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3843
                                          "base64 decoding not supported for file attributes.");
3844
                }
3845
                if (!i.argument) {
1,077✔
3846
                        *invalid_config = true;
×
3847
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3848
                                          "Set file attribute requires argument.");
3849
                }
3850
                r = parse_attribute_from_arg(&i);
1,077✔
3851
                if (IN_SET(r, -EINVAL, -EBADSLT))
1,077✔
3852
                        *invalid_config = true;
×
3853
                if (r < 0)
1,077✔
3854
                        return r;
3855
                break;
3856

3857
        default:
×
3858
                *invalid_config = true;
×
3859
                return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3860
                                  "Unknown command type '%c'.", (char) i.type);
3861
        }
3862

3863
        if (i.purge && !needs_purge(i.type)) {
63,902✔
3864
                *invalid_config = true;
×
3865
                return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3866
                                  "Purge flag '$' combined with line type '%c' which does not support purging.", (char) i.type);
3867
        }
3868

3869
        if (i.ignore_if_target_missing && !supports_ignore_if_target_missing(i.type)) {
63,902✔
3870
                *invalid_config = true;
×
3871
                return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3872
                                  "Modifier '?' combined with line type '%c' which does not support this modifier.", (char) i.type);
3873
        }
3874

3875
        if (!should_include_path(i.path))
63,902✔
3876
                return 0;
3877

3878
        if (!unbase64) {
24,290✔
3879
                /* Do specifier expansion except if base64 mode is enabled */
3880
                r = specifier_expansion_from_arg(specifier_table, &i);
24,290✔
3881
                if (ERRNO_IS_NEG_NOINFO(r))
24,290✔
3882
                        return log_unresolvable_specifier(fname, line);
×
3883
                if (r < 0) {
24,290✔
3884
                        if (IN_SET(r, -EINVAL, -EBADSLT))
×
3885
                                *invalid_config = true;
×
3886
                        return log_syntax(NULL, LOG_ERR, fname, line, r,
×
3887
                                          "Failed to substitute specifiers in argument: %m");
3888
                }
3889
        }
3890

3891
        switch (i.type) {
24,290✔
3892
        case CREATE_SYMLINK:
1,485✔
3893
                if (!i.argument) {
1,485✔
3894
                        i.argument = path_join("/usr/share/factory", i.path);
2✔
3895
                        if (!i.argument)
2✔
3896
                                return log_oom();
×
3897
                }
3898

3899
                break;
3900

3901
        case COPY_FILES:
4,290✔
3902
                if (!i.argument) {
4,290✔
3903
                        i.argument = path_join("/usr/share/factory", i.path);
3,789✔
3904
                        if (!i.argument)
3,789✔
3905
                                return log_oom();
×
3906
                } else if (!path_is_absolute(i.argument)) {
501✔
3907
                        *invalid_config = true;
×
3908
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
×
3909
                                          "Source path '%s' is not absolute.", i.argument);
3910

3911
                }
3912

3913
                if (!empty_or_root(arg_root)) {
4,290✔
3914
                        char *p;
1✔
3915

3916
                        p = path_join(arg_root, i.argument);
1✔
3917
                        if (!p)
1✔
3918
                                return log_oom();
×
3919
                        free_and_replace(i.argument, p);
1✔
3920
                }
3921

3922
                path_simplify(i.argument);
4,290✔
3923

3924
                if (access_nofollow(i.argument, F_OK) == -ENOENT) {
4,290✔
3925
                        /* Silently skip over lines where the source file is missing. */
3926
                        log_syntax(NULL, LOG_DEBUG, fname, line, 0,
493✔
3927
                                   "Copy source path '%s' does not exist, skipping line.", i.argument);
3928
                        return 0;
493✔
3929
                }
3930

3931
                break;
3932

3933
        default:
23,797✔
3934
                ;
23,797✔
3935
        }
3936

3937
        if (from_cred) {
23,797✔
3938
                if (!i.argument)
492✔
3939
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
3940
                                          "Reading from credential requested, but no credential name specified.");
3941
                if (!credential_name_valid(i.argument))
492✔
3942
                        return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
×
3943
                                          "Credential name not valid: %s", i.argument);
3944

3945
                r = read_credential(i.argument, &i.binary_argument, &i.binary_argument_size);
492✔
3946
                if (IN_SET(r, -ENXIO, -ENOENT)) {
492✔
3947
                        /* Silently skip over lines that have no credentials passed */
3948
                        log_syntax(NULL, LOG_DEBUG, fname, line, 0,
374✔
3949
                                   "Credential '%s' not specified, skipping line.", i.argument);
3950
                        return 0;
374✔
3951
                }
3952
                if (r < 0)
118✔
3953
                        return log_error_errno(r, "Failed to read credential '%s': %m", i.argument);
×
3954
        }
3955

3956
        /* If base64 decoding is requested, do so now */
3957
        if (unbase64 && item_binary_argument(&i)) {
23,423✔
3958
                _cleanup_free_ void *data = NULL;
×
3959
                size_t data_size = 0;
×
3960

3961
                r = unbase64mem_full(item_binary_argument(&i), item_binary_argument_size(&i), /* secure= */ false,
×
3962
                                     &data, &data_size);
3963
                if (r < 0)
×
3964
                        return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to base64 decode specified argument '%s': %m", i.argument);
×
3965

3966
                free_and_replace(i.binary_argument, data);
×
3967
                i.binary_argument_size = data_size;
×
3968
        }
3969

3970
        if (!empty_or_root(arg_root)) {
23,423✔
3971
                char *p;
14✔
3972

3973
                p = path_join(arg_root, i.path);
14✔
3974
                if (!p)
14✔
3975
                        return log_oom();
63,993✔
3976
                free_and_replace(i.path, p);
14✔
3977
        }
3978

3979
        if (!empty_or_dash(user)) {
23,423✔
3980
                const char *u;
8,752✔
3981

3982
                u = startswith(user, ":");
8,752✔
3983
                if (u)
8,752✔
3984
                        i.uid_only_create = true;
4✔
3985
                else
3986
                        u = user;
3987

3988
                r = find_uid(u, &i.uid, &c->uid_cache);
8,752✔
3989
                if (r == -ESRCH && arg_graceful) {
8,752✔
3990
                        log_syntax(NULL, LOG_DEBUG, fname, line, r,
×
3991
                                   "%s: user '%s' not found, not adjusting ownership.", i.path, u);
3992
                        missing_user_or_group = true;
3993
                } else if (r < 0) {
8,752✔
3994
                        *invalid_config = true;
×
3995
                        return log_syntax(NULL, LOG_ERR, fname, line, r,
×
3996
                                          "Failed to resolve user '%s': %s", u, STRERROR_USER(r));
3997
                } else
3998
                        i.uid_set = true;
8,752✔
3999
        }
4000

4001
        if (!empty_or_dash(group)) {
23,423✔
4002
                const char *g;
10,198✔
4003

4004
                g = startswith(group, ":");
10,198✔
4005
                if (g)
10,198✔
4006
                        i.gid_only_create = true;
368✔
4007
                else
4008
                        g = group;
4009

4010
                r = find_gid(g, &i.gid, &c->gid_cache);
10,198✔
4011
                if (r == -ESRCH && arg_graceful) {
10,198✔
4012
                        log_syntax(NULL, LOG_DEBUG, fname, line, r,
×
4013
                                   "%s: group '%s' not found, not adjusting ownership.", i.path, g);
4014
                        missing_user_or_group = true;
4015
                } else if (r < 0) {
10,198✔
4016
                        *invalid_config = true;
×
4017
                        return log_syntax(NULL, LOG_ERR, fname, line, r,
×
4018
                                          "Failed to resolve group '%s': %s", g, STRERROR_GROUP(r));
4019
                } else
4020
                        i.gid_set = true;
10,198✔
4021
        }
4022

4023
        if (!empty_or_dash(mode)) {
23,423✔
4024
                const char *mm;
4025
                unsigned m;
4026

4027
                for (mm = mode;; mm++)
491✔
4028
                        if (*mm == '~')
15,439✔
4029
                                i.mask_perms = true;
123✔
4030
                        else if (*mm == ':')
15,316✔
4031
                                i.mode_only_create = true;
368✔
4032
                        else
4033
                                break;
4034

4035
                r = parse_mode(mm, &m);
14,948✔
4036
                if (r < 0) {
14,948✔
4037
                        *invalid_config = true;
×
4038
                        return log_syntax(NULL, LOG_ERR, fname, line, r, "Invalid mode '%s'.", mode);
×
4039
                }
4040

4041
                i.mode = m;
14,948✔
4042
                i.mode_set = true;
14,948✔
4043
        } else
4044
                i.mode = IN_SET(i.type,
8,475✔
4045
                                CREATE_DIRECTORY,
4046
                                TRUNCATE_DIRECTORY,
4047
                                CREATE_SUBVOLUME,
4048
                                CREATE_SUBVOLUME_INHERIT_QUOTA,
4049
                                CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644;
4050

4051
        if (missing_user_or_group && (i.mode & ~0777) != 0) {
23,423✔
4052
                /* Refuse any special bits for nodes where we couldn't resolve the ownership properly. */
4053
                mode_t adjusted = i.mode & 0777;
×
4054
                log_syntax(NULL, LOG_INFO, fname, line, 0,
×
4055
                           "Changing mode 0%o to 0%o because of changed ownership.", i.mode, adjusted);
4056
                i.mode = adjusted;
×
4057
        }
4058

4059
        if (!empty_or_dash(age)) {
23,423✔
4060
                const char *a = age;
1,250✔
4061
                _cleanup_free_ char *seconds = NULL, *age_by = NULL;
1,250✔
4062

4063
                if (*a == '~') {
1,250✔
4064
                        i.keep_first_level = true;
×
4065
                        a++;
×
4066
                }
4067

4068
                /* Format: "age-by:age"; where age-by is "[abcmABCM]+". */
4069
                r = split_pair(a, ":", &age_by, &seconds);
1,250✔
4070
                if (r == -ENOMEM)
1,250✔
4071
                        return log_oom();
×
4072
                if (r < 0 && r != -EINVAL)
1,250✔
4073
                        return log_error_errno(r, "Failed to parse age-by for '%s': %m", age);
×
4074
                if (r >= 0) {
1,250✔
4075
                        /* We found a ":", parse the "age-by" part. */
4076
                        r = parse_age_by_from_arg(age_by, &i);
151✔
4077
                        if (r == -ENOMEM)
151✔
4078
                                return log_oom();
×
4079
                        if (r < 0) {
151✔
4080
                                *invalid_config = true;
7✔
4081
                                return log_syntax(NULL, LOG_ERR, fname, line, r, "Invalid age-by '%s'.", age_by);
7✔
4082
                        }
4083

4084
                        /* For parsing the "age" part, after the ":". */
4085
                        a = seconds;
144✔
4086
                }
4087

4088
                r = parse_sec(a, &i.age);
1,243✔
4089
                if (r < 0) {
1,243✔
4090
                        *invalid_config = true;
2✔
4091
                        return log_syntax(NULL, LOG_ERR, fname, line, r, "Invalid age '%s'.", a);
2✔
4092
                }
4093

4094
                i.age_set = true;
1,241✔
4095
        }
4096

4097
        h = needs_glob(i.type) ? c->globs : c->items;
23,414✔
4098

4099
        existing = ordered_hashmap_get(h, i.path);
23,414✔
4100
        if (existing) {
23,414✔
4101
                if (is_duplicated_item(existing, &i)) {
1,357✔
4102
                        log_syntax(NULL, LOG_NOTICE, fname, line, 0,
3✔
4103
                                   "Duplicate line for path \"%s\", ignoring.", i.path);
4104
                        return 0;
3✔
4105
                }
4106
        } else {
4107
                existing = new0(ItemArray, 1);
22,057✔
4108
                if (!existing)
22,057✔
4109
                        return log_oom();
×
4110

4111
                r = ordered_hashmap_put(h, i.path, existing);
22,057✔
4112
                if (r < 0) {
22,057✔
4113
                        free(existing);
×
4114
                        return log_oom();
×
4115
                }
4116
        }
4117

4118
        if (!GREEDY_REALLOC(existing->items, existing->n_items + 1))
23,411✔
4119
                return log_oom();
×
4120

4121
        existing->items[existing->n_items++] = TAKE_STRUCT(i);
23,411✔
4122

4123
        /* Sort item array, to enforce stable ordering of application */
4124
        typesafe_qsort(existing->items, existing->n_items, item_compare);
23,411✔
4125

4126
        return 0;
4127
}
4128

4129
static int cat_config(char **config_dirs, char **args) {
×
4130
        _cleanup_strv_free_ char **files = NULL;
×
4131
        int r;
×
4132

4133
        r = conf_files_list_with_replacement(arg_root, config_dirs, arg_replace, &files, NULL);
×
4134
        if (r < 0)
×
4135
                return r;
4136

4137
        pager_open(arg_pager_flags);
×
4138

4139
        return cat_files(NULL, files, arg_cat_flags);
×
4140
}
4141

4142
static int exclude_default_prefixes(void) {
×
4143
        int r;
×
4144

4145
        /* Provide an easy way to exclude virtual/memory file systems from what we do here. Useful in
4146
         * combination with --root= where we probably don't want to apply stuff to these dirs as they are
4147
         * likely over-mounted if the root directory is actually used, and it wouldbe less than ideal to have
4148
         * all kinds of files created/adjusted underneath these mount points. */
4149

4150
        r = strv_extend_many(
×
4151
                        &arg_exclude_prefixes,
4152
                        "/dev",
4153
                        "/proc",
4154
                        "/run",
4155
                        "/sys");
4156
        if (r < 0)
×
4157
                return log_oom();
×
4158

4159
        strv_uniq(arg_exclude_prefixes);
×
4160
        return 0;
×
4161
}
4162

4163
static int help(void) {
×
4164
        _cleanup_free_ char *link = NULL;
×
4165
        int r;
×
4166

4167
        r = terminal_urlify_man("systemd-tmpfiles", "8", &link);
×
4168
        if (r < 0)
×
4169
                return log_oom();
×
4170

4171
        printf("%1$s COMMAND [OPTIONS...] [CONFIGURATION FILE...]\n"
×
4172
               "\n%2$sCreate, delete, and clean up files and directories.%4$s\n"
4173
               "\n%3$sCommands:%4$s\n"
4174
               "     --create               Create and adjust files and directories\n"
4175
               "     --clean                Clean up files and directories\n"
4176
               "     --remove               Remove files and directories marked for removal\n"
4177
               "     --purge                Delete files and directories marked for creation in\n"
4178
               "                            specified configuration files (careful!)\n"
4179
               "     --cat-config           Show configuration files\n"
4180
               "     --tldr                 Show non-comment parts of configuration files\n"
4181
               "  -h --help                 Show this help\n"
4182
               "     --version              Show package version\n"
4183
               "\n%3$sOptions:%4$s\n"
4184
               "     --user                 Execute user configuration\n"
4185
               "     --boot                 Execute actions only safe at boot\n"
4186
               "     --graceful             Quietly ignore unknown users or groups\n"
4187
               "     --prefix=PATH          Only apply rules with the specified prefix\n"
4188
               "     --exclude-prefix=PATH  Ignore rules with the specified prefix\n"
4189
               "  -E                        Ignore rules prefixed with /dev, /proc, /run, /sys\n"
4190
               "     --root=PATH            Operate on an alternate filesystem root\n"
4191
               "     --image=PATH           Operate on disk image as filesystem root\n"
4192
               "     --image-policy=POLICY  Specify disk image dissection policy\n"
4193
               "     --replace=PATH         Treat arguments as replacement for PATH\n"
4194
               "     --dry-run              Just print what would be done\n"
4195
               "     --no-pager             Do not pipe output into a pager\n"
4196
               "\nSee the %5$s for details.\n",
4197
               program_invocation_short_name,
4198
               ansi_highlight(),
4199
               ansi_underline(),
4200
               ansi_normal(),
4201
               link);
4202

4203
        return 0;
4204
}
4205

4206
static int parse_argv(int argc, char *argv[]) {
689✔
4207
        enum {
689✔
4208
                ARG_VERSION = 0x100,
4209
                ARG_CAT_CONFIG,
4210
                ARG_TLDR,
4211
                ARG_USER,
4212
                ARG_CREATE,
4213
                ARG_CLEAN,
4214
                ARG_REMOVE,
4215
                ARG_PURGE,
4216
                ARG_BOOT,
4217
                ARG_GRACEFUL,
4218
                ARG_PREFIX,
4219
                ARG_EXCLUDE_PREFIX,
4220
                ARG_ROOT,
4221
                ARG_IMAGE,
4222
                ARG_IMAGE_POLICY,
4223
                ARG_REPLACE,
4224
                ARG_DRY_RUN,
4225
                ARG_NO_PAGER,
4226
        };
4227

4228
        static const struct option options[] = {
689✔
4229
                { "help",           no_argument,         NULL, 'h'                },
4230
                { "user",           no_argument,         NULL, ARG_USER           },
4231
                { "version",        no_argument,         NULL, ARG_VERSION        },
4232
                { "cat-config",     no_argument,         NULL, ARG_CAT_CONFIG     },
4233
                { "tldr",           no_argument,         NULL, ARG_TLDR           },
4234
                { "create",         no_argument,         NULL, ARG_CREATE         },
4235
                { "clean",          no_argument,         NULL, ARG_CLEAN          },
4236
                { "remove",         no_argument,         NULL, ARG_REMOVE         },
4237
                { "purge",          no_argument,         NULL, ARG_PURGE          },
4238
                { "boot",           no_argument,         NULL, ARG_BOOT           },
4239
                { "graceful",       no_argument,         NULL, ARG_GRACEFUL       },
4240
                { "prefix",         required_argument,   NULL, ARG_PREFIX         },
4241
                { "exclude-prefix", required_argument,   NULL, ARG_EXCLUDE_PREFIX },
4242
                { "root",           required_argument,   NULL, ARG_ROOT           },
4243
                { "image",          required_argument,   NULL, ARG_IMAGE          },
4244
                { "image-policy",   required_argument,   NULL, ARG_IMAGE_POLICY   },
4245
                { "replace",        required_argument,   NULL, ARG_REPLACE        },
4246
                { "dry-run",        no_argument,         NULL, ARG_DRY_RUN        },
4247
                { "no-pager",       no_argument,         NULL, ARG_NO_PAGER       },
4248
                {}
4249
        };
4250

4251
        int c, r;
689✔
4252

4253
        assert(argc >= 0);
689✔
4254
        assert(argv);
689✔
4255

4256
        while ((c = getopt_long(argc, argv, "hE", options, NULL)) >= 0)
2,933✔
4257

4258
                switch (c) {
2,244✔
4259

4260
                case 'h':
×
4261
                        return help();
×
4262

4263
                case ARG_VERSION:
×
4264
                        return version();
×
4265

4266
                case ARG_CAT_CONFIG:
×
4267
                        arg_cat_flags = CAT_CONFIG_ON;
×
4268
                        break;
×
4269

4270
                case ARG_TLDR:
×
4271
                        arg_cat_flags = CAT_TLDR;
×
4272
                        break;
×
4273

4274
                case ARG_USER:
192✔
4275
                        arg_runtime_scope = RUNTIME_SCOPE_USER;
192✔
4276
                        break;
192✔
4277

4278
                case ARG_CREATE:
638✔
4279
                        arg_operation |= OPERATION_CREATE;
638✔
4280
                        break;
638✔
4281

4282
                case ARG_CLEAN:
38✔
4283
                        arg_operation |= OPERATION_CLEAN;
38✔
4284
                        break;
38✔
4285

4286
                case ARG_REMOVE:
320✔
4287
                        arg_operation |= OPERATION_REMOVE;
320✔
4288
                        break;
320✔
4289

4290
                case ARG_BOOT:
542✔
4291
                        arg_boot = true;
542✔
4292
                        break;
542✔
4293

4294
                case ARG_PURGE:
5✔
4295
                        arg_operation |= OPERATION_PURGE;
5✔
4296
                        break;
5✔
4297

4298
                case ARG_GRACEFUL:
118✔
4299
                        arg_graceful = true;
118✔
4300
                        break;
118✔
4301

4302
                case ARG_PREFIX:
236✔
4303
                        if (strv_extend(&arg_include_prefixes, optarg) < 0)
236✔
4304
                                return log_oom();
×
4305
                        break;
4306

4307
                case ARG_EXCLUDE_PREFIX:
118✔
4308
                        if (strv_extend(&arg_exclude_prefixes, optarg) < 0)
118✔
4309
                                return log_oom();
×
4310
                        break;
4311

4312
                case ARG_ROOT:
8✔
4313
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
8✔
4314
                        if (r < 0)
8✔
4315
                                return r;
4316
                        break;
4317

4318
                case ARG_IMAGE:
×
4319
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
×
4320
                        if (r < 0)
×
4321
                                return r;
4322

4323
                        /* Imply -E here since it makes little sense to create files persistently in the /run mountpoint of a disk image */
4324
                        _fallthrough_;
×
4325

4326
                case 'E':
4327
                        r = exclude_default_prefixes();
×
4328
                        if (r < 0)
×
4329
                                return r;
4330

4331
                        break;
4332

4333
                case ARG_IMAGE_POLICY:
×
4334
                        r = parse_image_policy_argument(optarg, &arg_image_policy);
×
4335
                        if (r < 0)
×
4336
                                return r;
4337
                        break;
4338

4339
                case ARG_REPLACE:
×
4340
                        if (!path_is_absolute(optarg))
×
4341
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
4342
                                                       "The argument to --replace= must be an absolute path.");
4343
                        if (!endswith(optarg, ".conf"))
×
4344
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
4345
                                                       "The argument to --replace= must have the extension '.conf'.");
4346

4347
                        arg_replace = optarg;
×
4348
                        break;
×
4349

4350
                case ARG_DRY_RUN:
29✔
4351
                        arg_dry_run = true;
29✔
4352
                        break;
29✔
4353

4354
                case ARG_NO_PAGER:
×
4355
                        arg_pager_flags |= PAGER_DISABLE;
×
4356
                        break;
×
4357

4358
                case '?':
4359
                        return -EINVAL;
4360

4361
                default:
×
4362
                        assert_not_reached();
×
4363
                }
4364

4365
        if (arg_operation == 0 && arg_cat_flags == CAT_CONFIG_OFF)
689✔
4366
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
4367
                                       "You need to specify at least one of --clean, --create, --remove, or --purge.");
4368

4369
        if (FLAGS_SET(arg_operation, OPERATION_PURGE) && optind >= argc)
689✔
4370
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
4371
                                       "Refusing --purge without specification of a configuration file.");
4372

4373
        if (arg_replace && arg_cat_flags != CAT_CONFIG_OFF)
689✔
4374
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
4375
                                       "Option --replace= is not supported with --cat-config/--tldr.");
4376

4377
        if (arg_replace && optind >= argc)
689✔
4378
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
4379
                                       "When --replace= is given, some configuration items must be specified.");
4380

4381
        if (arg_root && arg_runtime_scope == RUNTIME_SCOPE_USER)
689✔
4382
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
4383
                                       "Combination of --user and --root= is not supported.");
4384

4385
        if (arg_image && arg_root)
689✔
4386
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
4387
                                       "Please specify either --root= or --image=, the combination of both is not supported.");
4388

4389
        return 1;
4390
}
4391

4392
static int read_config_file(
15,411✔
4393
                Context *c,
4394
                char **config_dirs,
4395
                const char *fn,
4396
                bool ignore_enoent,
4397
                bool *invalid_config) {
4398

4399
        ItemArray *ia;
15,411✔
4400
        int r = 0;
15,411✔
4401

4402
        assert(c);
15,411✔
4403
        assert(fn);
15,411✔
4404

4405
        r = conf_file_read(arg_root, (const char**) config_dirs, fn,
15,411✔
4406
                           parse_line, c, ignore_enoent, invalid_config);
4407
        if (r <= 0)
15,411✔
4408
                return r;
15,411✔
4409

4410
        /* we have to determine age parameter for each entry of type X */
4411
        ORDERED_HASHMAP_FOREACH(ia, c->globs)
108,645✔
4412
                FOREACH_ARRAY(i, ia->items, ia->n_items) {
192,375✔
4413
                        ItemArray *ja;
98,790✔
4414
                        Item *candidate_item = NULL;
98,790✔
4415

4416
                        if (i->type != IGNORE_DIRECTORY_PATH)
98,790✔
4417
                                continue;
97,048✔
4418

4419
                        ORDERED_HASHMAP_FOREACH(ja, c->items)
164,718✔
4420
                                FOREACH_ARRAY(j, ja->items, ja->n_items) {
330,990✔
4421
                                        if (!IN_SET(j->type, CREATE_DIRECTORY,
168,014✔
4422
                                                             TRUNCATE_DIRECTORY,
4423
                                                             CREATE_SUBVOLUME,
4424
                                                             CREATE_SUBVOLUME_INHERIT_QUOTA,
4425
                                                             CREATE_SUBVOLUME_NEW_QUOTA))
4426
                                                continue;
82,468✔
4427

4428
                                        if (path_equal(j->path, i->path)) {
85,546✔
4429
                                                candidate_item = j;
4430
                                                break;
4431
                                        }
4432

4433
                                        if (candidate_item
85,546✔
4434
                                            ? (path_startswith(j->path, candidate_item->path) && fnmatch(i->path, j->path, FNM_PATHNAME | FNM_PERIOD) == 0)
7,547✔
4435
                                            : path_startswith(i->path, j->path) != NULL)
77,999✔
4436
                                                candidate_item = j;
4437
                                }
4438

4439
                        if (candidate_item && candidate_item->age_set) {
1,742✔
4440
                                i->age = candidate_item->age;
1,244✔
4441
                                i->age_set = true;
1,244✔
4442
                        }
4443
                }
4444

4445
        return r;
15,060✔
4446
}
4447

4448
static int parse_arguments(
138✔
4449
                Context *c,
4450
                char **config_dirs,
4451
                char **args,
4452
                bool *invalid_config) {
4453
        int r;
138✔
4454

4455
        assert(c);
138✔
4456

4457
        STRV_FOREACH(arg, args) {
276✔
4458
                r = read_config_file(c, config_dirs, *arg, false, invalid_config);
138✔
4459
                if (r < 0)
138✔
4460
                        return r;
4461
        }
4462

4463
        return 0;
4464
}
4465

4466
static int read_config_files(
551✔
4467
                Context *c,
4468
                char **config_dirs,
4469
                char **args,
4470
                bool *invalid_config) {
4471

4472
        _cleanup_strv_free_ char **files = NULL;
×
4473
        _cleanup_free_ char *p = NULL;
551✔
4474
        int r;
551✔
4475

4476
        assert(c);
551✔
4477

4478
        r = conf_files_list_with_replacement(arg_root, config_dirs, arg_replace, &files, &p);
551✔
4479
        if (r < 0)
551✔
4480
                return r;
4481

4482
        STRV_FOREACH(f, files)
15,470✔
4483
                if (p && path_equal(*f, p)) {
14,919✔
4484
                        log_debug("Parsing arguments at position \"%s\"%s", *f, glyph(GLYPH_ELLIPSIS));
×
4485

4486
                        r = parse_arguments(c, config_dirs, args, invalid_config);
×
4487
                        if (r < 0)
×
4488
                                return r;
4489
                } else
4490
                        /* Just warn, ignore result otherwise.
4491
                         * read_config_file() has some debug output, so no need to print anything. */
4492
                        (void) read_config_file(c, config_dirs, *f, true, invalid_config);
14,919✔
4493

4494
        return 0;
4495
}
4496

4497
static int read_credential_lines(Context *c, bool *invalid_config) {
689✔
4498
        _cleanup_free_ char *j = NULL;
689✔
4499
        const char *d;
689✔
4500
        int r;
689✔
4501

4502
        assert(c);
689✔
4503

4504
        r = get_credentials_dir(&d);
689✔
4505
        if (r == -ENXIO)
689✔
4506
                return 0;
4507
        if (r < 0)
354✔
4508
                return log_error_errno(r, "Failed to get credentials directory: %m");
×
4509

4510
        j = path_join(d, "tmpfiles.extra");
354✔
4511
        if (!j)
354✔
4512
                return log_oom();
×
4513

4514
        (void) read_config_file(c, /* config_dirs= */ NULL, j, /* ignore_enoent= */ true, invalid_config);
354✔
4515
        return 0;
4516
}
4517

4518
static int link_parent(Context *c, ItemArray *a) {
22,057✔
4519
        const char *path;
22,057✔
4520
        char *prefix;
22,057✔
4521
        int r;
22,057✔
4522

4523
        assert(c);
22,057✔
4524
        assert(a);
22,057✔
4525

4526
        /* Finds the closest "parent" item array for the specified item array. Then registers the specified
4527
         * item array as child of it, and fills the parent in, linking them both ways. This allows us to
4528
         * later create parents before their children, and clean up/remove children before their parents.
4529
         */
4530

4531
        if (a->n_items <= 0)
22,057✔
4532
                return 0;
4533

4534
        path = a->items[0].path;
22,057✔
4535
        prefix = newa(char, strlen(path) + 1);
22,057✔
4536
        PATH_FOREACH_PREFIX(prefix, path) {
74,809✔
4537
                ItemArray *j;
39,689✔
4538

4539
                j = ordered_hashmap_get(c->items, prefix);
39,689✔
4540
                if (!j)
39,689✔
4541
                        j = ordered_hashmap_get(c->globs, prefix);
31,441✔
4542
                if (j) {
31,441✔
4543
                        r = set_ensure_put(&j->children, NULL, a);
8,994✔
4544
                        if (r < 0)
8,994✔
4545
                                return log_oom();
×
4546

4547
                        a->parent = j;
8,994✔
4548
                        return 1;
8,994✔
4549
                }
4550
        }
4551

4552
        return 0;
4553
}
4554

4555
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(item_array_hash_ops, char, string_hash_func, string_compare_func,
22,057✔
4556
                                              ItemArray, item_array_free);
4557

4558
static int run(int argc, char *argv[]) {
689✔
4559
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
689✔
4560
        _cleanup_(umount_and_freep) char *mounted_dir = NULL;
689✔
4561
        _cleanup_strv_free_ char **config_dirs = NULL;
689✔
4562
        _cleanup_(context_done) Context c = {};
×
4563
        bool invalid_config = false;
689✔
4564
        ItemArray *a;
689✔
4565
        enum {
689✔
4566
                PHASE_PURGE,
4567
                PHASE_REMOVE_AND_CLEAN,
4568
                PHASE_CREATE,
4569
                _PHASE_MAX
4570
        } phase;
4571
        int r;
689✔
4572

4573
        r = parse_argv(argc, argv);
689✔
4574
        if (r <= 0)
689✔
4575
                return r;
4576

4577
        log_setup();
689✔
4578

4579
        /* We require /proc/ for a lot of our operations, i.e. for adjusting access modes, for anything
4580
         * SELinux related, for recursive operation, for xattr, acl and chattr handling, for btrfs stuff and
4581
         * a lot more. It's probably the majority of invocations where /proc/ is required. Since people
4582
         * apparently invoke it without anyway and are surprised about the failures, let's catch this early
4583
         * and output a nice and friendly warning. */
4584
        if (proc_mounted() == 0)
689✔
4585
                return log_error_errno(SYNTHETIC_ERRNO(ENOSYS),
×
4586
                                       "/proc/ is not mounted, but required for successful operation of systemd-tmpfiles. "
4587
                                       "Please mount /proc/. Alternatively, consider using the --root= or --image= switches.");
4588

4589
        /* Descending down file system trees might take a lot of fds */
4590
        (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE);
689✔
4591

4592
        switch (arg_runtime_scope) {
689✔
4593

4594
        case RUNTIME_SCOPE_USER:
192✔
4595
                r = user_config_paths(&config_dirs);
192✔
4596
                if (r < 0)
192✔
4597
                        return log_error_errno(r, "Failed to initialize configuration directory list: %m");
×
4598
                break;
4599

4600
        case RUNTIME_SCOPE_SYSTEM:
497✔
4601
                config_dirs = strv_new(CONF_PATHS("tmpfiles.d"));
497✔
4602
                if (!config_dirs)
497✔
4603
                        return log_oom();
×
4604
                break;
4605

4606
        default:
×
4607
                assert_not_reached();
×
4608
        }
4609

4610
        if (DEBUG_LOGGING) {
689✔
4611
                _cleanup_free_ char *t = NULL;
577✔
4612

4613
                STRV_FOREACH(i, config_dirs) {
3,269✔
4614
                        _cleanup_free_ char *j = NULL;
2,692✔
4615

4616
                        j = path_join(arg_root, *i);
2,692✔
4617
                        if (!j)
2,692✔
4618
                                return log_oom();
×
4619

4620
                        if (!strextend(&t, "\n\t", j))
2,692✔
4621
                                return log_oom();
×
4622
                }
4623

4624
                log_debug("Looking for configuration files in (higher priority first):%s", t);
577✔
4625
        }
4626

4627
        if (arg_cat_flags != CAT_CONFIG_OFF)
689✔
4628
                return cat_config(config_dirs, argv + optind);
×
4629

4630
        if (should_bypass("SYSTEMD_TMPFILES"))
689✔
4631
                return 0;
4632

4633
        umask(0022);
689✔
4634

4635
        r = mac_init();
689✔
4636
        if (r < 0)
689✔
4637
                return r;
4638

4639
        if (arg_image) {
689✔
4640
                assert(!arg_root);
×
4641

4642
                r = mount_image_privately_interactively(
×
4643
                                arg_image,
4644
                                arg_image_policy,
4645
                                DISSECT_IMAGE_GENERIC_ROOT |
4646
                                DISSECT_IMAGE_REQUIRE_ROOT |
4647
                                DISSECT_IMAGE_VALIDATE_OS |
4648
                                DISSECT_IMAGE_RELAX_VAR_CHECK |
4649
                                DISSECT_IMAGE_FSCK |
4650
                                DISSECT_IMAGE_GROWFS |
4651
                                DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
4652
                                &mounted_dir,
4653
                                /* ret_dir_fd= */ NULL,
4654
                                &loop_device);
4655
                if (r < 0)
×
4656
                        return r;
4657

4658
                arg_root = strdup(mounted_dir);
×
4659
                if (!arg_root)
×
4660
                        return log_oom();
×
4661
        }
4662

4663
        c.items = ordered_hashmap_new(&item_array_hash_ops);
689✔
4664
        c.globs = ordered_hashmap_new(&item_array_hash_ops);
689✔
4665
        if (!c.items || !c.globs)
689✔
4666
                return log_oom();
×
4667

4668
        /* If command line arguments are specified along with --replace=, read all configuration files and
4669
         * insert the positional arguments at the specified place. Otherwise, if command line arguments are
4670
         * specified, execute just them, and finally, without --replace= or any positional arguments, just
4671
         * read configuration and execute it. */
4672
        if (arg_replace || optind >= argc)
689✔
4673
                r = read_config_files(&c, config_dirs, argv + optind, &invalid_config);
551✔
4674
        else
4675
                r = parse_arguments(&c, config_dirs, argv + optind, &invalid_config);
138✔
4676
        if (r < 0)
689✔
4677
                return r;
4678

4679
        r = read_credential_lines(&c, &invalid_config);
689✔
4680
        if (r < 0)
689✔
4681
                return r;
4682

4683
        /* Let's now link up all child/parent relationships */
4684
        ORDERED_HASHMAP_FOREACH(a, c.items) {
16,592✔
4685
                r = link_parent(&c, a);
15,903✔
4686
                if (r < 0)
15,903✔
4687
                        return r;
×
4688
        }
4689
        ORDERED_HASHMAP_FOREACH(a, c.globs) {
6,843✔
4690
                r = link_parent(&c, a);
6,154✔
4691
                if (r < 0)
6,154✔
4692
                        return r;
×
4693
        }
4694

4695
        /* If multiple operations are requested, let's first run the remove/clean operations, and only then
4696
         * the create operations. i.e. that we first clean out the platform we then build on. */
4697
        for (phase = 0; phase < _PHASE_MAX; phase++) {
2,756✔
4698
                OperationMask op;
2,067✔
4699

4700
                if (phase == PHASE_PURGE)
2,067✔
4701
                        op = arg_operation & OPERATION_PURGE;
689✔
4702
                else if (phase == PHASE_REMOVE_AND_CLEAN)
1,378✔
4703
                        op = arg_operation & (OPERATION_REMOVE|OPERATION_CLEAN);
689✔
4704
                else if (phase == PHASE_CREATE)
689✔
4705
                        op = arg_operation & OPERATION_CREATE;
689✔
4706
                else
4707
                        assert_not_reached();
×
4708

4709
                if (op == 0) /* Nothing requested in this phase */
2,067✔
4710
                        continue;
1,066✔
4711

4712
                /* The non-globbing ones usually create things, hence we apply them first */
4713
                ORDERED_HASHMAP_FOREACH(a, c.items)
29,781✔
4714
                        RET_GATHER(r, process_item_array(&c, a, op));
28,780✔
4715

4716
                /* The globbing ones usually alter things, hence we apply them second. */
4717
                ORDERED_HASHMAP_FOREACH(a, c.globs)
10,935✔
4718
                        RET_GATHER(r, process_item_array(&c, a, op));
9,934✔
4719
        }
4720

4721
        if (ERRNO_IS_NEG_RESOURCE(r))
1,378✔
4722
                return r;
4723
        if (invalid_config)
689✔
4724
                return EX_DATAERR;
4725
        if (r < 0)
679✔
4726
                return EX_CANTCREAT;
15✔
4727
        return 0;
4728
}
4729

4730
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
689✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc