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

systemd / systemd / 25529530918

07 May 2026 07:03PM UTC coverage: 72.624% (-0.03%) from 72.658%
25529530918

push

github

web-flow
Convert resolvectl to option and verb macros (#41978)

This was one quite involved because some preparatory work was needed and
there are three parsers in the same binary.

636 of 1004 new or added lines in 2 files covered. (63.35%)

3865 existing lines in 59 files now uncovered.

326571 of 449671 relevant lines covered (72.62%)

1217500.19 hits per line

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

81.09
/src/basic/fs-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <linux/falloc.h>
4
#include <stdlib.h>
5
#include <sys/file.h>
6
#include <sys/mount.h>
7
#include <unistd.h>
8

9
#include "alloc-util.h"
10
#include "btrfs.h"
11
#include "chattr-util.h"
12
#include "dirent-util.h"
13
#include "errno-util.h"
14
#include "fd-util.h"
15
#include "fs-util.h"
16
#include "hostname-util.h"
17
#include "label.h"
18
#include "lock-util.h"
19
#include "log.h"
20
#include "mkdir.h"
21
#include "path-util.h"
22
#include "process-util.h"
23
#include "random-util.h"
24
#include "ratelimit.h"
25
#include "stat-util.h"
26
#include "string-util.h"
27
#include "strv.h"
28
#include "time-util.h"
29
#include "tmpfile-util.h"
30
#include "umask-util.h"
31

32
int rmdir_parents(const char *path, const char *stop) {
63,481✔
33
        char *p;
63,481✔
34
        int r;
63,481✔
35

36
        assert(path);
63,481✔
37
        assert(stop);
63,481✔
38

39
        if (!path_is_safe(path))
63,481✔
40
                return -EINVAL;
41

42
        if (!path_is_safe(stop))
63,480✔
43
                return -EINVAL;
44

45
        p = strdupa_safe(path);
63,479✔
46

47
        for (;;) {
3,236✔
48
                char *slash = NULL;
66,715✔
49

50
                /* skip the last component. */
51
                r = path_find_last_component(p, /* accept_dot_dot= */ false, (const char **) &slash, NULL);
66,715✔
52
                if (r <= 0)
66,715✔
53
                        return r;
63,479✔
54
                if (slash == p)
66,715✔
55
                        return 0;
56

57
                assert(*slash == '/');
66,715✔
58
                *slash = '\0';
66,715✔
59

60
                if (path_startswith_full(stop, p, PATH_STARTSWITH_REFUSE_DOT_DOT))
66,715✔
61
                        return 0;
62

63
                if (rmdir(p) < 0 && errno != ENOENT)
66,492✔
64
                        return -errno;
63,256✔
65
        }
66
}
67

68
int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
173✔
69
        int r;
173✔
70

71
        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
173✔
72
        assert(oldpath);
173✔
73
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
173✔
74
        assert(newpath);
173✔
75

76
        /* Try the ideal approach first */
77
        if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) >= 0)
173✔
78
                return 0;
79

80
        /* renameat2() exists since Linux 3.15, btrfs and FAT added support for it later. If it is not implemented,
81
         * fall back to a different method. */
82
        if (!ERRNO_IS_NOT_SUPPORTED(errno) && errno != EINVAL)
93✔
83
                return -errno;
93✔
84

85
        /* Let's try to use linkat()+unlinkat() as fallback. This doesn't work on directories and on some file systems
86
         * that do not support hard links (such as FAT, most prominently), but for files it's pretty close to what we
87
         * want — though not atomic (i.e. for a short period both the new and the old filename will exist). */
88
        if (linkat(olddirfd, oldpath, newdirfd, newpath, 0) >= 0) {
×
89

90
                r = RET_NERRNO(unlinkat(olddirfd, oldpath, 0));
×
91
                if (r < 0) {
×
92
                        (void) unlinkat(newdirfd, newpath, 0);
×
93
                        return r;
×
94
                }
95

96
                return 0;
97
        }
98

99
        if (!ERRNO_IS_NOT_SUPPORTED(errno) && !IN_SET(errno, EINVAL, EPERM)) /* FAT returns EPERM on link()… */
×
100
                return -errno;
×
101

102
        /* OK, neither RENAME_NOREPLACE nor linkat()+unlinkat() worked. Let's then fall back to the racy TOCTOU
103
         * vulnerable accessat(F_OK) check followed by classic, replacing renameat(), we have nothing better. */
104

105
        if (faccessat(newdirfd, newpath, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
×
106
                return -EEXIST;
107
        if (errno != ENOENT)
×
108
                return -errno;
×
109

110
        return RET_NERRNO(renameat(olddirfd, oldpath, newdirfd, newpath));
×
111
}
112

113
int readlinkat_malloc(int fd, const char *p, char **ret) {
5,911,028✔
114
        size_t l = PATH_MAX;
5,911,028✔
115

116
        assert(fd >= 0 || fd == AT_FDCWD);
5,911,028✔
117

118
        if (fd < 0 && isempty(p))
5,911,028✔
119
                return -EISDIR; /* In this case, the fd points to the current working directory, and is
120
                                 * definitely not a symlink. Let's return earlier. */
121

122
        for (;;) {
5,911,028✔
123
                _cleanup_free_ char *c = NULL;
5,911,028✔
124
                ssize_t n;
5,911,028✔
125

126
                c = new(char, l+1);
5,911,028✔
127
                if (!c)
5,911,028✔
128
                        return -ENOMEM;
129

130
                n = readlinkat(fd, strempty(p), c, l);
5,911,030✔
131
                if (n < 0)
5,911,028✔
132
                        return -errno;
401,777✔
133

134
                if ((size_t) n < l) {
5,509,251✔
135
                        c[n] = 0;
5,509,251✔
136

137
                        if (ret)
5,509,251✔
138
                                *ret = TAKE_PTR(c);
5,509,251✔
139

140
                        return 0;
141
                }
142

143
                if (l > (SSIZE_MAX-1)/2) /* readlinkat() returns an ssize_t, and we want an extra byte for a
×
144
                                          * trailing NUL, hence do an overflow check relative to SSIZE_MAX-1
145
                                          * here */
146
                        return -EFBIG;
147

148
                l *= 2;
×
149
        }
150
}
151

152
int readlink_value(const char *p, char **ret) {
649,697✔
153
        _cleanup_free_ char *link = NULL, *name = NULL;
649,697✔
154
        int r;
649,697✔
155

156
        assert(p);
649,697✔
157
        assert(ret);
649,697✔
158

159
        r = readlink_malloc(p, &link);
649,697✔
160
        if (r < 0)
649,697✔
161
                return r;
162

163
        r = path_extract_filename(link, &name);
379,218✔
164
        if (r < 0)
379,218✔
165
                return r;
166
        if (r == O_DIRECTORY)
379,218✔
167
                return -EINVAL;
168

169
        *ret = TAKE_PTR(name);
379,218✔
170
        return 0;
379,218✔
171
}
172

173
int readlink_and_make_absolute(const char *p, char **ret) {
3,182✔
174
        _cleanup_free_ char *target = NULL;
3,182✔
175
        int r;
3,182✔
176

177
        assert(p);
3,182✔
178
        assert(ret);
3,182✔
179

180
        r = readlink_malloc(p, &target);
3,182✔
181
        if (r < 0)
3,182✔
182
                return r;
183

184
        return file_in_same_dir(p, target, ret);
27✔
185
}
186

187
int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
23,142✔
188
        _cleanup_close_ int fd = -EBADF;
23,142✔
189

190
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
23,142✔
191

192
        if (path) {
23,142✔
193
                /* Let's acquire an O_PATH fd, as precaution to change mode/owner on the same file */
194
                fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
23,142✔
195
                if (fd < 0)
23,142✔
196
                        return -errno;
×
197
                dir_fd = fd;
198

199
        } else if (dir_fd == AT_FDCWD) {
×
200
                /* Let's acquire an O_PATH fd of the current directory */
201
                fd = openat(dir_fd, ".", O_PATH|O_CLOEXEC|O_NOFOLLOW|O_DIRECTORY);
×
202
                if (fd < 0)
×
203
                        return -errno;
×
204
                dir_fd = fd;
205
        }
206

207
        return fchmod_and_chown(dir_fd, mode, uid, gid);
23,142✔
208
}
209

210
int fchmod_and_chown_with_fallback(int fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
135,160✔
211
        bool do_chown, do_chmod;
135,160✔
212
        struct stat st;
135,160✔
213
        int r;
135,160✔
214

215
        /* Change ownership and access mode of the specified fd. Tries to do so safely, ensuring that at no
216
         * point in time the access mode is above the old access mode under the old ownership or the new
217
         * access mode under the new ownership. Note: this call tries hard to leave the access mode
218
         * unaffected if the uid/gid is changed, i.e. it undoes implicit suid/sgid dropping the kernel does
219
         * on chown().
220
         *
221
         * This call is happy with O_PATH fds.
222
         *
223
         * If path is given, allow a fallback path which does not use /proc/self/fd/. On any normal system
224
         * /proc will be mounted, but in certain improperly assembled environments it might not be. This is
225
         * less secure (potential TOCTOU), so should only be used after consideration. */
226

227
        if (fstat(fd, &st) < 0)
135,160✔
228
                return -errno;
×
229

230
        do_chown =
270,320✔
231
                (uid != UID_INVALID && st.st_uid != uid) ||
135,160✔
232
                (gid != GID_INVALID && st.st_gid != gid);
33,142✔
233

234
        do_chmod =
270,320✔
235
                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
135,160✔
236
                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
129,861✔
237
                 do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown()
238
                             * modifies the access mode too */
239

240
        if (mode == MODE_INVALID)
135,160✔
241
                mode = st.st_mode; /* If we only shall do a chown(), save original mode, since chown() might break it. */
242
        else if ((mode & S_IFMT) != 0 && ((mode ^ st.st_mode) & S_IFMT) != 0)
94,978✔
243
                return -EINVAL; /* insist on the right file type if it was specified */
244

245
        if (do_chown && do_chmod) {
135,156✔
246
                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
14,143✔
247

248
                if (((minimal ^ st.st_mode) & 07777) != 0) {
14,143✔
249
                        r = fchmod_opath(fd, minimal & 07777);
8✔
250
                        if (r < 0) {
8✔
251
                                if (!path || r != -ENOSYS)
×
252
                                        return r;
253

254
                                /* Fallback path which doesn't use /proc/self/fd/. */
255
                                if (chmod(path, minimal & 07777) < 0)
×
256
                                        return -errno;
×
257
                        }
258
                }
259
        }
260

261
        if (do_chown)
135,156✔
262
                if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0)
14,146✔
263
                        return -errno;
1✔
264

265
        if (do_chmod) {
135,155✔
266
                r = fchmod_opath(fd, mode & 07777);
31,051✔
267
                if (r < 0) {
31,051✔
268
                        if (!path || r != -ENOSYS)
×
269
                                return r;
270

271
                        /* Fallback path which doesn't use /proc/self/fd/. */
272
                        if (chmod(path, mode & 07777) < 0)
×
273
                                return -errno;
×
274
                }
275
        }
276

277
        return do_chown || do_chmod;
135,155✔
278
}
279

280
int fchmod_umask(int fd, mode_t m) {
3,379✔
281
        _cleanup_umask_ mode_t u = umask(0777);
3,379✔
282

283
        return RET_NERRNO(fchmod(fd, m & (~u)));
3,379✔
284
}
285

286
int fchmod_opath(int fd, mode_t m) {
34,056✔
287
        /* This function operates also on fd that might have been opened with
288
         * O_PATH. The tool set we have is non-intuitive:
289
         * - fchmod(2) only operates on open files (i. e., fds with an open file description);
290
         * - fchmodat(2) does not have a flag arg like fchownat(2) does, so no way to pass AT_EMPTY_PATH;
291
         *   + it should not be confused with the libc fchmodat(3) interface, which adds 4th flag argument,
292
         *     and supports AT_EMPTY_PATH since v2.39 (previously only supported AT_SYMLINK_NOFOLLOW). So if
293
         *     the kernel has fchmodat2(2), since v2.39 glibc will call into it directly. If the kernel
294
         *     doesn't, or glibc is older than v2.39, glibc's internal fallback will return EINVAL if
295
         *     AT_EMPTY_PATH is passed.
296
         * - fchmodat2(2) supports all the AT_* flags, but is still very recent.
297
         *
298
         * We try to use fchmodat(3) first, and on EINVAL fall back to fchmodat2(), and, if that is also not
299
         * supported, resort to the /proc/self/fd dance. */
300

301
        assert(fd >= 0);
34,056✔
302

303
        if (fchmodat(fd, "", m, AT_EMPTY_PATH) >= 0)
34,056✔
304
                return 0;
305
        if (errno == EINVAL && fchmodat2(fd, "", m, AT_EMPTY_PATH) >= 0) /* glibc too old? */
2✔
306
                return 0;
307
        if (!IN_SET(errno, ENOSYS, EPERM)) /* Some container managers block unknown syscalls with EPERM */
2✔
308
                return -errno;
2✔
309

310
        if (chmod(FORMAT_PROC_FD_PATH(fd), m) < 0) {
×
311
                if (errno != ENOENT)
×
312
                        return -errno;
×
313

314
                return proc_fd_enoent_errno();
×
315
        }
316

317
        return 0;
×
318
}
319

320
int futimens_opath(int fd, const struct timespec ts[2]) {
139,387✔
321
        /* Similar to fchmod_opath() but for futimens() */
322

323
        assert(fd >= 0);
139,387✔
324

325
        return RET_NERRNO(utimensat(fd, "", ts, AT_EMPTY_PATH));
139,387✔
326
}
327

328
int stat_warn_permissions(const char *path, const struct stat *st) {
134,565✔
329
        assert(path);
134,565✔
330
        assert(st);
134,565✔
331

332
        /* Don't complain if we are reading something that is not a file, for example /dev/null */
333
        if (!S_ISREG(st->st_mode))
134,565✔
334
                return 0;
335

336
        if (st->st_mode & 0111)
134,565✔
337
                log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path);
×
338

339
        if (st->st_mode & 0002)
134,565✔
340
                log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
×
341

342
        if (getpid_cached() == 1 && (st->st_mode & 0044) != 0044)
134,565✔
343
                log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path);
51✔
344

345
        return 0;
346
}
347

348
int fd_warn_permissions(const char *path, int fd) {
×
349
        struct stat st;
×
350

351
        assert(path);
×
352
        assert(fd >= 0);
×
353

354
        if (fstat(fd, &st) < 0)
×
355
                return -errno;
×
356

357
        return stat_warn_permissions(path, &st);
×
358
}
359

360
int access_nofollow(const char *path, int mode) {
28,608✔
361
        return RET_NERRNO(faccessat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW));
28,608✔
362
}
363

364
int access_fd(int fd, int mode) {
28,797✔
365
        /* Like access() but operates on an already open fd */
366

367
        if (fd == AT_FDCWD)
28,797✔
368
                return RET_NERRNO(access(".", mode));
×
369
        if (fd == XAT_FDROOT)
28,797✔
370
                return RET_NERRNO(access("/", mode));
1✔
371

372
        assert(fd >= 0);
28,796✔
373

374
        return RET_NERRNO(faccessat(fd, "", mode, AT_EMPTY_PATH));
28,796✔
375
}
376

377
int touch_fd(int fd, usec_t stamp) {
78,395✔
378
        assert(fd >= 0);
78,395✔
379

380
        if (stamp == USEC_INFINITY)
78,395✔
381
                return futimens_opath(fd, /* ts= */ NULL);
77,687✔
382

383
        struct timespec ts[2];
708✔
384
        timespec_store(ts + 0, stamp);
708✔
385
        ts[1] = ts[0];
708✔
386
        return futimens_opath(fd, ts);
708✔
387
}
388

389
int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
78,399✔
390
        _cleanup_close_ int fd = -EBADF;
78,399✔
391
        int ret;
78,399✔
392

393
        assert(path);
78,399✔
394

395
        /* Note that touch_file() does not follow symlinks: if invoked on an existing symlink, then it is the symlink
396
         * itself which is updated, not its target
397
         *
398
         * Returns the first error we encounter, but tries to apply as much as possible. */
399

400
        if (parents)
78,399✔
401
                (void) mkdir_parents(path, 0755);
37,577✔
402

403
        /* Initially, we try to open the node with O_PATH, so that we get a reference to the node. This is useful in
404
         * case the path refers to an existing device or socket node, as we can open it successfully in all cases, and
405
         * won't trigger any driver magic or so. */
406
        fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
78,399✔
407
        if (fd < 0) {
78,399✔
408
                if (errno != ENOENT)
49,571✔
409
                        return -errno;
×
410

411
                /* if the node doesn't exist yet, we create it, but with O_EXCL, so that we only create a regular file
412
                 * here, and nothing else */
413
                fd = open(path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode);
49,571✔
414
                if (fd < 0)
49,571✔
415
                        return -errno;
4✔
416
        }
417

418
        /* Let's make a path from the fd, and operate on that. With this logic, we can adjust the access mode,
419
         * ownership and time of the file node in all cases, even if the fd refers to an O_PATH object — which is
420
         * something fchown(), fchmod(), futimensat() don't allow. */
421
        ret = fchmod_and_chown(fd, mode, uid, gid);
78,395✔
422

423
        return RET_GATHER(ret, touch_fd(fd, stamp));
78,395✔
424
}
425

426
int touch(const char *path) {
40,087✔
427
        return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID);
40,087✔
428
}
429

430
int symlinkat_idempotent(const char *target, int atfd, const char *linkpath, bool make_relative) {
2,290✔
431
        _cleanup_free_ char *relpath = NULL;
2,290✔
432
        int r;
2,290✔
433

434
        assert(target);
2,290✔
435
        assert(linkpath);
2,290✔
436

437
        if (make_relative) {
2,290✔
438
                r = path_make_relative_parent(linkpath, target, &relpath);
188✔
439
                if (r < 0)
188✔
440
                        return r;
441

442
                target = relpath;
188✔
443
        }
444

445
        if (symlinkat(target, atfd, linkpath) < 0) {
2,290✔
446
                _cleanup_free_ char *p = NULL;
124✔
447

448
                if (errno != EEXIST)
124✔
449
                        return -errno;
×
450

451
                r = readlinkat_malloc(atfd, linkpath, &p);
124✔
452
                if (r == -EINVAL) /* Not a symlink? In that case return the original error we encountered: -EEXIST */
124✔
453
                        return -EEXIST;
454
                if (r < 0) /* Any other error? In that case propagate it as is */
124✔
455
                        return r;
456

457
                if (!streq(p, target)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
124✔
458
                        return -EEXIST;
459
        }
460

461
        return 0;
462
}
463

464
int symlinkat_atomic_full(const char *target, int atfd, const char *linkpath, SymlinkFlags flags) {
144,802✔
465
        int r;
144,802✔
466

467
        assert(target);
144,802✔
468
        assert(linkpath);
144,802✔
469

470
        _cleanup_free_ char *relpath = NULL;
144,802✔
471
        if (FLAGS_SET(flags, SYMLINK_MAKE_RELATIVE)) {
144,802✔
472
                r = path_make_relative_parent(linkpath, target, &relpath);
135,926✔
473
                if (r < 0)
135,926✔
474
                        return r;
475

476
                target = relpath;
135,926✔
477
        }
478

479
        _cleanup_free_ char *t = NULL;
144,802✔
480
        r = tempfn_random(linkpath, NULL, &t);
144,802✔
481
        if (r < 0)
144,802✔
482
                return r;
483

484
        bool call_label_ops_post = false;
144,802✔
485
        if (FLAGS_SET(flags, SYMLINK_LABEL)) {
144,802✔
486
                r = label_ops_pre(atfd, linkpath, S_IFLNK);
138,858✔
487
                if (r < 0)
138,858✔
488
                        return r;
489

490
                call_label_ops_post = true;
491
        }
492

493
        r = RET_NERRNO(symlinkat(target, atfd, t));
144,802✔
494
        if (call_label_ops_post)
144,802✔
495
                RET_GATHER(r, label_ops_post(atfd, t, /* created= */ r >= 0));
138,858✔
496
        if (r < 0)
144,802✔
497
                return r;
498

499
        r = RET_NERRNO(renameat(atfd, t, atfd, linkpath));
144,803✔
500
        if (r < 0) {
1✔
501
                (void) unlinkat(atfd, t, 0);
1✔
502
                return r;
1✔
503
        }
504

505
        return 0;
506
}
507

508
int mknodat_atomic(int atfd, const char *path, mode_t mode, dev_t dev) {
×
509
        _cleanup_free_ char *t = NULL;
×
510
        int r;
×
511

512
        assert(path);
×
513

514
        r = tempfn_random(path, NULL, &t);
×
515
        if (r < 0)
×
516
                return r;
517

518
        if (mknodat(atfd, t, mode, dev) < 0)
×
519
                return -errno;
×
520

521
        r = RET_NERRNO(renameat(atfd, t, atfd, path));
×
522
        if (r < 0) {
×
523
                (void) unlinkat(atfd, t, 0);
×
524
                return r;
×
525
        }
526

527
        return 0;
528
}
529

530
int mkfifoat_atomic(int dir_fd, const char *path, mode_t mode) {
1✔
531
        _cleanup_free_ char *t = NULL;
1✔
532
        int r;
1✔
533

534
        assert(path);
1✔
535

536
        /* We're only interested in the (random) filename.  */
537
        r = tempfn_random(path, NULL, &t);
1✔
538
        if (r < 0)
1✔
539
                return r;
540

541
        if (mkfifoat(dir_fd, t, mode) < 0)
1✔
542
                return -errno;
×
543

544
        r = RET_NERRNO(renameat(dir_fd, t, dir_fd, path));
1✔
545
        if (r < 0) {
×
546
                (void) unlinkat(dir_fd, t, 0);
×
547
                return r;
×
548
        }
549

550
        return 0;
551
}
552

553
int get_files_in_directory(const char *path, char ***ret_list) {
469✔
554
        _cleanup_strv_free_ char **l = NULL;
×
555
        _cleanup_closedir_ DIR *d = NULL;
469✔
556
        size_t n = 0;
469✔
557

558
        assert(path);
469✔
559

560
        /* Returns all files in a directory in *list, and the number
561
         * of files as return value. If list is NULL returns only the
562
         * number. */
563

564
        d = opendir(path);
469✔
565
        if (!d)
469✔
566
                return -errno;
2✔
567

568
        FOREACH_DIRENT_ALL(de, d, return -errno) {
1,942✔
569
                if (!dirent_is_file(de))
1,475✔
570
                        continue;
985✔
571

572
                if (ret_list) {
490✔
573
                        /* one extra slot is needed for the terminating NULL */
574
                        if (!GREEDY_REALLOC(l, n + 2))
476✔
575
                                return -ENOMEM;
576

577
                        l[n] = strdup(de->d_name);
476✔
578
                        if (!l[n])
476✔
579
                                return -ENOMEM;
580

581
                        l[++n] = NULL;
476✔
582
                } else
583
                        n++;
14✔
584
        }
585

586
        if (ret_list)
467✔
587
                *ret_list = TAKE_PTR(l);
464✔
588

589
        return n;
467✔
590
}
591

592
static int getenv_tmp_dir(const char **ret_path) {
2,435✔
593
        int r, ret = 0;
2,435✔
594

595
        assert(ret_path);
2,435✔
596

597
        /* We use the same order of environment variables python uses in tempfile.gettempdir():
598
         * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */
599
        FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP") {
9,737✔
600
                const char *e;
7,303✔
601

602
                e = secure_getenv(n);
7,303✔
603
                if (!e)
7,303✔
604
                        continue;
7,301✔
605
                if (!path_is_absolute(e)) {
2✔
606
                        r = -ENOTDIR;
×
607
                        goto next;
×
608
                }
609
                if (!path_is_normalized(e)) {
2✔
610
                        r = -EPERM;
×
611
                        goto next;
×
612
                }
613

614
                r = is_dir(e, true);
2✔
615
                if (r < 0)
2✔
616
                        goto next;
1✔
617
                if (r == 0) {
1✔
618
                        r = -ENOTDIR;
×
619
                        goto next;
×
620
                }
621

622
                *ret_path = e;
1✔
623
                return 1;
1✔
624

625
        next:
1✔
626
                /* Remember first error, to make this more debuggable */
627
                if (ret >= 0)
1✔
628
                        ret = r;
1✔
629
        }
630

631
        if (ret < 0)
2,434✔
632
                return ret;
633

634
        *ret_path = NULL;
2,433✔
635
        return ret;
2,433✔
636
}
637

638
static int tmp_dir_internal(const char *def, const char **ret) {
2,435✔
639
        const char *e;
2,435✔
640
        int r, k;
2,435✔
641

642
        assert(def);
2,435✔
643
        assert(ret);
2,435✔
644

645
        r = getenv_tmp_dir(&e);
2,435✔
646
        if (r > 0) {
2,435✔
647
                *ret = e;
1✔
648
                return 0;
1✔
649
        }
650

651
        k = is_dir(def, /* follow= */ true);
2,434✔
652
        if (k == 0)
2,434✔
653
                k = -ENOTDIR;
654
        if (k < 0)
2,434✔
655
                return RET_GATHER(r, k);
×
656

657
        *ret = def;
2,434✔
658
        return 0;
2,434✔
659
}
660

661
int var_tmp_dir(const char **ret) {
2,268✔
662
        assert(ret);
2,268✔
663

664
        /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus
665
         * even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is
666
         * returned preferably however. Note that both this function and tmp_dir() below are affected by $TMPDIR,
667
         * making it a variable that overrides all temporary file storage locations. */
668

669
        return tmp_dir_internal("/var/tmp", ret);
2,268✔
670
}
671

672
int tmp_dir(const char **ret) {
167✔
673
        assert(ret);
167✔
674

675
        /* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually
676
         * backed by an in-memory file system: /tmp. */
677

678
        return tmp_dir_internal("/tmp", ret);
167✔
679
}
680

681
int unlink_or_warn(const char *filename) {
193✔
682
        assert(filename);
193✔
683

684
        if (unlink(filename) < 0 && errno != ENOENT)
193✔
685
                /* If the file doesn't exist and the fs simply was read-only (in which
686
                 * case unlink() returns EROFS even if the file doesn't exist), don't
687
                 * complain */
688
                if (errno != EROFS || access(filename, F_OK) >= 0)
×
689
                        return log_error_errno(errno, "Failed to remove \"%s\": %m", filename);
×
690

691
        return 0;
692
}
693

694
char *rmdir_and_free(char *p) {
224,898✔
695
        PROTECT_ERRNO;
224,898✔
696

697
        if (!p)
224,898✔
698
                return NULL;
699

700
        (void) rmdir(p);
224,898✔
701
        return mfree(p);
224,898✔
702
}
703

704
char* unlink_and_free(char *p) {
7,227✔
705
        PROTECT_ERRNO;
7,227✔
706

707
        if (!p)
7,227✔
708
                return NULL;
709

710
        (void) unlink(p);
5,892✔
711
        return mfree(p);
5,892✔
712
}
713

714
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
83✔
715
        _cleanup_close_ int truncate_fd = -EBADF;
83✔
716
        struct stat st;
83✔
717
        off_t l, bs;
83✔
718

719
        assert(fd >= 0 || fd == AT_FDCWD);
83✔
720
        assert(name);
83✔
721
        assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0);
83✔
722

723
        /* Operates like unlinkat() but also deallocates the file contents if it is a regular file and there's no other
724
         * link to it. This is useful to ensure that other processes that might have the file open for reading won't be
725
         * able to keep the data pinned on disk forever. This call is particular useful whenever we execute clean-up
726
         * jobs ("vacuuming"), where we want to make sure the data is really gone and the disk space released and
727
         * returned to the free pool.
728
         *
729
         * Deallocation is preferably done by FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE (👊) if supported, which means
730
         * the file won't change size. That's a good thing since we shouldn't needlessly trigger SIGBUS in other
731
         * programs that have mmap()ed the file. (The assumption here is that changing file contents to all zeroes
732
         * underneath those programs is the better choice than simply triggering SIGBUS in them which truncation does.)
733
         * However if hole punching is not implemented in the kernel or file system we'll fall back to normal file
734
         * truncation (🔪), as our goal of deallocating the data space trumps our goal of being nice to readers (💐).
735
         *
736
         * Note that we attempt deallocation, but failure to succeed with that is not considered fatal, as long as the
737
         * primary job – to delete the file – is accomplished. */
738

739
        if (!FLAGS_SET(flags, UNLINK_REMOVEDIR)) {
83✔
740
                truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
83✔
741
                if (truncate_fd < 0) {
83✔
742

743
                        /* If this failed because the file doesn't exist propagate the error right-away. Also,
744
                         * AT_REMOVEDIR wasn't set, and we tried to open the file for writing, which means EISDIR is
745
                         * returned when this is a directory but we are not supposed to delete those, hence propagate
746
                         * the error right-away too. */
747
                        if (IN_SET(errno, ENOENT, EISDIR))
×
748
                                return -errno;
×
749

750
                        if (errno != ELOOP) /* don't complain if this is a symlink */
×
751
                                log_debug_errno(errno, "Failed to open file '%s' for deallocation, ignoring: %m", name);
×
752
                }
753
        }
754

755
        if (unlinkat(fd, name, FLAGS_SET(flags, UNLINK_REMOVEDIR) ? AT_REMOVEDIR : 0) < 0)
83✔
756
                return -errno;
×
757

758
        if (truncate_fd < 0) /* Don't have a file handle, can't do more ☹️ */
83✔
759
                return 0;
760

761
        if (fstat(truncate_fd, &st) < 0) {
83✔
762
                log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
763
                return 0;
764
        }
765

766
        if (!S_ISREG(st.st_mode))
83✔
767
                return 0;
768

769
        if (FLAGS_SET(flags, UNLINK_ERASE) && st.st_size > 0 && st.st_nlink == 0) {
83✔
770
                uint64_t left = st.st_size;
3✔
771
                char buffer[64 * 1024];
3✔
772

773
                /* If erasing is requested, let's overwrite the file with random data once before deleting
774
                 * it. This isn't going to give you shred(1) semantics, but hopefully should be good enough
775
                 * for stuff backed by tmpfs at least.
776
                 *
777
                 * Note that we only erase like this if the link count of the file is zero. If it is higher it
778
                 * is still linked by someone else and we'll leave it to them to remove it securely
779
                 * eventually! */
780

781
                random_bytes(buffer, sizeof(buffer));
3✔
782

783
                while (left > 0) {
6✔
784
                        ssize_t n;
3✔
785

786
                        n = write(truncate_fd, buffer, MIN(sizeof(buffer), left));
3✔
787
                        if (n < 0) {
3✔
788
                                log_debug_errno(errno, "Failed to erase data in file '%s', ignoring.", name);
×
789
                                break;
790
                        }
791

792
                        assert(left >= (size_t) n);
3✔
793
                        left -= n;
3✔
794
                }
795

796
                /* Let's refresh metadata */
797
                if (fstat(truncate_fd, &st) < 0) {
3✔
798
                        log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
799
                        return 0;
×
800
                }
801
        }
802

803
        /* Don't deallocate if there's nothing to deallocate or if the file is linked elsewhere */
804
        if (st.st_blocks == 0 || st.st_nlink > 0)
83✔
805
                return 0;
806

807
        /* If this is a regular file, it actually took up space on disk and there are no other links it's time to
808
         * punch-hole/truncate this to release the disk space. */
809

810
        bs = MAX(st.st_blksize, 512);
83✔
811
        l = ROUND_UP(st.st_size, bs); /* Round up to next block size */
83✔
812

813
        if (fallocate(truncate_fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, 0, l) >= 0)
83✔
814
                return 0; /* Successfully punched a hole! 😊 */
815

816
        /* Fall back to truncation */
817
        if (ftruncate(truncate_fd, 0) < 0) {
×
818
                log_debug_errno(errno, "Failed to truncate file to 0, ignoring: %m");
83✔
819
                return 0;
820
        }
821

822
        return 0;
823
}
824

825
int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
1,817,643✔
826
        _cleanup_free_ char *parent = NULL;
1,817,643✔
827
        int r;
1,817,643✔
828

829
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,817,643✔
830
        assert(path);
1,817,643✔
831

832
        r = path_extract_directory(path, &parent);
1,817,643✔
833
        if (r == -EDESTADDRREQ) {
1,817,643✔
834
                parent = strdup(".");
15,512✔
835
                if (!parent)
15,512✔
836
                        return -ENOMEM;
837
        } else if (r == -EADDRNOTAVAIL) {
1,802,131✔
838
                parent = strdup(path);
×
839
                if (!parent)
×
840
                        return -ENOMEM;
841
        } else if (r < 0)
1,802,131✔
842
                return r;
843

844
        /* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
845
         * O_TMPFILE file, because in that case we are actually create a regular file below the parent directory. */
846

847
        if (!FLAGS_SET(flags, O_TMPFILE))
1,817,643✔
848
                flags |= O_DIRECTORY;
1,793,330✔
849

850
        return RET_NERRNO(openat(dir_fd, parent, flags, mode));
1,817,754✔
851
}
852

853
int conservative_renameat(
40,545✔
854
                int olddirfd, const char *oldpath,
855
                int newdirfd, const char *newpath) {
856

857
        _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF;
40,545✔
858
        struct stat old_stat, new_stat;
40,545✔
859

860
        /* Renames the old path to the new path, much like renameat() — except if both are regular files and
861
         * have the exact same contents and basic file attributes already. In that case remove the new file
862
         * instead. This call is useful for reducing inotify wakeups on files that are updated but don't
863
         * actually change. This function is written in a style that we rather rename too often than suppress
864
         * too much. I.e. whenever we are in doubt, we rather rename than fail. After all reducing inotify
865
         * events is an optimization only, not more. */
866

867
        old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
40,545✔
868
        if (old_fd < 0)
40,545✔
869
                goto do_rename;
×
870

871
        new_fd = openat(newdirfd, newpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
40,545✔
872
        if (new_fd < 0)
40,545✔
873
                goto do_rename;
3,543✔
874

875
        if (fstat(old_fd, &old_stat) < 0)
37,002✔
876
                goto do_rename;
×
877

878
        if (!S_ISREG(old_stat.st_mode))
37,002✔
879
                goto do_rename;
×
880

881
        if (fstat(new_fd, &new_stat) < 0)
37,002✔
882
                goto do_rename;
×
883

884
        if (stat_inode_same(&new_stat, &old_stat))
37,002✔
885
                goto is_same;
1✔
886

887
        if (old_stat.st_mode != new_stat.st_mode ||
37,001✔
888
            old_stat.st_size != new_stat.st_size ||
37,001✔
889
            old_stat.st_uid != new_stat.st_uid ||
25,875✔
890
            old_stat.st_gid != new_stat.st_gid)
25,875✔
891
                goto do_rename;
11,126✔
892

893
        for (;;) {
3✔
894
                uint8_t buf1[16*1024];
25,878✔
895
                uint8_t buf2[sizeof(buf1)];
25,878✔
896
                ssize_t l1, l2;
25,878✔
897

898
                l1 = read(old_fd, buf1, sizeof(buf1));
25,878✔
899
                if (l1 < 0)
25,878✔
900
                        goto do_rename;
316✔
901

902
                if (l1 == sizeof(buf1))
25,878✔
903
                        /* Read the full block, hence read a full block in the other file too */
904

905
                        l2 = read(new_fd, buf2, l1);
4✔
906
                else {
907
                        assert((size_t) l1 < sizeof(buf1));
25,874✔
908

909
                        /* Short read. This hence was the last block in the first file, and then came
910
                         * EOF. Read one byte more in the second file, so that we can verify we hit EOF there
911
                         * too. */
912

913
                        assert((size_t) (l1 + 1) <= sizeof(buf2));
25,874✔
914
                        l2 = read(new_fd, buf2, l1 + 1);
25,874✔
915
                }
916
                if (l2 != l1)
25,878✔
917
                        goto do_rename;
×
918

919
                if (memcmp(buf1, buf2, l1) != 0)
25,878✔
920
                        goto do_rename;
316✔
921

922
                if ((size_t) l1 < sizeof(buf1)) /* We hit EOF on the first file, and the second file too, hence exit
25,562✔
923
                                                 * now. */
924
                        break;
925
        }
926

927
is_same:
25,560✔
928
        /* Everything matches? Then don't rename, instead remove the source file, and leave the existing
929
         * destination in place */
930

931
        if (unlinkat(olddirfd, oldpath, 0) < 0)
25,560✔
932
                goto do_rename;
×
933

934
        return 0;
935

936
do_rename:
14,985✔
937
        if (renameat(olddirfd, oldpath, newdirfd, newpath) < 0)
14,985✔
938
                return -errno;
×
939

940
        return 1;
941
}
942

943
int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) {
1,762✔
944
        RateLimit rl;
1,762✔
945
        int r;
1,762✔
946

947
        r = posix_fallocate(fd, offset, size); /* returns positive errnos on error */
1,762✔
948
        if (r != EINTR)
1,762✔
949
                return -r; /* Let's return negative errnos, like common in our codebase */
1,762✔
950

951
        /* On EINTR try a couple of times more, but protect against busy looping
952
         * (not more than 16 times per 10s) */
953
        rl = (const RateLimit) { 10 * USEC_PER_SEC, 16 };
×
954
        while (ratelimit_below(&rl)) {
×
955
                r = posix_fallocate(fd, offset, size);
×
956
                if (r != EINTR)
×
957
                        return -r;
×
958
        }
959

960
        return -EINTR;
961
}
962

963
int parse_cifs_service(
15✔
964
                const char *s,
965
                char **ret_host,
966
                char **ret_service,
967
                char **ret_path) {
968

969
        _cleanup_free_ char *h = NULL, *ss = NULL, *x = NULL;
15✔
970
        const char *p, *e, *d;
15✔
971
        char delimiter;
15✔
972

973
        /* Parses a CIFS service in form of //host/service/path… and splitting it in three parts. The last
974
         * part is optional, in which case NULL is returned there. To maximize compatibility syntax with
975
         * backslashes instead of slashes is accepted too. */
976

977
        if (!s)
15✔
978
                return -EINVAL;
979

980
        p = startswith(s, "//");
14✔
981
        if (!p) {
14✔
982
                p = startswith(s, "\\\\");
6✔
983
                if (!p)
6✔
984
                        return -EINVAL;
985
        }
986

987
        delimiter = s[0];
11✔
988
        e = strchr(p, delimiter);
11✔
989
        if (!e)
11✔
990
                return -EINVAL;
991

992
        h = strndup(p, e - p);
11✔
993
        if (!h)
11✔
994
                return -ENOMEM;
995

996
        if (!hostname_is_valid(h, 0))
11✔
997
                return -EINVAL;
998

999
        e++;
10✔
1000

1001
        d = strchrnul(e, delimiter);
10✔
1002

1003
        ss = strndup(e, d - e);
10✔
1004
        if (!ss)
10✔
1005
                return -ENOMEM;
1006

1007
        if (!filename_is_valid(ss))
10✔
1008
                return -EINVAL;
1009

1010
        if (!isempty(d)) {
8✔
1011
                x = strdup(skip_leading_chars(d, CHAR_TO_STR(delimiter)));
6✔
1012
                if (!x)
6✔
1013
                        return -EINVAL;
2✔
1014

1015
                /* Make sure to convert Windows-style "\" → Unix-style / */
1016
                for (char *i = x; *i; i++)
33✔
1017
                        if (*i == delimiter)
27✔
1018
                                *i = '/';
3✔
1019

1020
                if (!path_is_valid(x))
6✔
1021
                        return -EINVAL;
1022

1023
                path_simplify(x);
6✔
1024
                if (!path_is_normalized(x))
6✔
1025
                        return -EINVAL;
1026
        }
1027

1028
        if (ret_host)
6✔
1029
                *ret_host = TAKE_PTR(h);
6✔
1030
        if (ret_service)
6✔
1031
                *ret_service = TAKE_PTR(ss);
6✔
1032
        if (ret_path)
6✔
1033
                *ret_path = TAKE_PTR(x);
6✔
1034

1035
        return 0;
1036
}
1037

1038
int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode) {
231,397✔
1039
        _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
231,397✔
1040
        _cleanup_free_ char *fname = NULL, *parent = NULL;
231,397✔
1041
        int r;
231,397✔
1042

1043
        /* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can
1044
         * do. Guarantees that the returned fd refers to a directory. If O_EXCL is specified will fail if the
1045
         * dir already exists. Otherwise will open an existing dir, but only if it is one.  */
1046

1047
        if (flags & ~(O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_EXCL|O_NOATIME|O_NOFOLLOW|O_PATH))
231,397✔
1048
                return -EINVAL;
1049
        if ((flags & O_ACCMODE_STRICT) != O_RDONLY)
231,397✔
1050
                return -EINVAL;
1051

1052
        /* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following
1053
         * flags actually make sense to specify: O_CLOEXEC, O_EXCL, O_NOATIME, O_PATH */
1054

1055
        /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
1056
         * that we can pin it, and operate below it. */
1057
        r = path_extract_directory(path, &parent);
231,397✔
1058
        if (r < 0) {
231,397✔
1059
                if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
8,215✔
1060
                        return r;
1061
        } else {
1062
                r = path_extract_filename(path, &fname);
223,182✔
1063
                if (r < 0)
223,182✔
1064
                        return r;
1065

1066
                parent_fd = openat(dirfd, parent, O_PATH|O_DIRECTORY|O_CLOEXEC);
223,182✔
1067
                if (parent_fd < 0)
223,182✔
1068
                        return -errno;
×
1069

1070
                dirfd = parent_fd;
223,182✔
1071
                path = fname;
223,182✔
1072
        }
1073

1074
        fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode);
231,397✔
1075
        if (IN_SET(fd, -ELOOP, -ENOTDIR))
231,397✔
1076
                return -EEXIST;
1077
        if (fd < 0)
231,396✔
1078
                return fd;
4,618✔
1079

1080
        return TAKE_FD(fd);
1081
}
1082

1083
int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) {
23,862,232✔
1084
        int fd;
23,862,232✔
1085

1086
        /* Just like openat(), but adds one thing: optionally returns whether we created the file anew or if
1087
         * it already existed before. This is only relevant if O_CREAT is set without O_EXCL, and thus will
1088
         * shortcut to openat() otherwise.
1089
         *
1090
         * Note that this routine is a bit more strict with symlinks than regular openat() is. If O_NOFOLLOW
1091
         * is not specified, then we'll follow the symlink when opening an existing file but we will *not*
1092
         * follow it when creating a new one (because that's a terrible UNIX misfeature and generally a
1093
         * security hole). */
1094

1095
        if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) {
23,862,232✔
1096
                fd = openat(dirfd, pathname, flags, mode);
21,836,443✔
1097
                if (fd < 0)
21,836,443✔
1098
                        return -errno;
1,599,884✔
1099

1100
                if (ret_newly_created)
20,236,559✔
1101
                        *ret_newly_created = FLAGS_SET(flags, O_CREAT);
20,236,559✔
1102
                return fd;
1103
        }
1104

1105
        for (unsigned attempts = 7;;) {
1106
                /* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */
1107
                fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode);
2,025,922✔
1108
                if (fd >= 0) {
2,025,922✔
1109
                        if (ret_newly_created)
1,798,949✔
1110
                                *ret_newly_created = false;
1,798,949✔
1111
                        return fd;
1112
                }
1113
                if (errno != ENOENT)
226,973✔
1114
                        return -errno;
×
1115

1116
                /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL/O_NOFOLLOW. */
1117
                fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL | O_NOFOLLOW, mode);
226,973✔
1118
                if (fd >= 0) {
226,973✔
1119
                        if (ret_newly_created)
226,838✔
1120
                                *ret_newly_created = true;
226,837✔
1121
                        return fd;
1122
                }
1123
                if (errno != EEXIST)
135✔
1124
                        return -errno;
×
1125

1126
                /* Hmm, so now we got EEXIST? Then someone might have created the file between the first and
1127
                 * second call to openat(). Let's try again but with a limit so we don't spin forever. */
1128

1129
                if (--attempts == 0) /* Give up eventually, somebody is playing with us */
135✔
1130
                        return -EEXIST;
1131
        }
1132
}
1133

1134
static int openat_with_automount(int dir_fd, const char *path, int open_flags, mode_t mode) {
87,504✔
1135
        /* When XO_TRIGGER_AUTOMOUNT is set we want to trigger automounts on the path. open() with O_PATH
1136
         * does not do that, so we use open_tree() without OPEN_TREE_CLONE which is equivalent to open() with
1137
         * O_PATH except that it does trigger automounts. Some sandboxes reject open_tree() with EPERM or
1138
         * ENOSYS, in which case we fall back to plain openat(): autofs wouldn't work inside a restricted
1139
         * mount namespace anyway. open_tree() only ever returns O_PATH fds, so this helper is for O_PATH
1140
         * acquisition only. */
1141

1142
        static bool can_open_tree = true;
87,504✔
1143

1144
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
87,504✔
1145
        assert(path);
87,504✔
1146
        assert(FLAGS_SET(open_flags, O_PATH));
87,504✔
1147

1148
        if (can_open_tree) {
87,504✔
1149
                int fd = RET_NERRNO(open_tree(dir_fd, path,
87,504✔
1150
                                              OPEN_TREE_CLOEXEC |
87,504✔
1151
                                              (FLAGS_SET(open_flags, O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)));
87,504✔
1152
                if (fd >= 0) {
2,990✔
1153
                        /* open_tree() doesn't honor O_DIRECTORY, so enforce it ourselves to match
1154
                         * the openat() fallback's behavior. */
1155
                        if (FLAGS_SET(open_flags, O_DIRECTORY)) {
84,514✔
1156
                                int q = fd_verify_directory(fd);
11✔
1157
                                if (q < 0) {
11✔
1158
                                        safe_close(fd);
×
UNCOV
1159
                                        return q;
×
1160
                                }
1161
                        }
1162

1163
                        return fd;
1164
                }
1165
                if (fd != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(fd))
2,990✔
1166
                        return fd;
1167

UNCOV
1168
                can_open_tree = false;
×
1169
        }
1170

UNCOV
1171
        return RET_NERRNO(openat(dir_fd, path, open_flags, mode));
×
1172
}
1173

1174
int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
26,305,159✔
1175
        _cleanup_close_ int fd = -EBADF;
26,305,159✔
1176
        bool made_dir = false, made_file = false;
26,305,159✔
1177
        int r;
26,305,159✔
1178

1179
        assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
26,305,159✔
1180

1181
        /* An inode can only be one of a directory, a regular file or a socket at the same time. */
1182
        assert(FLAGS_SET(open_flags, O_DIRECTORY) + FLAGS_SET(xopen_flags, XO_REGULAR) + FLAGS_SET(xopen_flags, XO_SOCKET) <= 1);
26,305,159✔
1183
        /* Sockets cannot be open()ed, only pinned via O_PATH. */
1184
        assert(!FLAGS_SET(xopen_flags, XO_SOCKET) || FLAGS_SET(open_flags, O_PATH));
26,305,159✔
1185
        /* XO_TRIGGER_AUTOMOUNT requires O_PATH and does not support creating inodes. XO_SUBVOLUME
1186
         * requires O_CREAT, and XO_NOCOW needs a writable fd for its chattr ioctl, so neither is
1187
         * compatible with XO_TRIGGER_AUTOMOUNT. */
1188
        assert(!FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) ||
26,305,159✔
1189
               (FLAGS_SET(open_flags, O_PATH) && !FLAGS_SET(open_flags, O_CREAT)));
1190
        assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_SUBVOLUME)));
26,305,159✔
1191
        assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_NOCOW)));
26,305,159✔
1192

1193
        /* Don't specify an access mode if you want auto mode. */
1194
        assert(!FLAGS_SET(xopen_flags, XO_AUTO_RW_RO) || (open_flags & O_ACCMODE_STRICT) == 0);
26,305,159✔
1195

1196
        /* This is like openat(), but has a few tricks up its sleeves, extending behaviour:
1197
         *
1198
         *   • O_DIRECTORY|O_CREAT is supported, which causes a directory to be created, and immediately
1199
         *     opened. When used with the XO_SUBVOLUME flag this will even create a btrfs subvolume.
1200
         *
1201
         *   • If O_CREAT is used with XO_LABEL, any created file will be immediately relabelled.
1202
         *
1203
         *   • If the path is specified NULL or empty, behaves like fd_reopen().
1204
         *
1205
         *   • If XO_NOCOW is specified will turn on the NOCOW btrfs flag on the file, if available.
1206
         *
1207
         *   • if XO_REGULAR is specified will return an error if inode is not a regular file.
1208
         *
1209
         *   • if XO_SOCKET is specified will return an error if inode is not a socket.
1210
         *
1211
         *   • if XO_TRIGGER_AUTOMOUNT is specified O_PATH fds will trigger automounts.
1212
         *
1213
         *   • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files.
1214
         *
1215
         *   • The dir fd can be passed as XAT_FDROOT, in which case any relative paths will be taken relative to the root fs.
1216
         *
1217
         *   • If XO_AUTO_RW_RO is specified and the file cannot be opened in O_RDWR mode due to EACCES/EROFS or similar, retry in O_RDONLY mode.
1218
         */
1219

1220
        if (mode == MODE_INVALID)
26,305,159✔
1221
                mode = (open_flags & O_DIRECTORY) ? 0755 : 0644;
25,412,122✔
1222

1223
        if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) {
26,305,159✔
1224
                if (open_flags & O_DIRECTORY) {
5✔
1225
                        /* Directories can only be opened in read-only mode */
1226
                        xopen_flags &= ~XO_AUTO_RW_RO;
2✔
1227
                        open_flags |= O_RDONLY;
2✔
1228
                } else if (open_flags & O_PATH)
3✔
1229
                        /* O_PATH is incompatible with O_RDONLY/O_RDWR → fail */
1230
                        return -EINVAL;
1231
        }
1232

1233
        if (isempty(path)) {
26,305,159✔
1234
                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
3,833,923✔
1235
                open_flags &= ~O_NOFOLLOW;
3,833,923✔
1236

1237
                if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
3,833,923✔
1238
                        r = fd_verify_regular(dir_fd);
48✔
1239
                        if (r < 0)
48✔
1240
                                return r;
1241
                }
1242

1243
                if (FLAGS_SET(xopen_flags, XO_SOCKET)) {
3,833,923✔
1244
                        r = fd_verify_socket(dir_fd);
3✔
1245
                        if (r < 0)
3✔
1246
                                return r;
1247
                }
1248

1249
                if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) {
3,833,921✔
1250
                        /* First try: in r/w mode */
1251
                        fd = fd_reopen(dir_fd, open_flags|O_RDWR);
1✔
1252
                        if (!ERRNO_IS_NEG_FS_WRITE_REFUSED(fd) && fd != -EISDIR)
2✔
1253
                                return TAKE_FD(fd);
26,305,159✔
1254

1255
                        open_flags |= O_RDONLY;
1256
                }
1257

1258
                return fd_reopen(dir_fd, open_flags);
3,833,920✔
1259
        }
1260

1261
        _cleanup_close_ int _dir_fd = -EBADF;
26,305,159✔
1262
        if (dir_fd == XAT_FDROOT) {
22,471,236✔
1263
                if (path_is_absolute(path))
9,437✔
1264
                        dir_fd = AT_FDCWD;
1265
                else {
1266
                        _dir_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
141✔
1267
                        if (_dir_fd < 0)
141✔
UNCOV
1268
                                return -errno;
×
1269

1270
                        dir_fd = _dir_fd;
1271
                }
1272
        }
1273

1274
        bool call_label_ops_post = false;
22,471,236✔
1275

1276
        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
22,471,236✔
1277
                r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG);
598✔
1278
                if (r < 0)
598✔
1279
                        return r;
1280

1281
                call_label_ops_post = true;
1282
        }
1283

1284
        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
22,471,236✔
1285
                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
269,873✔
1286
                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
5✔
1287
                else
1288
                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
269,868✔
1289
                if (r == -EEXIST) {
172,274✔
1290
                        if (FLAGS_SET(open_flags, O_EXCL))
172,265✔
1291
                                return -EEXIST;
1292
                } else if (r < 0)
97,608✔
1293
                        return r;
1294
                else
1295
                        made_dir = true;
1296

1297
                open_flags &= ~(O_EXCL|O_CREAT);
265,181✔
1298
        }
1299

1300
        if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
22,466,544✔
1301
                /* Guarantee we return a regular fd only, and don't open the file unless we verified it
1302
                 * first */
1303

1304
                if (FLAGS_SET(open_flags, O_PATH)) {
427,907✔
1305
                        fd = FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) ?
2✔
1306
                                openat_with_automount(dir_fd, path, open_flags, mode) :
1✔
1307
                                RET_NERRNO(openat(dir_fd, path, open_flags, mode));
1✔
UNCOV
1308
                        if (fd < 0) {
×
1309
                                r = fd;
×
1310
                                goto error;
×
1311
                        }
1312

1313
                        r = fd_verify_regular(fd);
1✔
1314
                        if (r < 0)
1✔
UNCOV
1315
                                goto error;
×
1316

1317
                } else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) {
427,906✔
1318
                        /* In O_EXCL mode we can just create the thing, everything is dealt with for us */
1319

1320
                        if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) {
3✔
1321
                                fd = RET_NERRNO(openat(dir_fd, path, open_flags|O_RDWR, mode));
1✔
1322
                                if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd))
1✔
1323
                                        open_flags |= O_RDONLY;
1324
                                else if (fd < 0) {
1✔
UNCOV
1325
                                        r = fd;
×
1326
                                        goto error;
×
1327
                                }
1328
                        }
1329

UNCOV
1330
                        if (fd < 0) {
×
1331
                                fd = openat(dir_fd, path, open_flags, mode);
2✔
1332
                                if (fd < 0) {
2✔
1333
                                        r = -errno;
1✔
1334
                                        goto error;
1✔
1335
                                }
1336
                        }
1337

1338
                        made_file = true;
2✔
1339
                } else {
1340
                        /* Otherwise pin the inode first via O_PATH */
1341
                        _cleanup_close_ int inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|(open_flags & O_NOFOLLOW));
427,903✔
1342
                        if (inode_fd < 0) {
427,903✔
1343
                                if (errno != ENOENT || !FLAGS_SET(open_flags, O_CREAT)) {
1,348✔
1344
                                        r = -errno;
1,326✔
1345
                                        goto error;
1,326✔
1346
                                }
1347

1348
                                /* Doesn't exist yet, then try to create it */
1349
                                open_flags |= O_EXCL;
22✔
1350

1351
                                if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) {
22✔
UNCOV
1352
                                        fd = RET_NERRNO(openat(dir_fd, path, open_flags|O_RDWR, mode));
×
1353
                                        if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd))
×
1354
                                                open_flags |= O_RDONLY;
UNCOV
1355
                                        else if (fd < 0) {
×
1356
                                                r = fd;
×
1357
                                                goto error;
×
1358
                                        }
1359
                                }
1360

UNCOV
1361
                                if (fd < 0) {
×
1362
                                        fd = openat(dir_fd, path, open_flags, mode);
22✔
1363
                                        if (fd < 0) {
22✔
UNCOV
1364
                                                r = -errno;
×
1365
                                                goto error;
×
1366
                                        }
1367
                                }
1368

1369
                                made_file = true;
22✔
1370
                        } else {
1371
                                /* OK, we pinned it. Now verify it's actually a regular file, and then reopen it */
1372
                                r = fd_verify_regular(inode_fd);
426,555✔
1373
                                if (r < 0)
426,555✔
1374
                                        goto error;
36✔
1375

1376
                                open_flags &= ~(O_NOFOLLOW|O_CREAT);
426,519✔
1377

1378
                                if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) {
426,519✔
UNCOV
1379
                                        fd = fd_reopen(inode_fd, open_flags|O_RDWR);
×
1380
                                        if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd))
×
1381
                                                open_flags |= O_RDONLY;
UNCOV
1382
                                        else if (fd < 0) {
×
1383
                                                r = fd;
×
1384
                                                goto error;
×
1385
                                        }
1386
                                }
1387

UNCOV
1388
                                if (fd < 0) {
×
1389
                                        fd = fd_reopen(inode_fd, open_flags);
426,519✔
1390
                                        if (fd < 0) {
426,519✔
UNCOV
1391
                                                r = fd;
×
1392
                                                goto error;
×
1393
                                        }
1394
                                }
1395
                        }
1396
                }
1397
        } else if (FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT)) {
22,038,637✔
1398
                fd = openat_with_automount(dir_fd, path, open_flags, mode);
87,504✔
1399
                if (fd < 0) {
87,504✔
1400
                        r = fd;
2,990✔
1401
                        goto error;
2,990✔
1402
                }
1403
        } else {
1404
                /* XO_SOCKET also lands here: it requires O_PATH (see asserts above) so openat() pins
1405
                 * the inode without connecting, and fd_verify_socket() below enforces the type. */
1406
                if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) {
21,951,133✔
1407
                        fd = openat_report_new(dir_fd, path, O_RDWR|open_flags, mode, &made_file);
1✔
1408
                        if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd) || fd == -EISDIR)
2✔
1409
                                open_flags |= O_RDONLY;
1410
                        else if (fd < 0) {
1✔
UNCOV
1411
                                r = fd;
×
1412
                                goto error;
×
1413
                        }
1414
                }
1415

UNCOV
1416
                if (fd < 0) {
×
1417
                        fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
21,951,132✔
1418
                        if (fd < 0) {
21,951,132✔
1419
                                r = fd;
1,598,137✔
1420
                                goto error;
1,598,137✔
1421
                        }
1422
                }
1423
        }
1424

1425
        if (FLAGS_SET(xopen_flags, XO_SOCKET)) {
20,864,054✔
1426
                r = fd_verify_socket(fd);
3✔
1427
                if (r < 0)
3✔
1428
                        goto error;
2✔
1429
        }
1430

1431
        if (call_label_ops_post) {
20,864,052✔
1432
                call_label_ops_post = false;
598✔
1433

1434
                r = label_ops_post(fd, /* path= */ NULL, made_file || made_dir);
598✔
1435
                if (r < 0)
598✔
UNCOV
1436
                        goto error;
×
1437
        }
1438

1439
        if (FLAGS_SET(xopen_flags, XO_NOCOW)) {
20,864,052✔
1440
                r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
101✔
1441
                if (r < 0 && !ERRNO_IS_IOCTL_NOT_SUPPORTED(r))
101✔
UNCOV
1442
                        goto error;
×
1443
        }
1444

1445
        return TAKE_FD(fd);
1446

1447
error:
1,602,492✔
1448
        if (call_label_ops_post)
1,602,492✔
UNCOV
1449
                (void) label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : path, made_dir || made_file);
×
1450

1451
        if (made_dir || made_file)
1,602,492✔
UNCOV
1452
                (void) unlinkat(dir_fd, path, made_dir ? AT_REMOVEDIR : 0);
×
1453

1454
        return r;
1455
}
1456

1457
int xopenat_lock_full(
260,967✔
1458
                int dir_fd,
1459
                const char *path,
1460
                int open_flags,
1461
                XOpenFlags xopen_flags,
1462
                mode_t mode,
1463
                LockType locktype,
1464
                int operation) {
1465

1466
        _cleanup_close_ int fd = -EBADF;
260,967✔
1467
        int r;
260,967✔
1468

1469
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
260,967✔
1470
        assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
260,967✔
1471

1472
        /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
1473
         * the same error here). */
1474
        if (FLAGS_SET(open_flags, O_DIRECTORY) && !IN_SET(locktype, LOCK_BSD, LOCK_NONE))
260,967✔
1475
                return -EBADF;
1476

1477
        for (;;) {
290,174✔
1478
                struct stat st;
275,570✔
1479

1480
                fd = xopenat_full(dir_fd, path, open_flags, xopen_flags, mode);
275,570✔
1481
                if (fd < 0)
275,570✔
1482
                        return fd;
8✔
1483

1484
                r = lock_generic(fd, locktype, operation);
275,570✔
1485
                if (r < 0)
275,570✔
1486
                        return r;
1487

1488
                /* If we acquired the lock, let's check if the file/directory still exists in the file
1489
                 * system. If not, then the previous exclusive owner removed it and then closed it. In such a
1490
                 * case our acquired lock is worthless, hence try again. */
1491

1492
                if (fstat(fd, &st) < 0)
275,562✔
UNCOV
1493
                        return -errno;
×
1494
                if (st.st_nlink > 0)
275,562✔
1495
                        break;
1496

1497
                fd = safe_close(fd);
14,604✔
1498
        }
1499

1500
        return TAKE_FD(fd);
260,958✔
1501
}
1502

1503
int link_fd(int fd, int newdirfd, const char *newpath) {
29,584✔
1504
        int r;
29,584✔
1505

1506
        assert(fd >= 0);
29,584✔
1507
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
29,584✔
1508
        assert(newpath);
29,584✔
1509

1510
        /* Try to link via AT_EMPTY_PATH first. This fails with ENOENT if we don't have CAP_DAC_READ_SEARCH
1511
         * on kernels < 6.10, in which case we'd then resort to /proc/self/fd/ dance.
1512
         *
1513
         * See also: https://github.com/torvalds/linux/commit/42bd2af5950456d46fdaa91c3a8fb02e680f19f5 */
1514
        r = RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
29,584✔
1515
        if (r == -ENOENT) {
5,518✔
UNCOV
1516
                r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
×
1517
                if (r == -ENOENT && proc_mounted() == 0) /* No proc_fd_enoent_errno() here because we don't
×
1518
                                                            know if it's the target path that's missing. */
UNCOV
1519
                        return -ENOSYS;
×
1520
        }
1521

1522
        return r;
1523
}
1524

1525
int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
23,331✔
1526
        _cleanup_close_ int old_fd = -EBADF;
23,331✔
1527
        int r;
23,331✔
1528

1529
        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
23,331✔
1530
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
23,331✔
1531
        assert(!isempty(newpath)); /* source path is optional, but the target path is not */
23,331✔
1532

1533
        /* Like linkat() but replaces the target if needed. Is a NOP if source and target already share the
1534
         * same inode. */
1535

1536
        if (olddirfd == AT_FDCWD && isempty(oldpath)) /* Refuse operating on the cwd (which is a dir, and dirs can't be hardlinked) */
23,331✔
1537
                return -EISDIR;
1538

1539
        if (path_implies_directory(oldpath)) /* Refuse these definite directories early */
23,331✔
1540
                return -EISDIR;
1541

1542
        if (path_implies_directory(newpath))
23,331✔
1543
                return -EISDIR;
1544

1545
        /* First, try to link this directly */
1546
        if (oldpath)
23,331✔
1547
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
23,395✔
1548
        else
1549
                r = link_fd(olddirfd, newdirfd, newpath);
22,941✔
1550
        if (r >= 0)
23,005✔
1551
                return 0;
1552
        if (r != -EEXIST)
5,579✔
1553
                return r;
1554

1555
        old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC);
5,579✔
1556
        if (old_fd < 0)
5,579✔
1557
                return old_fd;
1558

1559
        struct stat old_st;
5,579✔
1560
        if (fstat(old_fd, &old_st) < 0)
5,579✔
UNCOV
1561
                return -errno;
×
1562

1563
        if (S_ISDIR(old_st.st_mode)) /* Don't bother if we are operating on a directory */
5,579✔
1564
                return -EISDIR;
1565

1566
        struct stat new_st;
5,579✔
1567
        if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0)
5,579✔
UNCOV
1568
                return -errno;
×
1569

1570
        if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */
5,579✔
1571
                return -EEXIST;
1572

1573
        if (stat_inode_same(&old_st, &new_st)) /* Already the same inode? Then shortcut this */
5,578✔
1574
                return 0;
1575

1576
        _cleanup_free_ char *tmp_path = NULL;
5,577✔
1577
        r = tempfn_random(newpath, /* extra= */ NULL, &tmp_path);
5,577✔
1578
        if (r < 0)
5,577✔
1579
                return r;
1580

1581
        r = link_fd(old_fd, newdirfd, tmp_path);
5,577✔
1582
        if (r < 0) {
5,577✔
UNCOV
1583
                if (!ERRNO_IS_PRIVILEGE(r))
×
1584
                        return r;
1585

1586
                /* If that didn't work due to permissions then go via the path of the dentry */
UNCOV
1587
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, tmp_path, 0));
×
1588
                if (r < 0)
×
1589
                        return r;
1590
        }
1591

1592
        r = RET_NERRNO(renameat(newdirfd, tmp_path, newdirfd, newpath));
5,577✔
UNCOV
1593
        if (r < 0) {
×
1594
                (void) unlinkat(newdirfd, tmp_path, /* flags= */ 0);
×
1595
                return r;
×
1596
        }
1597

1598
        return 0;
1599
}
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