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

systemd / systemd / 14630481637

23 Apr 2025 07:04PM UTC coverage: 72.178% (-0.002%) from 72.18%
14630481637

push

github

DaanDeMeyer
mkosi: Run clangd within the tools tree instead of the build container

Running within the build sandbox has a number of disadvantages:
- We have a separate clangd cache for each distribution/release combo
- It requires to build the full image before clangd can be used
- It breaks every time the image becomes out of date and requires a
  rebuild
- We can't look at system headers as we don't have the knowledge to map
  them from inside the build sandbox to the corresponding path on the host

Instead, let's have mkosi.clangd run clangd within the tools tree. We
already require building systemd for both the host and the target anyway,
and all the dependencies to build systemd are installed in the tools tree
already for that, as well as clangd since it's installed together with the
other clang tooling we install in the tools tree. Unlike the previous approach,
this approach only requires the mkosi tools tree to be built upfront, which has
a much higher chance of not invalidating its cache. We can also trivially map
system header lookups from within the sandbox to the path within mkosi.tools
on the host so that starts working as well.

297054 of 411557 relevant lines covered (72.18%)

686269.58 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 <errno.h>
4
#include <fcntl.h>
5
#include <fnmatch.h>
6
#include <getopt.h>
7
#include <limits.h>
8
#include <stdbool.h>
9
#include <stddef.h>
10
#include <stdlib.h>
11
#include <sys/file.h>
12
#include <sysexits.h>
13
#include <time.h>
14
#include <unistd.h>
15

16
#include "sd-path.h"
17

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

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

84
typedef enum OperationMask {
85
        OPERATION_CREATE = 1 << 0,
86
        OPERATION_REMOVE = 1 << 1,
87
        OPERATION_CLEAN  = 1 << 2,
88
        OPERATION_PURGE  = 1 << 3,
89
} OperationMask;
90

91
typedef enum ItemType {
92
        /* These ones take file names */
93
        CREATE_FILE                    = 'f',
94
        TRUNCATE_FILE                  = 'F', /* deprecated: use f+ */
95
        CREATE_DIRECTORY               = 'd',
96
        TRUNCATE_DIRECTORY             = 'D',
97
        CREATE_SUBVOLUME               = 'v',
98
        CREATE_SUBVOLUME_INHERIT_QUOTA = 'q',
99
        CREATE_SUBVOLUME_NEW_QUOTA     = 'Q',
100
        CREATE_FIFO                    = 'p',
101
        CREATE_SYMLINK                 = 'L',
102
        CREATE_CHAR_DEVICE             = 'c',
103
        CREATE_BLOCK_DEVICE            = 'b',
104
        COPY_FILES                     = 'C',
105

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

124
typedef enum AgeBy {
125
        AGE_BY_ATIME = 1 << 0,
126
        AGE_BY_BTIME = 1 << 1,
127
        AGE_BY_CTIME = 1 << 2,
128
        AGE_BY_MTIME = 1 << 3,
129

130
        /* All file timestamp types are checked by default. */
131
        AGE_BY_DEFAULT_FILE = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_CTIME | AGE_BY_MTIME,
132
        AGE_BY_DEFAULT_DIR  = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_MTIME,
133
} AgeBy;
134

135
typedef struct Item {
136
        ItemType type;
137

138
        char *path;
139
        char *argument;
140
        void *binary_argument;        /* set if binary data, in which case it takes precedence over 'argument' */
141
        size_t binary_argument_size;
142
        char **xattrs;
143
#if HAVE_ACL
144
        acl_t acl_access;
145
        acl_t acl_access_exec;
146
        acl_t acl_default;
147
#endif
148
        uid_t uid;
149
        gid_t gid;
150
        mode_t mode;
151
        usec_t age;
152
        AgeBy age_by_file, age_by_dir;
153

154
        dev_t major_minor;
155
        unsigned attribute_value;
156
        unsigned attribute_mask;
157

158
        bool uid_set:1;
159
        bool gid_set:1;
160
        bool mode_set:1;
161
        bool uid_only_create:1;
162
        bool gid_only_create:1;
163
        bool mode_only_create:1;
164
        bool age_set:1;
165
        bool mask_perms:1;
166
        bool attribute_set:1;
167

168
        bool keep_first_level:1;
169

170
        bool append_or_force:1;
171

172
        bool allow_failure:1;
173

174
        bool try_replace:1;
175

176
        bool purge:1;
177

178
        bool ignore_if_target_missing:1;
179

180
        OperationMask done;
181
} Item;
182

183
typedef struct ItemArray {
184
        Item *items;
185
        size_t n_items;
186

187
        struct ItemArray *parent;
188
        Set *children;
189
} ItemArray;
190

191
typedef enum DirectoryType {
192
        DIRECTORY_RUNTIME,
193
        DIRECTORY_STATE,
194
        DIRECTORY_CACHE,
195
        DIRECTORY_LOGS,
196
        _DIRECTORY_TYPE_MAX,
197
} DirectoryType;
198

199
typedef enum {
200
        CREATION_NORMAL,
201
        CREATION_EXISTING,
202
        CREATION_FORCE,
203
        _CREATION_MODE_MAX,
204
        _CREATION_MODE_INVALID = -EINVAL,
205
} CreationMode;
206

207
static CatFlags arg_cat_flags = CAT_CONFIG_OFF;
208
static bool arg_dry_run = false;
209
static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
210
static OperationMask arg_operation = 0;
211
static bool arg_boot = false;
212
static bool arg_graceful = false;
213
static PagerFlags arg_pager_flags = 0;
214
static char **arg_include_prefixes = NULL;
215
static char **arg_exclude_prefixes = NULL;
216
static char *arg_root = NULL;
217
static char *arg_image = NULL;
218
static char *arg_replace = NULL;
219
static ImagePolicy *arg_image_policy = NULL;
220

221
#define MAX_DEPTH 256
222

223
typedef struct Context {
224
        OrderedHashmap *items;
225
        OrderedHashmap *globs;
226
        Set *unix_sockets;
227
        Hashmap *uid_cache;
228
        Hashmap *gid_cache;
229
} Context;
230

231
STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, strv_freep);
681✔
232
STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, strv_freep);
681✔
233
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
681✔
234
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
681✔
235
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
681✔
236

237
static const char *const creation_mode_verb_table[_CREATION_MODE_MAX] = {
238
        [CREATION_NORMAL]   = "Created",
239
        [CREATION_EXISTING] = "Found existing",
240
        [CREATION_FORCE]    = "Created replacement",
241
};
242

243
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode);
10,497✔
244

245
static void context_done(Context *c) {
681✔
246
        assert(c);
681✔
247

248
        ordered_hashmap_free(c->items);
681✔
249
        ordered_hashmap_free(c->globs);
681✔
250

251
        set_free(c->unix_sockets);
681✔
252

253
        hashmap_free(c->uid_cache);
681✔
254
        hashmap_free(c->gid_cache);
681✔
255
}
681✔
256

257
/* Different kinds of errors that mean that information is not available in the environment. */
258
static bool ERRNO_IS_NEG_NOINFO(intmax_t r) {
89,183✔
259
        return IN_SET(r,
89,183✔
260
                      -EUNATCH,    /* os-release or machine-id missing */
261
                      -ENOMEDIUM,  /* machine-id or another file empty */
262
                      -ENOPKG,     /* machine-id is uninitialized */
263
                      -ENXIO);     /* env var is unset */
264
}
265

266
static int specifier_directory(
×
267
                char specifier,
268
                const void *data,
269
                const char *root,
270
                const void *userdata,
271
                char **ret) {
272

273
        struct table_entry {
×
274
                uint64_t type;
275
                const char *suffix;
276
        };
277

278
        static const struct table_entry paths_system[] = {
×
279
                [DIRECTORY_RUNTIME] = { SD_PATH_SYSTEM_RUNTIME            },
280
                [DIRECTORY_STATE] =   { SD_PATH_SYSTEM_STATE_PRIVATE      },
281
                [DIRECTORY_CACHE] =   { SD_PATH_SYSTEM_STATE_CACHE        },
282
                [DIRECTORY_LOGS] =    { SD_PATH_SYSTEM_STATE_LOGS         },
283
        };
284

285
        static const struct table_entry paths_user[] = {
×
286
                [DIRECTORY_RUNTIME] = { SD_PATH_USER_RUNTIME              },
287
                [DIRECTORY_STATE] =   { SD_PATH_USER_STATE_PRIVATE        },
288
                [DIRECTORY_CACHE] =   { SD_PATH_USER_STATE_CACHE          },
289
                [DIRECTORY_LOGS] =    { SD_PATH_USER_STATE_PRIVATE, "log" },
290
        };
291

292
        const struct table_entry *paths;
×
293
        _cleanup_free_ char *p = NULL;
×
294
        unsigned i;
×
295
        int r;
×
296

297
        assert_cc(ELEMENTSOF(paths_system) == ELEMENTSOF(paths_user));
×
298
        paths = arg_runtime_scope == RUNTIME_SCOPE_USER ? paths_user : paths_system;
×
299

300
        i = PTR_TO_UINT(data);
×
301
        assert(i < ELEMENTSOF(paths_system));
×
302

303
        r = sd_path_lookup(paths[i].type, paths[i].suffix, &p);
×
304
        if (r < 0)
×
305
                return r;
306

307
        if (arg_root) {
×
308
                _cleanup_free_ char *j = NULL;
×
309

310
                j = path_join(arg_root, p);
×
311
                if (!j)
×
312
                        return -ENOMEM;
×
313

314
                *ret = TAKE_PTR(j);
×
315
        } else
316
                *ret = TAKE_PTR(p);
×
317

318
        return 0;
319
}
320

321
static int log_unresolvable_specifier(const char *filename, unsigned line) {
×
322
        static bool notified = false;
×
323

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

334
        int log_level = notified || arg_root || running_in_chroot() > 0 ?
×
335
                LOG_DEBUG : LOG_NOTICE;
×
336

337
        log_syntax(NULL,
×
338
                   log_level,
339
                   filename, line, 0,
340
                   "Failed to resolve specifier: %s, skipping.",
341
                   arg_runtime_scope == RUNTIME_SCOPE_USER ? "Required $XDG_... variable not defined" : "uninitialized /etc/ detected");
342

343
        if (!notified)
×
344
                log_full(log_level,
×
345
                         "All rules containing unresolvable specifiers will be skipped.");
346

347
        notified = true;
×
348
        return 0;
×
349
}
350

351
#define log_action(would, doing, fmt, ...)              \
352
        log_full(arg_dry_run ? LOG_INFO : LOG_DEBUG,    \
353
                 fmt,                                   \
354
                 arg_dry_run ? (would) : (doing),       \
355
                 __VA_ARGS__)
356

357
static int user_config_paths(char ***ret) {
177✔
358
        _cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL;
177✔
359
        _cleanup_free_ char *runtime_config = NULL;
177✔
360
        int r;
177✔
361

362
        assert(ret);
177✔
363

364
        /* Combined user-specific and global dirs */
365
        r = user_search_dirs("/user-tmpfiles.d", &config_dirs, &data_dirs);
177✔
366
        if (r < 0)
177✔
367
                return r;
368

369
        r = xdg_user_runtime_dir("/user-tmpfiles.d", &runtime_config);
177✔
370
        if (r < 0 && !ERRNO_IS_NEG_NOINFO(r))
177✔
371
                return r;
372

373
        r = strv_consume(&config_dirs, TAKE_PTR(runtime_config));
177✔
374
        if (r < 0)
177✔
375
                return r;
376

377
        r = strv_extend_strv_consume(&config_dirs, TAKE_PTR(data_dirs), /* filter_duplicates = */ true);
177✔
378
        if (r < 0)
177✔
379
                return r;
380

381
        r = path_strv_make_absolute_cwd(config_dirs);
177✔
382
        if (r < 0)
177✔
383
                return r;
384

385
        *ret = TAKE_PTR(config_dirs);
177✔
386
        return 0;
177✔
387
}
388

389
static bool needs_purge(ItemType t) {
4,419✔
390
        return IN_SET(t,
4,419✔
391
                      CREATE_FILE,
392
                      TRUNCATE_FILE,
393
                      CREATE_DIRECTORY,
394
                      TRUNCATE_DIRECTORY,
395
                      CREATE_SUBVOLUME,
396
                      CREATE_SUBVOLUME_INHERIT_QUOTA,
397
                      CREATE_SUBVOLUME_NEW_QUOTA,
398
                      CREATE_FIFO,
399
                      CREATE_SYMLINK,
400
                      CREATE_CHAR_DEVICE,
401
                      CREATE_BLOCK_DEVICE,
402
                      COPY_FILES,
403
                      WRITE_FILE,
404
                      EMPTY_DIRECTORY);
405
}
406

407
static bool needs_glob(ItemType t) {
23,798✔
408
        return IN_SET(t,
23,798✔
409
                      WRITE_FILE,
410
                      EMPTY_DIRECTORY,
411
                      SET_XATTR,
412
                      RECURSIVE_SET_XATTR,
413
                      SET_ACL,
414
                      RECURSIVE_SET_ACL,
415
                      SET_ATTRIBUTE,
416
                      RECURSIVE_SET_ATTRIBUTE,
417
                      IGNORE_PATH,
418
                      IGNORE_DIRECTORY_PATH,
419
                      REMOVE_PATH,
420
                      RECURSIVE_REMOVE_PATH,
421
                      RELABEL_PATH,
422
                      RECURSIVE_RELABEL_PATH,
423
                      ADJUST_MODE);
424
}
425

426
static bool takes_ownership(ItemType t) {
5,976✔
427
        return IN_SET(t,
5,976✔
428
                      CREATE_FILE,
429
                      TRUNCATE_FILE,
430
                      CREATE_DIRECTORY,
431
                      TRUNCATE_DIRECTORY,
432
                      CREATE_SUBVOLUME,
433
                      CREATE_SUBVOLUME_INHERIT_QUOTA,
434
                      CREATE_SUBVOLUME_NEW_QUOTA,
435
                      CREATE_FIFO,
436
                      CREATE_SYMLINK,
437
                      CREATE_CHAR_DEVICE,
438
                      CREATE_BLOCK_DEVICE,
439
                      COPY_FILES,
440
                      WRITE_FILE,
441
                      EMPTY_DIRECTORY,
442
                      IGNORE_PATH,
443
                      IGNORE_DIRECTORY_PATH,
444
                      REMOVE_PATH,
445
                      RECURSIVE_REMOVE_PATH);
446
}
447

448
static bool supports_ignore_if_target_missing(ItemType t) {
2✔
449
        return t == CREATE_SYMLINK;
2✔
450
}
451

452
static struct Item* find_glob(OrderedHashmap *h, const char *match) {
189✔
453
        ItemArray *j;
189✔
454

455
        ORDERED_HASHMAP_FOREACH(j, h)
500✔
456
                FOREACH_ARRAY(item, j->items, j->n_items)
678✔
457
                        if (fnmatch(item->path, match, FNM_PATHNAME|FNM_PERIOD) == 0)
367✔
458
                                return item;
20✔
459
        return NULL;
169✔
460
}
461

462
static int load_unix_sockets(Context *c) {
×
463
        _cleanup_set_free_ Set *sockets = NULL;
×
464
        _cleanup_fclose_ FILE *f = NULL;
×
465
        int r;
×
466

467
        if (c->unix_sockets)
×
468
                return 0;
469

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

472
        f = fopen("/proc/net/unix", "re");
×
473
        if (!f)
×
474
                return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
×
475
                                      "Failed to open /proc/net/unix, ignoring: %m");
476

477
        /* Skip header */
478
        r = read_line(f, LONG_LINE_MAX, NULL);
×
479
        if (r < 0)
×
480
                return log_warning_errno(r, "Failed to skip /proc/net/unix header line: %m");
×
481
        if (r == 0)
×
482
                return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Premature end of file reading /proc/net/unix.");
×
483

484
        for (;;) {
×
485
                _cleanup_free_ char *line = NULL;
×
486
                char *p;
×
487

488
                r = read_line(f, LONG_LINE_MAX, &line);
×
489
                if (r < 0)
×
490
                        return log_warning_errno(r, "Failed to read /proc/net/unix line, ignoring: %m");
×
491
                if (r == 0) /* EOF */
×
492
                        break;
493

494
                p = strchr(line, ':');
×
495
                if (!p)
×
496
                        continue;
×
497

498
                if (strlen(p) < 37)
×
499
                        continue;
×
500

501
                p += 37;
×
502
                p += strspn(p, WHITESPACE);
×
503
                p += strcspn(p, WHITESPACE); /* skip one more word */
×
504
                p += strspn(p, WHITESPACE);
×
505

506
                if (!path_is_absolute(p))
×
507
                        continue;
×
508

509
                r = set_put_strdup_full(&sockets, &path_hash_ops_free, p);
×
510
                if (r < 0)
×
511
                        return log_warning_errno(r, "Failed to add AF_UNIX socket to set, ignoring: %m");
×
512
        }
513

514
        c->unix_sockets = TAKE_PTR(sockets);
×
515
        return 1;
×
516
}
517

518
static bool unix_socket_alive(Context *c, const char *fn) {
×
519
        assert(c);
×
520
        assert(fn);
×
521

522
        if (load_unix_sockets(c) < 0)
×
523
                return true;     /* We don't know, so assume yes */
524

525
        return set_contains(c->unix_sockets, fn);
×
526
}
527

528
/* Accessors for the argument in binary format */
529
static const void* item_binary_argument(const Item *i) {
1,803✔
530
        assert(i);
1,803✔
531
        return i->binary_argument ?: i->argument;
1,803✔
532
}
533

534
static size_t item_binary_argument_size(const Item *i) {
1,583✔
535
        assert(i);
1,583✔
536
        return i->binary_argument ? i->binary_argument_size : strlen_ptr(i->argument);
1,583✔
537
}
538

539
static DIR* xopendirat_nomod(int dirfd, const char *path) {
1,576✔
540
        DIR *dir;
1,576✔
541

542
        dir = xopendirat(dirfd, path, O_NOFOLLOW|O_NOATIME);
1,576✔
543
        if (dir)
1,576✔
544
                return dir;
545

546
        if (!IN_SET(errno, ENOENT, ELOOP))
497✔
547
                log_debug_errno(errno, "Cannot open %sdirectory \"%s\" with O_NOATIME: %m", dirfd == AT_FDCWD ? "" : "sub", path);
11✔
548
        if (!ERRNO_IS_PRIVILEGE(errno))
497✔
549
                return NULL;
550

551
        dir = xopendirat(dirfd, path, O_NOFOLLOW);
×
552
        if (!dir)
×
553
                log_debug_errno(errno, "Cannot open %sdirectory \"%s\" with or without O_NOATIME: %m", dirfd == AT_FDCWD ? "" : "sub", path);
×
554

555
        return dir;
556
}
557

558
static DIR* opendir_nomod(const char *path) {
1,535✔
559
        return xopendirat_nomod(AT_FDCWD, path);
1,535✔
560
}
561

562
static int opendir_and_stat(
547✔
563
                const char *path,
564
                DIR **ret,
565
                struct statx *ret_sx,
566
                bool *ret_mountpoint) {
567

568
        _cleanup_closedir_ DIR *d = NULL;
547✔
569
        struct statx sx1;
547✔
570
        int r;
547✔
571

572
        assert(path);
547✔
573
        assert(ret);
547✔
574
        assert(ret_sx);
547✔
575
        assert(ret_mountpoint);
547✔
576

577
        /* Do opendir() and statx() on the directory.
578
         * Return 1 if successful, 0 if file doesn't exist or is not a directory,
579
         * negative errno otherwise.
580
         */
581

582
        d = opendir_nomod(path);
547✔
583
        if (!d) {
547✔
584
                bool ignore = IN_SET(errno, ENOENT, ENOTDIR);
495✔
585
                r = log_full_errno(ignore ? LOG_DEBUG : LOG_ERR,
495✔
586
                                   errno, "Failed to open directory %s: %m", path);
587
                if (!ignore)
495✔
588
                        return r;
589

590
                *ret = NULL;
495✔
591
                *ret_sx = (struct statx) {};
495✔
592
                *ret_mountpoint = false;
495✔
593
                return 0;
495✔
594
        }
595

596
        if (statx(dirfd(d), "", AT_EMPTY_PATH, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, &sx1) < 0)
52✔
597
                return log_error_errno(errno, "statx(%s) failed: %m", path);
×
598

599
        if (FLAGS_SET(sx1.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT))
52✔
600
                *ret_mountpoint = FLAGS_SET(sx1.stx_attributes, STATX_ATTR_MOUNT_ROOT);
52✔
601
        else {
602
                struct statx sx2;
×
603
                if (statx(dirfd(d), "..", 0, STATX_INO, &sx2) < 0)
×
604
                        return log_error_errno(errno, "statx(%s/..) failed: %m", path);
×
605

606
                *ret_mountpoint = !statx_mount_same(&sx1, &sx2);
×
607
        }
608

609
        *ret = TAKE_PTR(d);
52✔
610
        *ret_sx = sx1;
52✔
611
        return 1;
52✔
612
}
613

614
static bool needs_cleanup(
169✔
615
                nsec_t atime,
616
                nsec_t btime,
617
                nsec_t ctime,
618
                nsec_t mtime,
619
                nsec_t cutoff,
620
                const char *sub_path,
621
                AgeBy age_by,
622
                bool is_dir) {
623

624
        if (FLAGS_SET(age_by, AGE_BY_MTIME) && mtime != NSEC_INFINITY && mtime >= cutoff) {
169✔
625
                /* Follows spelling in stat(1). */
626
                log_debug("%s \"%s\": modify time %s is too new.",
38✔
627
                          is_dir ? "Directory" : "File",
628
                          sub_path,
629
                          FORMAT_TIMESTAMP_STYLE(mtime / NSEC_PER_USEC, TIMESTAMP_US));
630

631
                return false;
28✔
632
        }
633

634
        if (FLAGS_SET(age_by, AGE_BY_ATIME) && atime != NSEC_INFINITY && atime >= cutoff) {
141✔
635
                log_debug("%s \"%s\": access time %s is too new.",
117✔
636
                          is_dir ? "Directory" : "File",
637
                          sub_path,
638
                          FORMAT_TIMESTAMP_STYLE(atime / NSEC_PER_USEC, TIMESTAMP_US));
639

640
                return false;
63✔
641
        }
642

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

653
                return false;
7✔
654
        }
655

656
        if (FLAGS_SET(age_by, AGE_BY_BTIME) && btime != NSEC_INFINITY && btime >= cutoff) {
71✔
657
                log_debug("%s \"%s\": birth time %s is too new.",
8✔
658
                          is_dir ? "Directory" : "File",
659
                          sub_path,
660
                          FORMAT_TIMESTAMP_STYLE(btime / NSEC_PER_USEC, TIMESTAMP_US));
661

662
                return false;
5✔
663
        }
664

665
        return true;
666
}
667

668
static int dir_cleanup(
93✔
669
                Context *c,
670
                Item *i,
671
                const char *p,
672
                DIR *d,
673
                nsec_t self_atime_nsec,
674
                nsec_t self_mtime_nsec,
675
                nsec_t cutoff_nsec,
676
                dev_t rootdev_major,
677
                dev_t rootdev_minor,
678
                bool mountpoint,
679
                int maxdepth,
680
                bool keep_this_level,
681
                AgeBy age_by_file,
682
                AgeBy age_by_dir) {
683

684
        bool deleted = false;
93✔
685
        int r = 0;
93✔
686

687
        assert(c);
93✔
688
        assert(i);
93✔
689
        assert(d);
93✔
690

691
        FOREACH_DIRENT_ALL(de, d, break) {
473✔
692
                _cleanup_free_ char *sub_path = NULL;
380✔
693
                nsec_t atime_nsec, mtime_nsec, ctime_nsec, btime_nsec;
380✔
694

695
                if (dot_or_dot_dot(de->d_name))
380✔
696
                        continue;
186✔
697

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

709
                struct statx sx;
194✔
710
                if (statx(dirfd(d), de->d_name,
194✔
711
                          AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT,
712
                          STATX_TYPE|STATX_MODE|STATX_UID|STATX_ATIME|STATX_MTIME|STATX_CTIME|STATX_BTIME,
713
                          &sx) < 0) {
714
                        if (errno == ENOENT)
×
715
                                continue;
×
716

717
                        /* FUSE, NFS mounts, SELinux might return EACCES */
718
                        log_full_errno(errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
×
719
                                       "statx(%s/%s) failed: %m", p, de->d_name);
720
                        continue;
×
721
                }
722

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

738
                        /* Try to detect bind mounts of the same filesystem instance; they do not differ in
739
                         * device major/minors. This type of query is not supported on all kernels or
740
                         * filesystem types though. */
741
                        if (S_ISDIR(sx.stx_mode)) {
×
742
                                int q;
×
743

744
                                q = is_mount_point_at(dirfd(d), de->d_name, 0);
×
745
                                if (q < 0)
×
746
                                        log_debug_errno(q, "Failed to determine whether \"%s/%s\" is a mount point, ignoring: %m", p, de->d_name);
×
747
                                else if (q > 0) {
×
748
                                        log_debug("Ignoring \"%s/%s\": different mount of the same filesystem.", p, de->d_name);
×
749
                                        continue;
×
750
                                }
751
                        }
752
                }
753

754
                atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : 0;
388✔
755
                mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : 0;
388✔
756
                ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? statx_timestamp_load_nsec(&sx.stx_ctime) : 0;
388✔
757
                btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? statx_timestamp_load_nsec(&sx.stx_btime) : 0;
388✔
758

759
                sub_path = path_join(p, de->d_name);
194✔
760
                if (!sub_path) {
194✔
761
                        r = log_oom();
×
762
                        goto finish;
×
763
                }
764

765
                /* Is there an item configured for this path? */
766
                if (ordered_hashmap_get(c->items, sub_path)) {
194✔
767
                        log_debug("Ignoring \"%s\": a separate entry exists.", sub_path);
5✔
768
                        continue;
5✔
769
                }
770

771
                if (find_glob(c->globs, sub_path)) {
189✔
772
                        log_debug("Ignoring \"%s\": a separate glob exists.", sub_path);
20✔
773
                        continue;
20✔
774
                }
775

776
                if (S_ISDIR(sx.stx_mode)) {
169✔
777
                        _cleanup_closedir_ DIR *sub_dir = NULL;
×
778

779
                        if (mountpoint &&
41✔
780
                            streq(de->d_name, "lost+found") &&
4✔
781
                            sx.stx_uid == 0) {
×
782
                                log_debug("Ignoring directory \"%s\".", sub_path);
×
783
                                continue;
×
784
                        }
785

786
                        if (maxdepth <= 0)
41✔
787
                                log_warning("Reached max depth on \"%s\".", sub_path);
×
788
                        else {
789
                                int q;
41✔
790

791
                                sub_dir = xopendirat_nomod(dirfd(d), de->d_name);
41✔
792
                                if (!sub_dir) {
41✔
793
                                        if (errno != ENOENT)
×
794
                                                r = log_warning_errno(errno, "Opening directory \"%s\" failed, ignoring: %m", sub_path);
×
795

796
                                        continue;
×
797
                                }
798

799
                                if (!arg_dry_run &&
78✔
800
                                    flock(dirfd(sub_dir), LOCK_EX|LOCK_NB) < 0) {
37✔
801
                                        log_debug_errno(errno, "Couldn't acquire shared BSD lock on directory \"%s\", skipping: %m", sub_path);
×
802
                                        continue;
×
803
                                }
804

805
                                q = dir_cleanup(c, i,
41✔
806
                                                sub_path, sub_dir,
807
                                                atime_nsec, mtime_nsec, cutoff_nsec,
808
                                                rootdev_major, rootdev_minor,
809
                                                false, maxdepth-1, false,
810
                                                age_by_file, age_by_dir);
811
                                if (q < 0)
41✔
812
                                        r = q;
×
813
                        }
814

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

820
                        if (keep_this_level) {
41✔
821
                                log_debug("Keeping directory \"%s\".", sub_path);
×
822
                                continue;
×
823
                        }
824

825
                        /*
826
                         * Check the file timestamps of an entry against the
827
                         * given cutoff time; delete if it is older.
828
                         */
829
                        if (!needs_cleanup(atime_nsec, btime_nsec, ctime_nsec, mtime_nsec,
41✔
830
                                           cutoff_nsec, sub_path, age_by_dir, true))
831
                                continue;
29✔
832

833
                        log_action("Would remove", "Removing", "%s directory \"%s\"", sub_path);
24✔
834
                        if (!arg_dry_run &&
20✔
835
                            unlinkat(dirfd(d), de->d_name, AT_REMOVEDIR) < 0 &&
8✔
836
                            !IN_SET(errno, ENOENT, ENOTEMPTY))
×
837
                                r = log_warning_errno(errno, "Failed to remove directory \"%s\", ignoring: %m", sub_path);
×
838

839
                } else {
840
                        _cleanup_close_ int fd = -EBADF; /* This file descriptor is defined here so that the
380✔
841
                                                          * lock that is taken below is only dropped _after_
842
                                                          * the unlink operation has finished. */
843

844
                        /* Skip files for which the sticky bit is set. These are semantics we define, and are
845
                         * unknown elsewhere. See XDG_RUNTIME_DIR specification for details. */
846
                        if (sx.stx_mode & S_ISVTX) {
128✔
847
                                log_debug("Skipping \"%s\": sticky bit set.", sub_path);
×
848
                                continue;
×
849
                        }
850

851
                        if (mountpoint &&
128✔
852
                            S_ISREG(sx.stx_mode) &&
×
853
                            sx.stx_uid == 0 &&
×
854
                            STR_IN_SET(de->d_name,
×
855
                                       ".journal",
856
                                       "aquota.user",
857
                                       "aquota.group")) {
858
                                log_debug("Skipping \"%s\".", sub_path);
×
859
                                continue;
×
860
                        }
861

862
                        /* Ignore sockets that are listed in /proc/net/unix */
863
                        if (S_ISSOCK(sx.stx_mode) && unix_socket_alive(c, sub_path)) {
128✔
864
                                log_debug("Skipping \"%s\": live socket.", sub_path);
×
865
                                continue;
×
866
                        }
867

868
                        /* Ignore device nodes */
869
                        if (S_ISCHR(sx.stx_mode) || S_ISBLK(sx.stx_mode)) {
128✔
870
                                log_debug("Skipping \"%s\": a device.", sub_path);
×
871
                                continue;
×
872
                        }
873

874
                        /* Keep files on this level if this was requested */
875
                        if (keep_this_level) {
128✔
876
                                log_debug("Keeping \"%s\".", sub_path);
×
877
                                continue;
×
878
                        }
879

880
                        if (!needs_cleanup(atime_nsec, btime_nsec, ctime_nsec, mtime_nsec,
128✔
881
                                           cutoff_nsec, sub_path, age_by_file, false))
882
                                continue;
74✔
883

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

894
                        log_action("Would remove", "Removing", "%s \"%s\"", sub_path);
104✔
895
                        if (!arg_dry_run &&
88✔
896
                            unlinkat(dirfd(d), de->d_name, 0) < 0 &&
34✔
897
                            errno != ENOENT)
×
898
                                r = log_warning_errno(errno, "Failed to remove \"%s\", ignoring: %m", sub_path);
×
899

900
                        deleted = true;
54✔
901
                }
902
        }
903

904
finish:
93✔
905
        if (deleted && (self_atime_nsec < NSEC_INFINITY || self_mtime_nsec < NSEC_INFINITY)) {
93✔
906
                struct timespec ts[2];
28✔
907

908
                log_action("Would restore", "Restoring",
54✔
909
                           "%s access and modification time on \"%s\": %s, %s",
910
                           p,
911
                           FORMAT_TIMESTAMP_STYLE(self_atime_nsec / NSEC_PER_USEC, TIMESTAMP_US),
912
                           FORMAT_TIMESTAMP_STYLE(self_mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
913

914
                timespec_store_nsec(ts + 0, self_atime_nsec);
28✔
915
                timespec_store_nsec(ts + 1, self_mtime_nsec);
28✔
916

917
                /* Restore original directory timestamps */
918
                if (!arg_dry_run &&
45✔
919
                    futimens(dirfd(d), ts) < 0)
17✔
920
                        log_warning_errno(errno, "Failed to revert timestamps of '%s', ignoring: %m", p);
28✔
921
        }
922

923
        return r;
93✔
924
}
925

926
static bool hardlinks_protected(void) {
×
927
        static int cached = -1;
×
928
        int r;
×
929

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

934
        if (cached >= 0)
×
935
                return cached;
×
936

937
        _cleanup_free_ char *value = NULL;
×
938

939
        r = sysctl_read("fs/protected_hardlinks", &value);
×
940
        if (r < 0) {
×
941
                log_debug_errno(r, "Failed to read fs.protected_hardlinks sysctl, assuming disabled: %m");
×
942
                return false;
×
943
        }
944

945
        cached = parse_boolean(value);
×
946
        if (cached < 0)
×
947
                log_debug_errno(cached, "Failed to parse fs.protected_hardlinks sysctl, assuming disabled: %m");
×
948
        return cached > 0;
×
949
}
950

951
static bool hardlink_vulnerable(const struct stat *st) {
14,809✔
952
        assert(st);
14,809✔
953

954
        return !S_ISDIR(st->st_mode) && st->st_nlink > 1 && !hardlinks_protected();
14,809✔
955
}
956

957
static mode_t process_mask_perms(mode_t mode, mode_t current) {
313✔
958

959
        if ((current & 0111) == 0)
313✔
960
                mode &= ~0111;
211✔
961
        if ((current & 0222) == 0)
313✔
962
                mode &= ~0222;
×
963
        if ((current & 0444) == 0)
313✔
964
                mode &= ~0444;
×
965
        if (!S_ISDIR(current))
313✔
966
                mode &= ~07000; /* remove sticky/sgid/suid bit, unless directory */
211✔
967

968
        return mode;
313✔
969
}
970

971
static int fd_set_perms(
19,135✔
972
                Context *c,
973
                Item *i,
974
                int fd,
975
                const char *path,
976
                const struct stat *st,
977
                CreationMode creation) {
978

979
        bool do_chown, do_chmod;
19,135✔
980
        struct stat stbuf;
19,135✔
981
        mode_t new_mode;
19,135✔
982
        uid_t new_uid;
19,135✔
983
        gid_t new_gid;
19,135✔
984
        int r;
19,135✔
985

986
        assert(c);
19,135✔
987
        assert(i);
19,135✔
988
        assert(fd >= 0);
19,135✔
989
        assert(path);
19,135✔
990

991
        if (!i->mode_set && !i->uid_set && !i->gid_set)
19,135✔
992
                goto shortcut;
5,082✔
993

994
        if (!st) {
14,053✔
995
                if (fstat(fd, &stbuf) < 0)
3,272✔
996
                        return log_error_errno(errno, "fstat(%s) failed: %m", path);
×
997
                st = &stbuf;
998
        }
999

1000
        if (hardlink_vulnerable(st))
14,053✔
1001
                return log_error_errno(SYNTHETIC_ERRNO(EPERM),
×
1002
                                       "Refusing to set permissions on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.",
1003
                                       path);
1004
        new_uid = i->uid_set && (creation != CREATION_EXISTING || !i->uid_only_create) ? i->uid : st->st_uid;
14,053✔
1005
        new_gid = i->gid_set && (creation != CREATION_EXISTING || !i->gid_only_create) ? i->gid : st->st_gid;
14,053✔
1006

1007
        /* Do we need a chown()? */
1008
        do_chown = (new_uid != st->st_uid) || (new_gid != st->st_gid);
14,053✔
1009

1010
        /* Calculate the mode to apply */
1011
        new_mode = i->mode_set && (creation != CREATION_EXISTING || !i->mode_only_create) ?
14,051✔
1012
                (i->mask_perms ? process_mask_perms(i->mode, st->st_mode) : i->mode) :
27,838✔
1013
                (st->st_mode & 07777);
268✔
1014

1015
        do_chmod = ((new_mode ^ st->st_mode) & 07777) != 0;
14,053✔
1016

1017
        if (do_chmod && do_chown) {
14,053✔
1018
                /* Before we issue the chmod() let's reduce the access mode to the common bits of the old and
1019
                 * the new mode. That way there's no time window where the file exists under the old owner
1020
                 * with more than the old access modes — and not under the new owner with more than the new
1021
                 * access modes either. */
1022

1023
                if (S_ISLNK(st->st_mode))
754✔
1024
                        log_debug("Skipping temporary mode fix for symlink %s.", path);
×
1025
                else {
1026
                        mode_t m = new_mode & st->st_mode; /* Mask new mode by old mode */
754✔
1027

1028
                        if (((m ^ st->st_mode) & 07777) == 0)
754✔
1029
                                log_debug("\"%s\" matches temporary mode %o already.", path, m);
743✔
1030
                        else {
1031
                                log_action("Would temporarily change", "Temporarily changing",
22✔
1032
                                           "%s \"%s\" to mode %o", path, m);
1033
                                if (!arg_dry_run) {
11✔
1034
                                        r = fchmod_opath(fd, m);
11✔
1035
                                        if (r < 0)
11✔
1036
                                                return log_error_errno(r, "fchmod() of %s failed: %m", path);
×
1037
                                }
1038
                        }
1039
                }
1040
        }
1041

1042
        if (do_chown) {
14,053✔
1043
                log_action("Would change", "Changing",
4,284✔
1044
                           "%s \"%s\" to owner "UID_FMT":"GID_FMT, path, new_uid, new_gid);
1045

1046
                if (!arg_dry_run &&
2,878✔
1047
                    fchownat(fd, "",
1,438✔
1048
                             new_uid != st->st_uid ? new_uid : UID_INVALID,
1,438✔
1049
                             new_gid != st->st_gid ? new_gid : GID_INVALID,
1,438✔
1050
                             AT_EMPTY_PATH) < 0)
1051
                        return log_error_errno(errno, "fchownat() of %s failed: %m", path);
×
1052
        }
1053

1054
        /* Now, apply the final mode. We do this in two cases: when the user set a mode explicitly, or after a
1055
         * chown(), since chown()'s mangle the access mode in regards to sgid/suid in some conditions. */
1056
        if (do_chmod || do_chown) {
14,053✔
1057
                if (S_ISLNK(st->st_mode))
2,771✔
1058
                        log_debug("Skipping mode fix for symlink %s.", path);
×
1059
                else {
1060
                        log_action("Would change", "Changing", "%s \"%s\" to mode %o", path, new_mode);
8,273✔
1061
                        if (!arg_dry_run) {
2,771✔
1062
                                r = fchmod_opath(fd, new_mode);
2,769✔
1063
                                if (r < 0)
2,769✔
1064
                                        return log_error_errno(r, "fchmod() of %s failed: %m", path);
2✔
1065
                        }
1066
                }
1067
        }
1068

1069
shortcut:
14,051✔
1070
        return label_fix_full(fd, /* inode_path= */ NULL, /* label_path= */ path, 0);
19,133✔
1071
}
1072

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

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

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

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

1102
        return fd;
16,219✔
1103
}
1104

1105
static int path_open_safe(const char *path) {
3,605✔
1106
        int r, fd;
3,605✔
1107

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

1112
        assert(path);
3,605✔
1113

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

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

1125
        return fd;
3,605✔
1126
}
1127

1128
static int path_set_perms(
3,111✔
1129
                Context *c,
1130
                Item *i,
1131
                const char *path,
1132
                CreationMode creation) {
1133

1134
        _cleanup_close_ int fd = -EBADF;
3,111✔
1135

1136
        assert(c);
3,111✔
1137
        assert(i);
3,111✔
1138
        assert(path);
3,111✔
1139

1140
        fd = path_open_safe(path);
3,111✔
1141
        if (fd == -ENOENT)
3,111✔
1142
                return 0;
1143
        if (fd < 0)
3,111✔
1144
                return fd;
1145

1146
        return fd_set_perms(c, i, fd, path, /* st= */ NULL, creation);
3,111✔
1147
}
1148

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

1153
        assert(i);
×
1154

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

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

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

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

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

1179
                name = value = NULL;
×
1180
        }
1181

1182
        return 0;
×
1183
}
1184

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

1193
        int r;
×
1194

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

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

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

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

1220
        _cleanup_close_ int fd = -EBADF;
×
1221

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

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

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

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

1239
        assert(item);
2,566✔
1240

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

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

1253
        return 0;
2,566✔
1254
}
1255

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

1265
        acl_entry_t entry;
312✔
1266
        acl_permset_t permset;
312✔
1267
        bool has_exec;
312✔
1268
        int r;
312✔
1269

1270
        assert(path);
312✔
1271
        assert(st);
312✔
1272
        assert(cond_exec);
312✔
1273
        assert(ret);
312✔
1274

1275
        if (!S_ISDIR(st->st_mode)) {
312✔
1276
                _cleanup_(acl_freep) acl_t old = NULL;
×
1277

1278
                old = acl_get_file(path, ACL_TYPE_ACCESS);
210✔
1279
                if (!old)
210✔
1280
                        return -errno;
×
1281

1282
                has_exec = false;
210✔
1283

1284
                for (r = acl_get_entry(old, ACL_FIRST_ENTRY, &entry);
210✔
1285
                     r > 0;
836✔
1286
                     r = acl_get_entry(old, ACL_NEXT_ENTRY, &entry)) {
626✔
1287

1288
                        acl_tag_t tag;
638✔
1289

1290
                        if (acl_get_tag_type(entry, &tag) < 0)
638✔
1291
                                return -errno;
×
1292

1293
                        if (tag == ACL_MASK)
638✔
1294
                                continue;
8✔
1295

1296
                        /* If not appending, skip ACL definitions */
1297
                        if (!append && IN_SET(tag, ACL_USER, ACL_GROUP))
632✔
1298
                                continue;
2✔
1299

1300
                        if (acl_get_permset(entry, &permset) < 0)
630✔
1301
                                return -errno;
×
1302

1303
                        r = acl_get_perm(permset, ACL_EXECUTE);
630✔
1304
                        if (r < 0)
630✔
1305
                                return -errno;
×
1306
                        if (r > 0) {
630✔
1307
                                has_exec = true;
12✔
1308
                                break;
12✔
1309
                        }
1310
                }
1311
                if (r < 0)
210✔
1312
                        return -errno;
×
1313

1314
                /* Check if we're about to set the execute bit in acl_access */
1315
                if (!has_exec && access) {
210✔
1316
                        for (r = acl_get_entry(access, ACL_FIRST_ENTRY, &entry);
×
1317
                             r > 0;
×
1318
                             r = acl_get_entry(access, ACL_NEXT_ENTRY, &entry)) {
×
1319

1320
                                if (acl_get_permset(entry, &permset) < 0)
×
1321
                                        return -errno;
×
1322

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

1337
        _cleanup_(acl_freep) acl_t parsed = access ? acl_dup(access) : acl_init(0);
624✔
1338
        if (!parsed)
312✔
1339
                return -errno;
×
1340

1341
        for (r = acl_get_entry(cond_exec, ACL_FIRST_ENTRY, &entry);
312✔
1342
             r > 0;
932✔
1343
             r = acl_get_entry(cond_exec, ACL_NEXT_ENTRY, &entry)) {
620✔
1344

1345
                acl_entry_t parsed_entry;
620✔
1346

1347
                if (acl_create_entry(&parsed, &parsed_entry) < 0)
620✔
1348
                        return -errno;
×
1349

1350
                if (acl_copy_entry(parsed_entry, entry) < 0)
620✔
1351
                        return -errno;
×
1352

1353
                /* We substituted 'X' with 'x' in parse_acl(), so drop execute bit here if not applicable. */
1354
                if (!has_exec) {
620✔
1355
                        if (acl_get_permset(parsed_entry, &permset) < 0)
393✔
1356
                                return -errno;
×
1357

1358
                        if (acl_delete_perm(permset, ACL_EXECUTE) < 0)
393✔
1359
                                return -errno;
×
1360
                }
1361
        }
1362
        if (r < 0)
312✔
1363
                return -errno;
×
1364

1365
        if (!append) { /* want_mask = true */
312✔
1366
                r = calc_acl_mask_if_needed(&parsed);
3✔
1367
                if (r < 0)
3✔
1368
                        return r;
1369
        }
1370

1371
        *ret = TAKE_PTR(parsed);
312✔
1372

1373
        return 0;
312✔
1374
}
1375

1376
static int path_set_acl(
1,029✔
1377
                Context *c,
1378
                const char *path,
1379
                const char *pretty,
1380
                acl_type_t type,
1381
                acl_t acl,
1382
                bool modify) {
1383

1384
        _cleanup_(acl_free_charpp) char *t = NULL;
1,029✔
1385
        _cleanup_(acl_freep) acl_t dup = NULL;
1,029✔
1386
        int r;
1,029✔
1387

1388
        assert(c);
1,029✔
1389

1390
        /* Returns 0 for success, positive error if already warned, negative error otherwise. */
1391

1392
        if (modify) {
1,029✔
1393
                r = acls_for_file(path, type, acl, &dup);
1,026✔
1394
                if (r < 0)
1,026✔
1395
                        return r;
1396

1397
                r = calc_acl_mask_if_needed(&dup);
1,024✔
1398
                if (r < 0)
1,024✔
1399
                        return r;
1400
        } else {
1401
                dup = acl_dup(acl);
3✔
1402
                if (!dup)
3✔
1403
                        return -errno;
×
1404

1405
                /* the mask was already added earlier if needed */
1406
        }
1407

1408
        r = add_base_acls_if_needed(&dup, path);
1,027✔
1409
        if (r < 0)
1,027✔
1410
                return r;
1411

1412
        t = acl_to_any_text(dup, NULL, ',', TEXT_ABBREVIATE);
1,027✔
1413
        log_action("Would set", "Setting",
3,541✔
1414
                   "%s %s ACL %s on %s",
1415
                   type == ACL_TYPE_ACCESS ? "access" : "default",
1416
                   strna(t), pretty);
1417

1418
        if (!arg_dry_run &&
2,053✔
1419
            acl_set_file(path, type, dup) < 0) {
1,026✔
1420
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
1421
                        /* No error if filesystem doesn't support ACLs. Return negative. */
1422
                        return -errno;
×
1423
                else
1424
                        /* Return positive to indicate we already warned */
1425
                        return -log_error_errno(errno,
×
1426
                                                "Setting %s ACL \"%s\" on %s failed: %m",
1427
                                                type == ACL_TYPE_ACCESS ? "access" : "default",
1428
                                                strna(t), pretty);
1429
        }
1430
        return 0;
1431
}
1432
#endif
1433

1434
static int fd_set_acls(
756✔
1435
                Context *c,
1436
                Item *item,
1437
                int fd,
1438
                const char *path,
1439
                const struct stat *st,
1440
                CreationMode creation) {
1441

1442
        int r = 0;
756✔
1443
#if HAVE_ACL
1444
        _cleanup_(acl_freep) acl_t access_with_exec_parsed = NULL;
756✔
1445
        struct stat stbuf;
756✔
1446

1447
        assert(c);
756✔
1448
        assert(item);
756✔
1449
        assert(fd >= 0);
756✔
1450
        assert(path);
756✔
1451

1452
        if (!st) {
756✔
1453
                if (fstat(fd, &stbuf) < 0)
448✔
1454
                        return log_error_errno(errno, "fstat(%s) failed: %m", path);
×
1455
                st = &stbuf;
1456
        }
1457

1458
        if (hardlink_vulnerable(st))
756✔
1459
                return log_error_errno(SYNTHETIC_ERRNO(EPERM),
×
1460
                                       "Refusing to set ACLs on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.",
1461
                                       path);
1462

1463
        if (S_ISLNK(st->st_mode)) {
756✔
1464
                log_debug("Skipping ACL fix for symlink %s.", path);
×
1465
                return 0;
×
1466
        }
1467

1468
        if (item->acl_access_exec) {
756✔
1469
                r = parse_acl_cond_exec(FORMAT_PROC_FD_PATH(fd), st,
312✔
1470
                                        item->acl_access_exec,
1471
                                        item->acl_access,
1472
                                        item->append_or_force,
312✔
1473
                                        &access_with_exec_parsed);
1474
                if (r < 0)
312✔
1475
                        return log_error_errno(r, "Failed to parse conditionalized execute bit for \"%s\": %m", path);
×
1476

1477
                r = path_set_acl(c, FORMAT_PROC_FD_PATH(fd), path, ACL_TYPE_ACCESS, access_with_exec_parsed, item->append_or_force);
312✔
1478
        } else if (item->acl_access)
444✔
1479
                r = path_set_acl(c, FORMAT_PROC_FD_PATH(fd), path, ACL_TYPE_ACCESS, item->acl_access, item->append_or_force);
194✔
1480

1481
        /* set only default acls to folders */
1482
        if (r == 0 && item->acl_default && S_ISDIR(st->st_mode))
756✔
1483
                r = path_set_acl(c, FORMAT_PROC_FD_PATH(fd), path, ACL_TYPE_DEFAULT, item->acl_default, item->append_or_force);
523✔
1484

1485
        if (ERRNO_IS_NOT_SUPPORTED(r)) {
756✔
1486
                log_debug_errno(r, "ACLs not supported by file system at %s", path);
2✔
1487
                return 0;
2✔
1488
        }
1489
        if (r > 0)
754✔
1490
                return -r; /* already warned in path_set_acl */
×
1491
        if (r < 0)
754✔
1492
                return log_error_errno(r, "ACL operation on \"%s\" failed: %m", path);
×
1493
#endif
1494
        return r;
1495
}
1496

1497
static int path_set_acls(
448✔
1498
                Context *c,
1499
                Item *item,
1500
                const char *path,
1501
                CreationMode creation) {
1502

1503
        int r = 0;
448✔
1504
#if HAVE_ACL
1505
        _cleanup_close_ int fd = -EBADF;
448✔
1506

1507
        assert(c);
448✔
1508
        assert(item);
448✔
1509
        assert(path);
448✔
1510

1511
        fd = path_open_safe(path);
448✔
1512
        if (fd == -ENOENT)
448✔
1513
                return 0;
1514
        if (fd < 0)
448✔
1515
                return fd;
1516

1517
        r = fd_set_acls(c, item, fd, path, /* st= */ NULL, creation);
448✔
1518
#endif
1519
        return r;
1520
}
1521

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

1544
        enum {
1,098✔
1545
                MODE_ADD,
1546
                MODE_DEL,
1547
                MODE_SET
1548
        } mode = MODE_ADD;
1,098✔
1549

1550
        unsigned value = 0, mask = 0;
1,098✔
1551
        const char *p;
1,098✔
1552

1553
        assert(item);
1,098✔
1554

1555
        p = item->argument;
1,098✔
1556
        if (p) {
1,098✔
1557
                if (*p == '+') {
1,098✔
1558
                        mode = MODE_ADD;
1,098✔
1559
                        p++;
1,098✔
1560
                } else if (*p == '-') {
×
1561
                        mode = MODE_DEL;
×
1562
                        p++;
×
1563
                } else  if (*p == '=') {
×
1564
                        mode = MODE_SET;
×
1565
                        p++;
×
1566
                }
1567
        }
1568

1569
        if (isempty(p) && mode != MODE_SET)
1,098✔
1570
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1571
                                       "Setting file attribute on '%s' needs an attribute specification.",
1572
                                       item->path);
1573

1574
        for (; p && *p ; p++) {
2,196✔
1575
                unsigned i, v;
1576

1577
                for (i = 0; i < ELEMENTSOF(attributes); i++)
15,372✔
1578
                        if (*p == attributes[i].character)
15,372✔
1579
                                break;
1580

1581
                if (i >= ELEMENTSOF(attributes))
1,098✔
1582
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1583
                                               "Unknown file attribute '%c' on '%s'.", *p, item->path);
1584

1585
                v = attributes[i].value;
1,098✔
1586

1587
                SET_FLAG(value, v, IN_SET(mode, MODE_ADD, MODE_SET));
1,098✔
1588

1589
                mask |= v;
1,098✔
1590
        }
1591

1592
        if (mode == MODE_SET)
1,098✔
1593
                mask |= CHATTR_ALL_FL;
×
1594

1595
        assert(mask != 0);
1,098✔
1596

1597
        item->attribute_mask = mask;
1,098✔
1598
        item->attribute_value = value;
1,098✔
1599
        item->attribute_set = true;
1,098✔
1600

1601
        return 0;
1,098✔
1602
}
1603

1604
static int fd_set_attribute(
46✔
1605
                Context *c,
1606
                Item *item,
1607
                int fd,
1608
                const char *path,
1609
                const struct stat *st,
1610
                CreationMode creation) {
1611

1612
        struct stat stbuf;
46✔
1613
        unsigned f;
46✔
1614
        int r;
46✔
1615

1616
        assert(c);
46✔
1617
        assert(item);
46✔
1618
        assert(fd >= 0);
46✔
1619
        assert(path);
46✔
1620

1621
        if (!item->attribute_set || item->attribute_mask == 0)
46✔
1622
                return 0;
46✔
1623

1624
        if (!st) {
46✔
1625
                if (fstat(fd, &stbuf) < 0)
46✔
1626
                        return log_error_errno(errno, "fstat(%s) failed: %m", path);
×
1627
                st = &stbuf;
1628
        }
1629

1630
        /* Issuing the file attribute ioctls on device nodes is not safe, as that will be delivered to the
1631
         * drivers, not the file system containing the device node. */
1632
        if (!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode))
46✔
1633
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1634
                                       "Setting file flags is only supported on regular files and directories, cannot set on '%s'.",
1635
                                       path);
1636

1637
        f = item->attribute_value & item->attribute_mask;
46✔
1638

1639
        /* Mask away directory-specific flags */
1640
        if (!S_ISDIR(st->st_mode))
46✔
1641
                f &= ~FS_DIRSYNC_FL;
×
1642

1643
        log_action("Would try to set", "Trying to set",
138✔
1644
                   "%s file attributes 0x%08x on %s",
1645
                   f & item->attribute_mask,
1646
                   path);
1647

1648
        if (!arg_dry_run) {
46✔
1649
                _cleanup_close_ int procfs_fd = -EBADF;
46✔
1650

1651
                procfs_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOATIME);
46✔
1652
                if (procfs_fd < 0)
46✔
1653
                        return log_error_errno(procfs_fd, "Failed to reopen '%s': %m", path);
×
1654

1655
                unsigned previous, current;
46✔
1656
                r = chattr_full(procfs_fd, NULL, f, item->attribute_mask, &previous, &current, CHATTR_FALLBACK_BITWISE);
46✔
1657
                if (r == -ENOANO)
46✔
1658
                        log_warning("Cannot set file attributes for '%s', maybe due to incompatibility in specified attributes, "
×
1659
                                    "previous=0x%08x, current=0x%08x, expected=0x%08x, ignoring.",
1660
                                    path, previous, current, (previous & ~item->attribute_mask) | (f & item->attribute_mask));
1661
                else if (r < 0)
46✔
1662
                        log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r,
46✔
1663
                                       "Cannot set file attributes for '%s', value=0x%08x, mask=0x%08x, ignoring: %m",
1664
                                       path, item->attribute_value, item->attribute_mask);
1665
        }
1666

1667
        return 0;
1668
}
1669

1670
static int path_set_attribute(
46✔
1671
                Context *c,
1672
                Item *item,
1673
                const char *path,
1674
                CreationMode creation) {
1675

1676
        _cleanup_close_ int fd = -EBADF;
46✔
1677

1678
        assert(c);
46✔
1679
        assert(item);
46✔
1680

1681
        if (!item->attribute_set || item->attribute_mask == 0)
46✔
1682
                return 0;
1683

1684
        fd = path_open_safe(path);
46✔
1685
        if (fd == -ENOENT)
46✔
1686
                return 0;
1687
        if (fd < 0)
46✔
1688
                return fd;
1689

1690
        return fd_set_attribute(c, item, fd, path, /* st= */ NULL, creation);
46✔
1691
}
1692

1693
static int write_argument_data(Item *i, int fd, const char *path) {
288✔
1694
        int r;
288✔
1695

1696
        assert(i);
288✔
1697
        assert(fd >= 0);
288✔
1698
        assert(path);
288✔
1699

1700
        if (item_binary_argument_size(i) == 0)
288✔
1701
                return 0;
1702

1703
        assert(item_binary_argument(i));
262✔
1704

1705
        log_action("Would write", "Writing", "%s to \"%s\"", path);
751✔
1706

1707
        if (!arg_dry_run) {
262✔
1708
                r = loop_write(fd, item_binary_argument(i), item_binary_argument_size(i));
259✔
1709
                if (r < 0)
259✔
1710
                        return log_error_errno(r, "Failed to write file \"%s\": %m", path);
×
1711
        }
1712

1713
        return 0;
1714
}
1715

1716
static int write_one_file(Context *c, Item *i, const char *path, CreationMode creation) {
18✔
1717
        _cleanup_close_ int fd = -EBADF, dir_fd = -EBADF;
18✔
1718
        _cleanup_free_ char *bn = NULL;
18✔
1719
        int r;
18✔
1720

1721
        assert(c);
18✔
1722
        assert(i);
18✔
1723
        assert(path);
18✔
1724
        assert(i->type == WRITE_FILE);
18✔
1725

1726
        r = path_extract_filename(path, &bn);
18✔
1727
        if (r < 0)
18✔
1728
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
1729
        if (r == O_DIRECTORY)
18✔
1730
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for writing, is a directory.", path);
×
1731

1732
        /* Validate the path and keep the fd on the directory for opening the file so we're sure that it
1733
         * can't be changed behind our back. */
1734
        dir_fd = path_open_parent_safe(path, i->allow_failure);
18✔
1735
        if (dir_fd < 0)
18✔
1736
                return dir_fd;
1737

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

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

1750
                return log_error_errno(errno, "Failed to open file \"%s\": %m", path);
×
1751
        }
1752

1753
        /* 'w' is allowed to write into any kind of files. */
1754

1755
        r = write_argument_data(i, fd, path);
18✔
1756
        if (r < 0)
18✔
1757
                return r;
1758

1759
        return fd_set_perms(c, i, fd, path, NULL, creation);
18✔
1760
}
1761

1762
static int create_file(
776✔
1763
                Context *c,
1764
                Item *i,
1765
                const char *path) {
1766

1767
        _cleanup_close_ int fd = -EBADF, dir_fd = -EBADF;
776✔
1768
        _cleanup_free_ char *bn = NULL;
776✔
1769
        struct stat stbuf, *st = NULL;
776✔
1770
        CreationMode creation;
776✔
1771
        int r = 0;
776✔
1772

1773
        assert(c);
776✔
1774
        assert(i);
776✔
1775
        assert(path);
776✔
1776
        assert(i->type == CREATE_FILE);
776✔
1777

1778
        /* 'f' operates on regular files exclusively. */
1779

1780
        r = path_extract_filename(path, &bn);
776✔
1781
        if (r < 0)
776✔
1782
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
1783
        if (r == O_DIRECTORY)
776✔
1784
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for writing, is a directory.", path);
×
1785

1786
        if (arg_dry_run) {
776✔
1787
                log_info("Would create file %s", path);
4✔
1788
                return 0;
4✔
1789

1790
                /* The opening of the directory below would fail if it doesn't exist,
1791
                 * so log and exit before even trying to do that. */
1792
        }
1793

1794
        /* Validate the path and keep the fd on the directory for opening the file so we're sure that it
1795
         * can't be changed behind our back. */
1796
        dir_fd = path_open_parent_safe(path, i->allow_failure);
772✔
1797
        if (dir_fd < 0)
772✔
1798
                return dir_fd;
1799

1800
        WITH_UMASK(0000) {
1,540✔
1801
                mac_selinux_create_file_prepare(path, S_IFREG);
770✔
1802
                fd = RET_NERRNO(openat(dir_fd, bn, O_CREAT|O_EXCL|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode));
770✔
1803
                mac_selinux_create_file_clear();
770✔
1804
        }
1805

1806
        if (fd < 0) {
770✔
1807
                /* Even on a read-only filesystem, open(2) returns EEXIST if the file already exists. It
1808
                 * returns EROFS only if it needs to create the file. */
1809
                if (fd != -EEXIST)
622✔
1810
                        return log_error_errno(fd, "Failed to create file %s: %m", path);
1✔
1811

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

1819
                if (fstat(fd, &stbuf) < 0)
621✔
1820
                        return log_error_errno(errno, "stat(%s) failed: %m", path);
×
1821

1822
                if (!S_ISREG(stbuf.st_mode))
621✔
1823
                        return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
5✔
1824
                                               "%s exists and is not a regular file.",
1825
                                               path);
1826

1827
                st = &stbuf;
1828
                creation = CREATION_EXISTING;
1829
        } else {
1830
                r = write_argument_data(i, fd, path);
148✔
1831
                if (r < 0)
148✔
1832
                        return r;
1833

1834
                creation = CREATION_NORMAL;
1835
        }
1836

1837
        return fd_set_perms(c, i, fd, path, st, creation);
764✔
1838
}
1839

1840
static int truncate_file(
251✔
1841
                Context *c,
1842
                Item *i,
1843
                const char *path) {
1844

1845
        _cleanup_close_ int fd = -EBADF, dir_fd = -EBADF;
251✔
1846
        _cleanup_free_ char *bn = NULL;
251✔
1847
        struct stat stbuf, *st = NULL;
251✔
1848
        CreationMode creation;
251✔
1849
        bool erofs = false;
251✔
1850
        int r = 0;
251✔
1851

1852
        assert(c);
251✔
1853
        assert(i);
251✔
1854
        assert(path);
251✔
1855
        assert(i->type == TRUNCATE_FILE || (i->type == CREATE_FILE && i->append_or_force));
251✔
1856

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

1861
        r = path_extract_filename(path, &bn);
251✔
1862
        if (r < 0)
251✔
1863
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
1864
        if (r == O_DIRECTORY)
251✔
1865
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for truncation, is a directory.", path);
×
1866

1867
        /* Validate the path and keep the fd on the directory for opening the file so we're sure that it
1868
         * can't be changed behind our back. */
1869
        dir_fd = path_open_parent_safe(path, i->allow_failure);
251✔
1870
        if (dir_fd < 0)
251✔
1871
                return dir_fd;
1872

1873
        if (arg_dry_run) {
250✔
1874
                log_info("Would truncate %s", path);
×
1875
                return 0;
×
1876
        }
1877

1878
        creation = CREATION_EXISTING;
250✔
1879
        fd = RET_NERRNO(openat(dir_fd, bn, O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode));
250✔
1880
        if (fd == -ENOENT) {
230✔
1881
                creation = CREATION_NORMAL; /* Didn't work without O_CREATE, try again with */
225✔
1882

1883
                WITH_UMASK(0000) {
450✔
1884
                        mac_selinux_create_file_prepare(path, S_IFREG);
225✔
1885
                        fd = RET_NERRNO(openat(dir_fd, bn, O_CREAT|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode));
225✔
1886
                        mac_selinux_create_file_clear();
225✔
1887
                }
1888
        }
1889

1890
        if (fd < 0) {
250✔
1891
                if (fd != -EROFS)
6✔
1892
                        return log_error_errno(fd, "Failed to open/create file %s: %m", path);
1✔
1893

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

1898
                fd = openat(dir_fd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH, i->mode);
5✔
1899
                if (fd < 0) {
5✔
1900
                        if (errno == ENOENT)
1✔
1901
                                return log_error_errno(SYNTHETIC_ERRNO(EROFS),
1✔
1902
                                                       "Cannot create file %s on a read-only file system.",
1903
                                                       path);
1904

1905
                        return log_error_errno(errno, "Failed to reopen file %s: %m", path);
×
1906
                }
1907

1908
                erofs = true;
1909
                creation = CREATION_EXISTING;
1910
        }
1911

1912
        if (fstat(fd, &stbuf) < 0)
248✔
1913
                return log_error_errno(errno, "stat(%s) failed: %m", path);
×
1914

1915
        if (!S_ISREG(stbuf.st_mode))
248✔
1916
                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
1917
                                       "%s exists and is not a regular file.",
1918
                                       path);
1919

1920
        if (stbuf.st_size > 0) {
248✔
1921
                if (ftruncate(fd, 0) < 0) {
13✔
1922
                        r = erofs ? -EROFS : -errno;
2✔
1923
                        return log_error_errno(r, "Failed to truncate file %s: %m", path);
2✔
1924
                }
1925
        } else
1926
                st = &stbuf;
1927

1928
        log_debug("\"%s\" has been created.", path);
246✔
1929

1930
        if (item_binary_argument(i)) {
246✔
1931
                r = write_argument_data(i, fd, path);
122✔
1932
                if (r < 0)
122✔
1933
                        return r;
1934
        }
1935

1936
        return fd_set_perms(c, i, fd, path, st, creation);
246✔
1937
}
1938

1939
static int copy_files(Context *c, Item *i) {
3,845✔
1940
        _cleanup_close_ int dfd = -EBADF, fd = -EBADF;
3,845✔
1941
        _cleanup_free_ char *bn = NULL;
3,845✔
1942
        struct stat st, a;
3,845✔
1943
        int r;
3,845✔
1944

1945
        log_action("Would copy", "Copying", "%s tree \"%s\" to \"%s\"", i->argument, i->path);
11,394✔
1946
        if (arg_dry_run)
3,845✔
1947
                return 0;
1948

1949
        r = path_extract_filename(i->path, &bn);
3,843✔
1950
        if (r < 0)
3,843✔
1951
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path);
×
1952

1953
        /* Validate the path and use the returned directory fd for copying the target so we're sure that the
1954
         * path can't be changed behind our back. */
1955
        dfd = path_open_parent_safe(i->path, i->allow_failure);
3,843✔
1956
        if (dfd < 0)
3,843✔
1957
                return dfd;
1958

1959
        r = copy_tree_at(AT_FDCWD, i->argument,
3,863✔
1960
                         dfd, bn,
1961
                         i->uid_set ? i->uid : UID_INVALID,
3,843✔
1962
                         i->gid_set ? i->gid : GID_INVALID,
3,843✔
1963
                         COPY_REFLINK | ((i->append_or_force) ? COPY_MERGE : COPY_MERGE_EMPTY) | COPY_MAC_CREATE | COPY_HARDLINKS,
3,843✔
1964
                         NULL, NULL);
1965

1966
        fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
3,843✔
1967
        if (fd < 0) {
3,843✔
1968
                if (r < 0) /* Look at original error first */
×
1969
                        return log_error_errno(r, "Failed to copy files to %s: %m", i->path);
×
1970

1971
                return log_error_errno(errno, "Failed to openat(%s): %m", i->path);
×
1972
        }
1973

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

1977
        if (stat(i->argument, &a) < 0)
3,843✔
1978
                return log_error_errno(errno, "Failed to stat(%s): %m", i->argument);
×
1979

1980
        if (((st.st_mode ^ a.st_mode) & S_IFMT) != 0) {
3,843✔
1981
                log_debug("Can't copy to %s, file exists already and is of different type", i->path);
×
1982
                return 0;
×
1983
        }
1984

1985
        return fd_set_perms(c, i, fd, i->path, &st, _CREATION_MODE_INVALID);
3,843✔
1986
}
1987

1988
static int create_directory_or_subvolume(
7,860✔
1989
                const char *path,
1990
                mode_t mode,
1991
                bool subvol,
1992
                bool allow_failure,
1993
                struct stat *ret_st,
1994
                CreationMode *ret_creation) {
1995

1996
        _cleanup_free_ char *bn = NULL;
7,860✔
1997
        _cleanup_close_ int pfd = -EBADF;
7,860✔
1998
        CreationMode creation;
7,860✔
1999
        struct stat st;
7,860✔
2000
        int r, fd;
7,860✔
2001

2002
        assert(path);
7,860✔
2003

2004
        r = path_extract_filename(path, &bn);
7,860✔
2005
        if (r < 0)
7,860✔
2006
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
2007

2008
        pfd = path_open_parent_safe(path, allow_failure);
7,860✔
2009
        if (pfd < 0)
7,860✔
2010
                return pfd;
2011

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

2038
        if (!subvol || ERRNO_IS_NEG_NOT_SUPPORTED(r)) {
×
2039
                log_action("Would create", "Creating", "%s directory \"%s\"", path);
23,284✔
2040
                if (!arg_dry_run)
7,858✔
2041
                        WITH_UMASK(0000)
15,716✔
2042
                                r = mkdirat_label(pfd, bn, mode);
7,858✔
2043
        }
2044

2045
        if (arg_dry_run)
7,858✔
2046
                return 0;
2047

2048
        creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
7,858✔
2049

2050
        fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH);
7,858✔
2051
        if (fd < 0) {
7,858✔
2052
                /* We couldn't open it because it is not actually a directory? */
2053
                if (errno == ENOTDIR)
×
2054
                        return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "\"%s\" already exists and is not a directory.", path);
×
2055

2056
                /* Then look at the original error */
2057
                if (r < 0)
×
2058
                        return log_full_errno(allow_failure ? LOG_INFO : LOG_ERR,
×
2059
                                              r,
2060
                                              "Failed to create directory or subvolume \"%s\"%s: %m",
2061
                                              path,
2062
                                              allow_failure ? ", ignoring" : "");
2063

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

2067
        if (fstat(fd, &st) < 0)
7,858✔
2068
                return log_error_errno(errno, "Failed to fstat(%s): %m", path);
×
2069

2070
        assert(S_ISDIR(st.st_mode)); /* we used O_DIRECTORY above */
7,858✔
2071

2072
        log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), path);
7,858✔
2073

2074
        if (ret_st)
7,858✔
2075
                *ret_st = st;
7,858✔
2076
        if (ret_creation)
7,858✔
2077
                *ret_creation = creation;
7,858✔
2078

2079
        return fd;
2080
}
2081

2082
static int create_directory(
6,996✔
2083
                Context *c,
2084
                Item *i,
2085
                const char *path) {
2086

2087
        _cleanup_close_ int fd = -EBADF;
6,996✔
2088
        CreationMode creation;
6,996✔
2089
        struct stat st;
6,996✔
2090

2091
        assert(c);
6,996✔
2092
        assert(i);
6,996✔
2093
        assert(IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY));
6,996✔
2094

2095
        if (arg_dry_run) {
6,996✔
2096
                log_info("Would create directory %s", path);
11✔
2097
                return 0;
11✔
2098
        }
2099

2100
        fd = create_directory_or_subvolume(path, i->mode, /* subvol= */ false, i->allow_failure, &st, &creation);
6,985✔
2101
        if (fd == -EEXIST)
6,985✔
2102
                return 0;
2103
        if (fd < 0)
6,985✔
2104
                return fd;
2105

2106
        return fd_set_perms(c, i, fd, path, &st, creation);
6,983✔
2107
}
2108

2109
static int create_subvolume(
875✔
2110
                Context *c,
2111
                Item *i,
2112
                const char *path) {
2113

2114
        _cleanup_close_ int fd = -EBADF;
875✔
2115
        CreationMode creation;
875✔
2116
        struct stat st;
875✔
2117
        int r, q = 0;
875✔
2118

2119
        assert(c);
875✔
2120
        assert(i);
875✔
2121
        assert(IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA));
875✔
2122

2123
        if (arg_dry_run) {
875✔
2124
                log_info("Would create subvolume %s", path);
×
2125
                return 0;
×
2126
        }
2127

2128
        fd = create_directory_or_subvolume(path, i->mode, /* subvol = */ true, i->allow_failure, &st, &creation);
875✔
2129
        if (fd == -EEXIST)
875✔
2130
                return 0;
2131
        if (fd < 0)
875✔
2132
                return fd;
2133

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

2151
        r = fd_set_perms(c, i, fd, path, &st, creation);
875✔
2152
        if (q < 0) /* prefer the quota change error from above */
875✔
2153
                return q;
×
2154

2155
        return r;
2156
}
2157

2158
static int empty_directory(
5✔
2159
                Context *c,
2160
                Item *i,
2161
                const char *path,
2162
                CreationMode creation) {
2163

2164
        _cleanup_close_ int fd = -EBADF;
5✔
2165
        struct stat st;
5✔
2166
        int r;
5✔
2167

2168
        assert(c);
5✔
2169
        assert(i);
5✔
2170
        assert(i->type == EMPTY_DIRECTORY);
5✔
2171

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

2184
        if (fstat(fd, &st) < 0)
5✔
2185
                return log_error_errno(errno, "Failed to fstat(%s): %m", path);
×
2186
        if (!S_ISDIR(st.st_mode)) {
5✔
2187
                log_warning("'%s' already exists and is not a directory.", path);
1✔
2188
                return 0;
1✔
2189
        }
2190

2191
        return fd_set_perms(c, i, fd, path, &st, creation);
4✔
2192
}
2193

2194
static int create_device(
1,984✔
2195
                Context *c,
2196
                Item *i,
2197
                mode_t file_type) {
2198

2199
        _cleanup_close_ int dfd = -EBADF, fd = -EBADF;
1,984✔
2200
        _cleanup_free_ char *bn = NULL;
1,984✔
2201
        CreationMode creation;
1,984✔
2202
        struct stat st;
1,984✔
2203
        int r;
1,984✔
2204

2205
        assert(c);
1,984✔
2206
        assert(i);
1,984✔
2207
        assert(IN_SET(i->type, CREATE_BLOCK_DEVICE, CREATE_CHAR_DEVICE));
1,984✔
2208
        assert(IN_SET(file_type, S_IFBLK, S_IFCHR));
1,984✔
2209

2210
        r = path_extract_filename(i->path, &bn);
1,984✔
2211
        if (r < 0)
1,984✔
2212
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path);
×
2213
        if (r == O_DIRECTORY)
1,984✔
2214
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR),
×
2215
                                       "Cannot open path '%s' for creating device node, is a directory.", i->path);
2216

2217
        if (arg_dry_run) {
1,984✔
2218
                log_info("Would create device node %s", i->path);
4✔
2219
                return 0;
4✔
2220
        }
2221

2222
        /* Validate the path and use the returned directory fd for copying the target so we're sure that the
2223
         * path can't be changed behind our back. */
2224
        dfd = path_open_parent_safe(i->path, i->allow_failure);
1,980✔
2225
        if (dfd < 0)
1,980✔
2226
                return dfd;
2227

2228
        WITH_UMASK(0000) {
3,960✔
2229
                mac_selinux_create_file_prepare(i->path, file_type);
1,980✔
2230
                r = RET_NERRNO(mknodat(dfd, bn, i->mode | file_type, i->major_minor));
1,980✔
2231
                mac_selinux_create_file_clear();
1,980✔
2232
        }
2233
        creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
1,980✔
2234

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

2241
                if (r < 0) {
×
2242
                        if (ERRNO_IS_PRIVILEGE(r))
×
2243
                                goto handle_privilege;
×
2244

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

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

2251
        if (fstat(fd, &st) < 0)
1,980✔
2252
                return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
×
2253

2254
        if (((st.st_mode ^ file_type) & S_IFMT) != 0) {
1,980✔
2255

2256
                if (i->append_or_force) {
×
2257
                        fd = safe_close(fd);
×
2258

2259
                        WITH_UMASK(0000) {
×
2260
                                mac_selinux_create_file_prepare(i->path, file_type);
×
2261
                                r = mknodat_atomic(dfd, bn, i->mode | file_type, i->major_minor);
×
2262
                                mac_selinux_create_file_clear();
×
2263
                        }
2264
                        if (ERRNO_IS_PRIVILEGE(r))
×
2265
                                goto handle_privilege;
×
2266
                        if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) {
×
2267
                                r = rm_rf_child(dfd, bn, REMOVE_PHYSICAL);
×
2268
                                if (r < 0)
×
2269
                                        return log_error_errno(r, "rm -rf %s failed: %m", i->path);
×
2270

2271
                                mac_selinux_create_file_prepare(i->path, file_type);
×
2272
                                r = RET_NERRNO(mknodat(dfd, bn, i->mode | file_type, i->major_minor));
×
2273
                                mac_selinux_create_file_clear();
×
2274
                        }
2275
                        if (r < 0)
×
2276
                                return log_error_errno(r, "Failed to create device node '%s': %m", i->path);
×
2277

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

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

2286
                        if (((st.st_mode ^ file_type) & S_IFMT) != 0)
×
2287
                                return log_error_errno(SYNTHETIC_ERRNO(EBADF),
×
2288
                                                       "Device node we just created is not a device node, refusing.");
2289

2290
                        creation = CREATION_FORCE;
2291
                } else {
2292
                        log_warning("\"%s\" already exists and is not a device node.", i->path);
×
2293
                        return 0;
×
2294
                }
2295
        }
2296

2297
        log_debug("%s %s device node \"%s\" %u:%u.",
3,960✔
2298
                  creation_mode_verb_to_string(creation),
2299
                  i->type == CREATE_BLOCK_DEVICE ? "block" : "char",
2300
                  i->path, major(i->mode), minor(i->mode));
2301

2302
        return fd_set_perms(c, i, fd, i->path, &st, creation);
1,980✔
2303

2304
handle_privilege:
×
2305
        log_debug_errno(r,
1,984✔
2306
                        "We lack permissions, possibly because of cgroup configuration; "
2307
                        "skipping creation of device node '%s'.", i->path);
2308
        return 0;
2309
}
2310

2311
static int create_fifo(Context *c, Item *i) {
4✔
2312
        _cleanup_close_ int pfd = -EBADF, fd = -EBADF;
4✔
2313
        _cleanup_free_ char *bn = NULL;
4✔
2314
        CreationMode creation;
4✔
2315
        struct stat st;
4✔
2316
        int r;
4✔
2317

2318
        assert(c);
4✔
2319
        assert(i);
4✔
2320
        assert(i->type == CREATE_FIFO);
4✔
2321

2322
        r = path_extract_filename(i->path, &bn);
4✔
2323
        if (r < 0)
4✔
2324
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path);
×
2325
        if (r == O_DIRECTORY)
4✔
2326
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR),
×
2327
                                       "Cannot open path '%s' for creating FIFO, is a directory.", i->path);
2328

2329
        if (arg_dry_run) {
4✔
2330
                log_info("Would create fifo %s", i->path);
1✔
2331
                return 0;
1✔
2332
        }
2333

2334
        pfd = path_open_parent_safe(i->path, i->allow_failure);
3✔
2335
        if (pfd < 0)
3✔
2336
                return pfd;
2337

2338
        WITH_UMASK(0000) {
6✔
2339
                mac_selinux_create_file_prepare(i->path, S_IFIFO);
3✔
2340
                r = RET_NERRNO(mkfifoat(pfd, bn, i->mode));
3✔
2341
                mac_selinux_create_file_clear();
3✔
2342
        }
2343

2344
        creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
3✔
2345

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

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

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

2358
        if (!S_ISFIFO(st.st_mode)) {
3✔
2359

2360
                if (i->append_or_force) {
2✔
2361
                        fd = safe_close(fd);
1✔
2362

2363
                        WITH_UMASK(0000) {
2✔
2364
                                mac_selinux_create_file_prepare(i->path, S_IFIFO);
1✔
2365
                                r = mkfifoat_atomic(pfd, bn, i->mode);
1✔
2366
                                mac_selinux_create_file_clear();
1✔
2367
                        }
2368
                        if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) {
1✔
2369
                                r = rm_rf_child(pfd, bn, REMOVE_PHYSICAL);
×
2370
                                if (r < 0)
×
2371
                                        return log_error_errno(r, "rm -rf %s failed: %m", i->path);
×
2372

2373
                                mac_selinux_create_file_prepare(i->path, S_IFIFO);
×
2374
                                r = RET_NERRNO(mkfifoat(pfd, bn, i->mode));
×
2375
                                mac_selinux_create_file_clear();
×
2376
                        }
2377
                        if (r < 0)
1✔
2378
                                return log_error_errno(r, "Failed to create FIFO %s: %m", i->path);
×
2379

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

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

2388
                        if (!S_ISFIFO(st.st_mode))
1✔
2389
                                return log_error_errno(SYNTHETIC_ERRNO(EBADF),
×
2390
                                                       "FIFO inode we just created is not a FIFO, refusing.");
2391

2392
                        creation = CREATION_FORCE;
2393
                } else {
2394
                        log_warning("\"%s\" already exists and is not a FIFO.", i->path);
1✔
2395
                        return 0;
1✔
2396
                }
2397
        }
2398

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

2401
        return fd_set_perms(c, i, fd, i->path, &st, creation);
2✔
2402
}
2403

2404
static int create_symlink(Context *c, Item *i) {
1,500✔
2405
        _cleanup_close_ int pfd = -EBADF, fd = -EBADF;
1,500✔
2406
        _cleanup_free_ char *bn = NULL;
1,500✔
2407
        CreationMode creation;
1,500✔
2408
        struct stat st;
1,500✔
2409
        bool good = false;
1,500✔
2410
        int r;
1,500✔
2411

2412
        assert(c);
1,500✔
2413
        assert(i);
1,500✔
2414

2415
        if (i->ignore_if_target_missing) {
1,500✔
2416
                r = chase(i->argument, arg_root, CHASE_SAFE|CHASE_PREFIX_ROOT|CHASE_NOFOLLOW, /* ret_path = */ NULL, /* ret_fd = */ NULL);
2✔
2417
                if (r == -ENOENT) {
2✔
2418
                        /* Silently skip over lines where the source file is missing. */
2419
                        log_info("Symlink source path '%s' does not exist, skipping line.", prefix_roota(arg_root, i->argument));
2✔
2420
                        return 0;
1✔
2421
                }
2422
                if (r < 0)
1✔
2423
                        return log_error_errno(r, "Failed to check if symlink source path '%s' exists: %m", prefix_roota(arg_root, i->argument));
×
2424
        }
2425

2426
        r = path_extract_filename(i->path, &bn);
1,499✔
2427
        if (r < 0)
1,499✔
2428
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path);
×
2429
        if (r == O_DIRECTORY)
1,499✔
2430
                return log_error_errno(SYNTHETIC_ERRNO(EISDIR),
×
2431
                                       "Cannot open path '%s' for creating symlink, is a directory.", i->path);
2432

2433
        if (arg_dry_run) {
1,499✔
2434
                log_info("Would create symlink %s -> %s", i->path, i->argument);
2✔
2435
                return 0;
2✔
2436
        }
2437

2438
        pfd = path_open_parent_safe(i->path, i->allow_failure);
1,497✔
2439
        if (pfd < 0)
1,497✔
2440
                return pfd;
2441

2442
        mac_selinux_create_file_prepare(i->path, S_IFLNK);
1,497✔
2443
        r = RET_NERRNO(symlinkat(i->argument, pfd, bn));
1,497✔
2444
        mac_selinux_create_file_clear();
1,497✔
2445

2446
        creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING;
1,497✔
2447

2448
        fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH);
1,497✔
2449
        if (fd < 0) {
1,497✔
2450
                if (r < 0)
×
2451
                        return log_error_errno(r, "Failed to create symlink '%s': %m", i->path); /* original error! */
×
2452

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

2456
        if (fstat(fd, &st) < 0)
1,497✔
2457
                return log_error_errno(errno, "Failed to fstat(%s): %m", i->path);
×
2458

2459
        if (S_ISLNK(st.st_mode)) {
1,497✔
2460
                _cleanup_free_ char *x = NULL;
×
2461

2462
                r = readlinkat_malloc(fd, "", &x);
1,490✔
2463
                if (r < 0)
1,490✔
2464
                        return log_error_errno(r, "readlinkat(%s) failed: %m", i->path);
×
2465

2466
                good = streq(x, i->argument);
1,490✔
2467
        } else
2468
                good = false;
2469

2470
        if (!good) {
1,490✔
2471
                if (!i->append_or_force) {
510✔
2472
                        log_debug("\"%s\" is not a symlink or does not point to the correct path.", i->path);
506✔
2473
                        return 0;
506✔
2474
                }
2475

2476
                fd = safe_close(fd);
4✔
2477

2478
                mac_selinux_create_file_prepare(i->path, S_IFLNK);
4✔
2479
                r = symlinkat_atomic_full(i->argument, pfd, bn, /* make_relative= */ false);
4✔
2480
                mac_selinux_create_file_clear();
4✔
2481
                if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) {
4✔
2482
                        r = rm_rf_child(pfd, bn, REMOVE_PHYSICAL);
1✔
2483
                        if (r < 0)
1✔
2484
                                return log_error_errno(r, "rm -rf %s failed: %m", i->path);
×
2485

2486
                        mac_selinux_create_file_prepare(i->path, S_IFLNK);
1✔
2487
                        r = RET_NERRNO(symlinkat(i->argument, pfd, i->path));
1✔
2488
                        mac_selinux_create_file_clear();
1✔
2489
                }
2490
                if (r < 0)
3✔
2491
                        return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path);
×
2492

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

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

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

2504
                creation = CREATION_FORCE;
2505
        }
2506

2507
        log_debug("%s symlink \"%s\".", creation_mode_verb_to_string(creation), i->path);
991✔
2508
        return fd_set_perms(c, i, fd, i->path, &st, creation);
991✔
2509
}
2510

2511
typedef int (*action_t)(Context *c, Item *i, const char *path, CreationMode creation);
2512
typedef int (*fdaction_t)(Context *c, Item *i, int fd, const char *path, const struct stat *st, CreationMode creation);
2513

2514
static int item_do(
626✔
2515
                Context *c,
2516
                Item *i,
2517
                int fd,
2518
                const char *path,
2519
                CreationMode creation,
2520
                fdaction_t action) {
2521

2522
        struct stat st;
626✔
2523
        int r;
626✔
2524

2525
        assert(c);
626✔
2526
        assert(i);
626✔
2527
        assert(fd >= 0);
626✔
2528
        assert(path);
626✔
2529
        assert(action);
626✔
2530

2531
        if (fstat(fd, &st) < 0) {
626✔
2532
                r = log_error_errno(errno, "fstat() on file failed: %m");
×
2533
                goto finish;
×
2534
        }
2535

2536
        /* This returns the first error we run into, but nevertheless tries to go on */
2537
        r = action(c, i, fd, path, &st, creation);
626✔
2538

2539
        if (S_ISDIR(st.st_mode)) {
626✔
2540
                _cleanup_closedir_ DIR *d = NULL;
206✔
2541

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

2550
                FOREACH_DIRENT_ALL(de, d, RET_GATHER(r, -errno); goto finish) {
1,038✔
2551
                        _cleanup_close_ int de_fd = -EBADF;
832✔
2552
                        _cleanup_free_ char *de_path = NULL;
832✔
2553

2554
                        if (dot_or_dot_dot(de->d_name))
832✔
2555
                                continue;
412✔
2556

2557
                        de_fd = openat(fd, de->d_name, O_NOFOLLOW|O_CLOEXEC|O_PATH);
420✔
2558
                        if (de_fd < 0) {
420✔
2559
                                if (errno != ENOENT)
×
2560
                                        RET_GATHER(r, log_error_errno(errno, "Failed to open file '%s': %m", de->d_name));
×
2561
                                continue;
×
2562
                        }
2563

2564
                        de_path = path_join(path, de->d_name);
420✔
2565
                        if (!de_path) {
420✔
2566
                                r = log_oom();
×
2567
                                goto finish;
×
2568
                        }
2569

2570
                        /* Pass ownership of dirent fd over */
2571
                        RET_GATHER(r, item_do(c, i, TAKE_FD(de_fd), de_path, CREATION_EXISTING, action));
420✔
2572
                }
2573
        }
2574

2575
finish:
420✔
2576
        safe_close(fd);
626✔
2577
        return r;
626✔
2578
}
2579

2580
static int glob_item(Context *c, Item *i, action_t action) {
6,249✔
2581
        _cleanup_globfree_ glob_t g = {
6,249✔
2582
                .gl_opendir = (void *(*)(const char *)) opendir_nomod,
2583
        };
2584
        int r;
6,249✔
2585

2586
        assert(c);
6,249✔
2587
        assert(i);
6,249✔
2588
        assert(action);
6,249✔
2589

2590
        r = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g);
6,249✔
2591
        if (r == -ENOENT)
6,249✔
2592
                return 0;
2593
        if (r < 0)
3,167✔
2594
                return log_error_errno(r, "Failed to glob '%s': %m", i->path);
×
2595

2596
        r = 0;
3,167✔
2597
        STRV_FOREACH(fn, g.gl_pathv)
6,841✔
2598
                /* We pass CREATION_EXISTING here, since if we are globbing for it, it always has to exist */
2599
                RET_GATHER(r, action(c, i, *fn, CREATION_EXISTING));
3,674✔
2600

2601
        return r;
2602
}
2603

2604
static int glob_item_recursively(
252✔
2605
                Context *c,
2606
                Item *i,
2607
                fdaction_t action) {
2608

2609
        _cleanup_globfree_ glob_t g = {
252✔
2610
                .gl_opendir = (void *(*)(const char *)) opendir_nomod,
2611
        };
2612
        int r;
252✔
2613

2614
        assert(c);
252✔
2615
        assert(i);
252✔
2616
        assert(action);
252✔
2617

2618
        r = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g);
252✔
2619
        if (r == -ENOENT)
252✔
2620
                return 0;
2621
        if (r < 0)
206✔
2622
                return log_error_errno(r, "Failed to glob '%s': %m", i->path);
×
2623

2624
        r = 0;
206✔
2625
        STRV_FOREACH(fn, g.gl_pathv) {
412✔
2626
                _cleanup_close_ int fd = -EBADF;
206✔
2627

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

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

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

2641
        return r;
2642
}
2643

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2738
        path_len = strlen(path);
×
2739

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2824
        return 0;
2825
}
2826

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

2830
        assert(c);
23,556✔
2831
        assert(i);
23,556✔
2832

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

2835
        switch (i->type) {
23,556✔
2836

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

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

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

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

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

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

2872
                break;
2873

2874
        case CREATE_DIRECTORY:
6,996✔
2875
        case TRUNCATE_DIRECTORY:
2876
                r = mkdir_parents_item(i, S_IFDIR);
6,996✔
2877
                if (r < 0)
6,996✔
2878
                        return r;
2879

2880
                r = create_directory(c, i, i->path);
6,996✔
2881
                if (r < 0)
6,996✔
2882
                        return r;
2✔
2883
                break;
2884

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

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

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

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

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

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

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

2922
                break;
2923

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

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

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

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

2942
                break;
2943

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

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

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

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

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

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

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

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

2994
        return 0;
2995
}
2996

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3089
                return 0;
3090

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

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

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

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

3105
        switch (i->type) {
17,934✔
3106

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

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

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

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

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

3128
        for (size_t i = 0; i < ELEMENTSOF(ab_map); i++)
300✔
3129
                if (BIT_SET(ab, i))
240✔
3130
                        ret[j++] = is_dir ? ascii_toupper(ab_map[i]) : ab_map[i];
163✔
3131

3132
        ret[j] = 0;
60✔
3133
        return ret;
60✔
3134
}
3135

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

3142
        assert(i);
139✔
3143

3144
        if (!i->age_set)
139✔
3145
                return 0;
139✔
3146

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

3151
        usec_t cutoff = n - i->age;
46✔
3152

3153
        _cleanup_closedir_ DIR *d = NULL;
139✔
3154
        struct statx sx;
46✔
3155
        bool mountpoint;
46✔
3156
        int r;
46✔
3157

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

3162
        if (DEBUG_LOGGING) {
42✔
3163
                _cleanup_free_ char *ab_f = NULL, *ab_d = NULL;
30✔
3164

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

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

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

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

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

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

3196
        switch (i->type) {
195✔
3197

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

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

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

3217
static int process_item(
78,014✔
3218
                Context *c,
3219
                Item *i,
3220
                OperationMask operation) {
3221

3222
        OperationMask todo;
78,014✔
3223
        _cleanup_free_ char *_path = NULL;
78,014✔
3224
        const char *path;
78,014✔
3225
        int r;
78,014✔
3226

3227
        assert(c);
78,014✔
3228
        assert(i);
78,014✔
3229

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

3234
        i->done |= operation;
41,700✔
3235

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

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

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

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

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

3265
        return r;
3266
}
3267

3268
static int process_item_array(
71,715✔
3269
                Context *c,
3270
                ItemArray *array,
3271
                OperationMask operation) {
3272

3273
        int r = 0;
71,715✔
3274

3275
        assert(c);
71,715✔
3276
        assert(array);
71,715✔
3277

3278
        /* Create any parent first. */
3279
        if (FLAGS_SET(operation, OPERATION_CREATE) && array->parent)
71,715✔
3280
                r = process_item_array(c, array->parent, operation & OPERATION_CREATE);
17,109✔
3281

3282
        /* Clean up all children first */
3283
        if ((operation & (OPERATION_REMOVE|OPERATION_CLEAN|OPERATION_PURGE)) && !set_isempty(array->children)) {
71,715✔
3284
                ItemArray *cc;
6,504✔
3285

3286
                SET_FOREACH(cc, array->children)
21,999✔
3287
                        RET_GATHER(r, process_item_array(c, cc, operation & (OPERATION_REMOVE|OPERATION_CLEAN|OPERATION_PURGE)));
15,495✔
3288
        }
3289

3290
        FOREACH_ARRAY(item, array->items, array->n_items)
149,729✔
3291
                RET_GATHER(r, process_item(c, item, operation));
78,014✔
3292

3293
        return r;
71,715✔
3294
}
3295

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

3303
#if HAVE_ACL
3304
        if (i->acl_access)
88,421✔
3305
                acl_free(i->acl_access);
1,464✔
3306

3307
        if (i->acl_access_exec)
88,421✔
3308
                acl_free(i->acl_access_exec);
370✔
3309

3310
        if (i->acl_default)
88,421✔
3311
                acl_free(i->acl_default);
2,196✔
3312
#endif
3313
}
88,421✔
3314

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

3319
        FOREACH_ARRAY(item, a->items, a->n_items)
46,181✔
3320
                item_free_contents(item);
23,785✔
3321

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

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

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

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

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

3344
        if (takes_ownership(a->type) && takes_ownership(b->type))
1,652✔
3345
                /* check if the items are the same */
3346
                return memcmp_nn(item_binary_argument(a), item_binary_argument_size(a),
518✔
3347
                                 item_binary_argument(b), item_binary_argument_size(b)) == 0 &&
499✔
3348

3349
                        a->uid_set == b->uid_set &&
499✔
3350
                        a->uid == b->uid &&
499✔
3351
                        a->uid_only_create == b->uid_only_create &&
499✔
3352

3353
                        a->gid_set == b->gid_set &&
499✔
3354
                        a->gid == b->gid &&
499✔
3355
                        a->gid_only_create == b->gid_only_create &&
499✔
3356

3357
                        a->mode_set == b->mode_set &&
499✔
3358
                        a->mode == b->mode &&
499✔
3359
                        a->mode_only_create == b->mode_only_create &&
499✔
3360

3361
                        a->age_set == b->age_set &&
499✔
3362
                        a->age == b->age &&
499✔
3363

3364
                        a->age_by_file == b->age_by_file &&
499✔
3365
                        a->age_by_dir == b->age_by_dir &&
499✔
3366

3367
                        a->mask_perms == b->mask_perms &&
499✔
3368

3369
                        a->keep_first_level == b->keep_first_level &&
1,535✔
3370

3371
                        a->major_minor == b->major_minor;
499✔
3372

3373
        return true;
3374
}
3375

3376
static bool should_include_path(const char *path) {
64,512✔
3377
        STRV_FOREACH(prefix, arg_exclude_prefixes)
83,233✔
3378
                if (path_startswith(path, *prefix)) {
21,121✔
3379
                        log_debug("Entry \"%s\" matches exclude prefix \"%s\", skipping.",
2,400✔
3380
                                  path, *prefix);
3381
                        return false;
2,400✔
3382
                }
3383

3384
        STRV_FOREACH(prefix, arg_include_prefixes)
99,554✔
3385
                if (path_startswith(path, *prefix)) {
42,242✔
3386
                        log_debug("Entry \"%s\" matches include prefix \"%s\".", path, *prefix);
4,800✔
3387
                        return true;
4,800✔
3388
                }
3389

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

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

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

3401
        assert(i);
24,670✔
3402

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

3406
        switch (i->type) {
6,328✔
3407
        case COPY_FILES:
3,079✔
3408
        case CREATE_SYMLINK:
3409
        case CREATE_FILE:
3410
        case TRUNCATE_FILE:
3411
        case WRITE_FILE: {
3412
                _cleanup_free_ char *unescaped = NULL, *resolved = NULL;
3,079✔
3413
                ssize_t l;
3,079✔
3414

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

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

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

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

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

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

3443
static int patch_var_run(const char *fname, unsigned line, char **path) {
64,513✔
3444
        const char *k;
64,513✔
3445
        char *n;
64,513✔
3446

3447
        assert(path);
64,513✔
3448
        assert(*path);
64,513✔
3449

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

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

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

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

3478
        free_and_replace(*path, n);
×
3479

3480
        return 0;
×
3481
}
3482

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

3486
        assert(user);
8,789✔
3487
        assert(ret_uid);
8,789✔
3488

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

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

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

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

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

3511
        assert(group);
10,265✔
3512
        assert(ret_gid);
10,265✔
3513

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

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

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

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

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

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

3546
        assert(age_by_str);
148✔
3547
        assert(item);
148✔
3548

3549
        if (isempty(age_by_str))
148✔
3550
                return -EINVAL;
3551

3552
        for (const char *s = age_by_str; *s != 0; s++) {
315✔
3553
                size_t i;
173✔
3554

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

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

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

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

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

3582
        return 0;
141✔
3583
}
3584

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

3589
        FOREACH_ARRAY(e, existing->items, existing->n_items) {
3,041✔
3590
                if (item_compatible(e, i))
1,652✔
3591
                        continue;
1,633✔
3592

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

3599
        return false;
3600
}
3601

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

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

3623
        assert(fname);
64,636✔
3624
        assert(line >= 1);
64,636✔
3625
        assert(buffer);
64,636✔
3626

3627
        const Specifier specifier_table[] = {
64,636✔
3628
                { 'h', specifier_user_home,       NULL },
3629

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

3635
                COMMON_SYSTEM_SPECIFIERS,
3636
                COMMON_CREDS_SPECIFIERS(arg_runtime_scope),
64,636✔
3637
                COMMON_TMP_SPECIFIERS,
3638
                {}
3639
        };
3640

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

3661
        if (!empty_or_dash(buffer)) {
64,636✔
3662
                i.argument = strdup(buffer);
15,495✔
3663
                if (!i.argument)
15,495✔
3664
                        return log_oom();
×
3665
        }
3666

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

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

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

3702
        i.type = action[0];
64,513✔
3703
        i.append_or_force = append_or_force;
64,513✔
3704
        i.allow_failure = allow_failure;
64,513✔
3705
        i.try_replace = try_replace;
64,513✔
3706
        i.purge = purge;
64,513✔
3707
        i.ignore_if_target_missing = ignore_if_target_missing;
64,513✔
3708

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

3719
        r = patch_var_run(fname, line, &i.path);
64,513✔
3720
        if (r < 0)
64,513✔
3721
                return r;
3722

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

3729
        path_simplify(i.path);
64,513✔
3730

3731
        switch (i.type) {
64,513✔
3732

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

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

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

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

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

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

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

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

3801
                break;
3802

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

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

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

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

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

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

3874
        if (!should_include_path(i.path))
64,512✔
3875
                return 0;
3876

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

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

3898
                break;
3899

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

3910
                }
3911

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

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

3921
                path_simplify(i.argument);
4,360✔
3922

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

3930
                break;
3931

3932
        default:
24,181✔
3933
                ;
24,181✔
3934
        }
3935

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

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

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

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

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

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

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

3978
        if (!empty_or_dash(user)) {
23,797✔
3979
                const char *u;
8,789✔
3980

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

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

3999
        if (!empty_or_dash(group)) {
23,797✔
4000
                const char *g;
10,265✔
4001

4002
                g = startswith(group, ":");
10,265✔
4003
                if (g)
10,265✔
4004
                        i.gid_only_create = true;
376✔
4005
                else
4006
                        g = group;
4007

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

4020
        if (!empty_or_dash(mode)) {
23,797✔
4021
                const char *mm;
4022
                unsigned m;
4023

4024
                for (mm = mode;; mm++)
502✔
4025
                        if (*mm == '~')
15,692✔
4026
                                i.mask_perms = true;
126✔
4027
                        else if (*mm == ':')
15,566✔
4028
                                i.mode_only_create = true;
376✔
4029
                        else
4030
                                break;
4031

4032
                r = parse_mode(mm, &m);
15,190✔
4033
                if (r < 0) {
15,190✔
4034
                        *invalid_config = true;
×
4035
                        return log_syntax(NULL, LOG_ERR, fname, line, r, "Invalid mode '%s'.", mode);
×
4036
                }
4037

4038
                i.mode = m;
15,190✔
4039
                i.mode_set = true;
15,190✔
4040
        } else
4041
                i.mode = IN_SET(i.type,
8,607✔
4042
                                CREATE_DIRECTORY,
4043
                                TRUNCATE_DIRECTORY,
4044
                                CREATE_SUBVOLUME,
4045
                                CREATE_SUBVOLUME_INHERIT_QUOTA,
4046
                                CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644;
4047

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

4056
        if (!empty_or_dash(age)) {
23,797✔
4057
                const char *a = age;
1,270✔
4058
                _cleanup_free_ char *seconds = NULL, *age_by = NULL;
1,270✔
4059

4060
                if (*a == '~') {
1,270✔
4061
                        i.keep_first_level = true;
×
4062
                        a++;
×
4063
                }
4064

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

4081
                        /* For parsing the "age" part, after the ":". */
4082
                        a = seconds;
141✔
4083
                }
4084

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

4091
                i.age_set = true;
1,261✔
4092
        }
4093

4094
        h = needs_glob(i.type) ? c->globs : c->items;
23,788✔
4095

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

4108
                r = ordered_hashmap_put(h, i.path, existing);
22,396✔
4109
                if (r < 0) {
22,396✔
4110
                        free(existing);
×
4111
                        return log_oom();
×
4112
                }
4113
        }
4114

4115
        if (!GREEDY_REALLOC(existing->items, existing->n_items + 1))
23,785✔
4116
                return log_oom();
×
4117

4118
        existing->items[existing->n_items++] = TAKE_STRUCT(i);
23,785✔
4119

4120
        /* Sort item array, to enforce stable ordering of application */
4121
        typesafe_qsort(existing->items, existing->n_items, item_compare);
23,785✔
4122

4123
        return 0;
4124
}
4125

4126
static int cat_config(char **config_dirs, char **args) {
×
4127
        _cleanup_strv_free_ char **files = NULL;
×
4128
        int r;
×
4129

4130
        r = conf_files_list_with_replacement(arg_root, config_dirs, arg_replace, &files, NULL);
×
4131
        if (r < 0)
×
4132
                return r;
4133

4134
        pager_open(arg_pager_flags);
×
4135

4136
        return cat_files(NULL, files, arg_cat_flags);
×
4137
}
4138

4139
static int exclude_default_prefixes(void) {
×
4140
        int r;
×
4141

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

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

4156
        strv_uniq(arg_exclude_prefixes);
×
4157
        return 0;
×
4158
}
4159

4160
static int help(void) {
×
4161
        _cleanup_free_ char *link = NULL;
×
4162
        int r;
×
4163

4164
        r = terminal_urlify_man("systemd-tmpfiles", "8", &link);
×
4165
        if (r < 0)
×
4166
                return log_oom();
×
4167

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

4200
        return 0;
4201
}
4202

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

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

4248
        int c, r;
681✔
4249

4250
        assert(argc >= 0);
681✔
4251
        assert(argv);
681✔
4252

4253
        while ((c = getopt_long(argc, argv, "hE", options, NULL)) >= 0)
2,886✔
4254

4255
                switch (c) {
2,205✔
4256

4257
                case 'h':
×
4258
                        return help();
×
4259

4260
                case ARG_VERSION:
×
4261
                        return version();
×
4262

4263
                case ARG_CAT_CONFIG:
×
4264
                        arg_cat_flags = CAT_CONFIG_ON;
×
4265
                        break;
×
4266

4267
                case ARG_TLDR:
×
4268
                        arg_cat_flags = CAT_TLDR;
×
4269
                        break;
×
4270

4271
                case ARG_USER:
177✔
4272
                        arg_runtime_scope = RUNTIME_SCOPE_USER;
177✔
4273
                        break;
177✔
4274

4275
                case ARG_CREATE:
628✔
4276
                        arg_operation |= OPERATION_CREATE;
628✔
4277
                        break;
628✔
4278

4279
                case ARG_CLEAN:
40✔
4280
                        arg_operation |= OPERATION_CLEAN;
40✔
4281
                        break;
40✔
4282

4283
                case ARG_REMOVE:
306✔
4284
                        arg_operation |= OPERATION_REMOVE;
306✔
4285
                        break;
306✔
4286

4287
                case ARG_BOOT:
532✔
4288
                        arg_boot = true;
532✔
4289
                        break;
532✔
4290

4291
                case ARG_PURGE:
5✔
4292
                        arg_operation |= OPERATION_PURGE;
5✔
4293
                        break;
5✔
4294

4295
                case ARG_GRACEFUL:
120✔
4296
                        arg_graceful = true;
120✔
4297
                        break;
120✔
4298

4299
                case ARG_PREFIX:
240✔
4300
                        if (strv_extend(&arg_include_prefixes, optarg) < 0)
240✔
4301
                                return log_oom();
×
4302
                        break;
4303

4304
                case ARG_EXCLUDE_PREFIX:
120✔
4305
                        if (strv_extend(&arg_exclude_prefixes, optarg) < 0)
120✔
4306
                                return log_oom();
×
4307
                        break;
4308

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

4315
                case ARG_IMAGE:
×
4316
#ifdef STANDALONE
4317
                        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
4318
                                               "This systemd-tmpfiles version is compiled without support for --image=.");
4319
#else
4320
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
×
4321
                        if (r < 0)
×
4322
                                return r;
4323
#endif
4324
                        /* Imply -E here since it makes little sense to create files persistently in the /run mountpoint of a disk image */
4325
                        _fallthrough_;
×
4326

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

4332
                        break;
4333

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

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

4348
                        arg_replace = optarg;
×
4349
                        break;
×
4350

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

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

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

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

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

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

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

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

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

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

4390
        return 1;
4391
}
4392

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

4400
        ItemArray *ia;
15,558✔
4401
        int r = 0;
15,558✔
4402

4403
        assert(c);
15,558✔
4404
        assert(fn);
15,558✔
4405

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

4411
        /* we have to determine age parameter for each entry of type X */
4412
        ORDERED_HASHMAP_FOREACH(ia, c->globs)
119,003✔
4413
                FOREACH_ARRAY(i, ia->items, ia->n_items) {
212,937✔
4414
                        ItemArray *ja;
109,134✔
4415
                        Item *candidate_item = NULL;
109,134✔
4416

4417
                        if (i->type != IGNORE_DIRECTORY_PATH)
109,134✔
4418
                                continue;
107,350✔
4419

4420
                        ORDERED_HASHMAP_FOREACH(ja, c->items)
162,158✔
4421
                                FOREACH_ARRAY(j, ja->items, ja->n_items) {
325,898✔
4422
                                        if (!IN_SET(j->type, CREATE_DIRECTORY,
165,524✔
4423
                                                             TRUNCATE_DIRECTORY,
4424
                                                             CREATE_SUBVOLUME,
4425
                                                             CREATE_SUBVOLUME_INHERIT_QUOTA,
4426
                                                             CREATE_SUBVOLUME_NEW_QUOTA))
4427
                                                continue;
83,912✔
4428

4429
                                        if (path_equal(j->path, i->path)) {
81,612✔
4430
                                                candidate_item = j;
4431
                                                break;
4432
                                        }
4433

4434
                                        if (candidate_item
81,612✔
4435
                                            ? (path_startswith(j->path, candidate_item->path) && fnmatch(i->path, j->path, FNM_PATHNAME | FNM_PERIOD) == 0)
7,722✔
4436
                                            : path_startswith(i->path, j->path) != NULL)
73,890✔
4437
                                                candidate_item = j;
4438
                                }
4439

4440
                        if (candidate_item && candidate_item->age_set) {
1,784✔
4441
                                i->age = candidate_item->age;
1,274✔
4442
                                i->age_set = true;
1,274✔
4443
                        }
4444
                }
4445

4446
        return r;
15,200✔
4447
}
4448

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

4456
        assert(c);
138✔
4457

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

4464
        return 0;
4465
}
4466

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

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

4477
        assert(c);
543✔
4478

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

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

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

4495
        return 0;
4496
}
4497

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

4503
        assert(c);
681✔
4504

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

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

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

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

4524
        assert(c);
22,396✔
4525
        assert(a);
22,396✔
4526

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

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

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

4540
                j = ordered_hashmap_get(c->items, prefix);
39,144✔
4541
                if (!j)
39,144✔
4542
                        j = ordered_hashmap_get(c->globs, prefix);
30,489✔
4543
                if (j) {
30,489✔
4544
                        r = set_ensure_put(&j->children, NULL, a);
9,545✔
4545
                        if (r < 0)
9,545✔
4546
                                return log_oom();
×
4547

4548
                        a->parent = j;
9,545✔
4549
                        return 1;
9,545✔
4550
                }
4551
        }
4552

4553
        return 0;
4554
}
4555

4556
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(item_array_hash_ops, char, string_hash_func, string_compare_func,
22,396✔
4557
                                              ItemArray, item_array_free);
4558

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

4576
        r = parse_argv(argc, argv);
681✔
4577
        if (r <= 0)
681✔
4578
                return r;
4579

4580
        log_setup();
681✔
4581

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

4592
        /* Descending down file system trees might take a lot of fds */
4593
        (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE);
681✔
4594

4595
        switch (arg_runtime_scope) {
681✔
4596

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

4603
        case RUNTIME_SCOPE_SYSTEM:
504✔
4604
                config_dirs = strv_new(CONF_PATHS("tmpfiles.d"));
504✔
4605
                if (!config_dirs)
504✔
4606
                        return log_oom();
×
4607
                break;
4608

4609
        default:
×
4610
                assert_not_reached();
×
4611
        }
4612

4613
        if (DEBUG_LOGGING) {
681✔
4614
                _cleanup_free_ char *t = NULL;
569✔
4615

4616
                STRV_FOREACH(i, config_dirs) {
3,199✔
4617
                        _cleanup_free_ char *j = NULL;
2,630✔
4618

4619
                        j = path_join(arg_root, *i);
2,630✔
4620
                        if (!j)
2,630✔
4621
                                return log_oom();
×
4622

4623
                        if (!strextend(&t, "\n\t", j))
2,630✔
4624
                                return log_oom();
×
4625
                }
4626

4627
                log_debug("Looking for configuration files in (higher priority first):%s", t);
569✔
4628
        }
4629

4630
        if (arg_cat_flags != CAT_CONFIG_OFF)
681✔
4631
                return cat_config(config_dirs, argv + optind);
×
4632

4633
        if (should_bypass("SYSTEMD_TMPFILES"))
681✔
4634
                return 0;
4635

4636
        umask(0022);
681✔
4637

4638
        r = mac_init();
681✔
4639
        if (r < 0)
681✔
4640
                return r;
4641

4642
#ifndef STANDALONE
4643
        if (arg_image) {
681✔
4644
                assert(!arg_root);
×
4645

4646
                r = mount_image_privately_interactively(
×
4647
                                arg_image,
4648
                                arg_image_policy,
4649
                                DISSECT_IMAGE_GENERIC_ROOT |
4650
                                DISSECT_IMAGE_REQUIRE_ROOT |
4651
                                DISSECT_IMAGE_VALIDATE_OS |
4652
                                DISSECT_IMAGE_RELAX_VAR_CHECK |
4653
                                DISSECT_IMAGE_FSCK |
4654
                                DISSECT_IMAGE_GROWFS |
4655
                                DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
4656
                                &mounted_dir,
4657
                                /* ret_dir_fd= */ NULL,
4658
                                &loop_device);
4659
                if (r < 0)
×
4660
                        return r;
4661

4662
                arg_root = strdup(mounted_dir);
×
4663
                if (!arg_root)
×
4664
                        return log_oom();
×
4665
        }
4666
#else
4667
        assert(!arg_image);
4668
#endif
4669

4670
        c.items = ordered_hashmap_new(&item_array_hash_ops);
681✔
4671
        c.globs = ordered_hashmap_new(&item_array_hash_ops);
681✔
4672
        if (!c.items || !c.globs)
681✔
4673
                return log_oom();
×
4674

4675
        /* If command line arguments are specified along with --replace=, read all configuration files and
4676
         * insert the positional arguments at the specified place. Otherwise, if command line arguments are
4677
         * specified, execute just them, and finally, without --replace= or any positional arguments, just
4678
         * read configuration and execute it. */
4679
        if (arg_replace || optind >= argc)
681✔
4680
                r = read_config_files(&c, config_dirs, argv + optind, &invalid_config);
543✔
4681
        else
4682
                r = parse_arguments(&c, config_dirs, argv + optind, &invalid_config);
138✔
4683
        if (r < 0)
681✔
4684
                return r;
4685

4686
        r = read_credential_lines(&c, &invalid_config);
681✔
4687
        if (r < 0)
681✔
4688
                return r;
4689

4690
        /* Let's now link up all child/parent relationships */
4691
        ORDERED_HASHMAP_FOREACH(a, c.items) {
16,554✔
4692
                r = link_parent(&c, a);
15,873✔
4693
                if (r < 0)
15,873✔
4694
                        return r;
×
4695
        }
4696
        ORDERED_HASHMAP_FOREACH(a, c.globs) {
7,204✔
4697
                r = link_parent(&c, a);
6,523✔
4698
                if (r < 0)
6,523✔
4699
                        return r;
×
4700
        }
4701

4702
        /* If multiple operations are requested, let's first run the remove/clean operations, and only then
4703
         * the create operations. i.e. that we first clean out the platform we then build on. */
4704
        for (phase = 0; phase < _PHASE_MAX; phase++) {
2,724✔
4705
                OperationMask op;
2,043✔
4706

4707
                if (phase == PHASE_PURGE)
2,043✔
4708
                        op = arg_operation & OPERATION_PURGE;
681✔
4709
                else if (phase == PHASE_REMOVE_AND_CLEAN)
1,362✔
4710
                        op = arg_operation & (OPERATION_REMOVE|OPERATION_CLEAN);
681✔
4711
                else if (phase == PHASE_CREATE)
681✔
4712
                        op = arg_operation & OPERATION_CREATE;
681✔
4713
                else
4714
                        assert_not_reached();
×
4715

4716
                if (op == 0) /* Nothing requested in this phase */
2,043✔
4717
                        continue;
1,064✔
4718

4719
                /* The non-globbing ones usually create things, hence we apply them first */
4720
                ORDERED_HASHMAP_FOREACH(a, c.items)
29,501✔
4721
                        RET_GATHER(r, process_item_array(&c, a, op));
28,522✔
4722

4723
                /* The globbing ones usually alter things, hence we apply them second. */
4724
                ORDERED_HASHMAP_FOREACH(a, c.globs)
11,568✔
4725
                        RET_GATHER(r, process_item_array(&c, a, op));
10,589✔
4726
        }
4727

4728
        if (ERRNO_IS_NEG_RESOURCE(r))
1,362✔
4729
                return r;
4730
        if (invalid_config)
681✔
4731
                return EX_DATAERR;
4732
        if (r < 0)
671✔
4733
                return EX_CANTCREAT;
15✔
4734
        return 0;
4735
}
4736

4737
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
1,362✔
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