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

systemd / systemd / 16062852561

03 Jul 2025 10:04PM UTC coverage: 72.193% (+0.1%) from 72.096%
16062852561

push

github

bluca
pcrlock: process components outside of location window properly

So far, when we tried to match a component to eent log entries we
skipped those components if they were outside of our location window.
That however is too aggressive, since it means any components that are
already in the logs, but outside of the location window will be
considered unrecognized in the logs, and thus removed from the PCR
policy.

Change things around: always try to match up all components, regardless
if inside the location window or outside, but then make it non-fatal we
can't find a component outside of the location window.

Fixes: #36079

7 of 9 new or added lines in 1 file covered. (77.78%)

4116 existing lines in 75 files now uncovered.

301219 of 417241 relevant lines covered (72.19%)

730820.5 hits per line

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

81.1
/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 <unistd.h>
7

8
#include "alloc-util.h"
9
#include "btrfs.h"
10
#include "chattr-util.h"
11
#include "dirent-util.h"
12
#include "errno-util.h"
13
#include "fd-util.h"
14
#include "fs-util.h"
15
#include "hostname-util.h"
16
#include "label.h"
17
#include "lock-util.h"
18
#include "log.h"
19
#include "missing_syscall.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) {
92,981✔
33
        char *p;
92,981✔
34
        int r;
92,981✔
35

36
        assert(path);
92,981✔
37
        assert(stop);
92,981✔
38

39
        if (!path_is_safe(path))
92,981✔
40
                return -EINVAL;
41

42
        if (!path_is_safe(stop))
92,980✔
43
                return -EINVAL;
44

45
        p = strdupa_safe(path);
92,979✔
46

47
        for (;;) {
3,509✔
48
                char *slash = NULL;
96,488✔
49

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

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

60
                if (path_startswith_full(stop, p, PATH_STARTSWITH_REFUSE_DOT_DOT))
96,488✔
61
                        return 0;
62

63
                if (rmdir(p) < 0 && errno != ENOENT)
96,352✔
64
                        return -errno;
92,843✔
65
        }
66
}
67

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

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

76
        /* Try the ideal approach first */
77
        if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) >= 0)
96✔
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)
36✔
83
                return -errno;
36✔
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). */
UNCOV
88
        if (linkat(olddirfd, oldpath, newdirfd, newpath, 0) >= 0) {
×
89

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

UNCOV
96
                return 0;
×
97
        }
98

UNCOV
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

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

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

113
int readlinkat_malloc(int fd, const char *p, char **ret) {
3,531,690✔
114
        size_t l = PATH_MAX;
3,531,690✔
115

116
        assert(fd >= 0 || fd == AT_FDCWD);
3,531,690✔
117

118
        if (fd < 0 && isempty(p))
3,531,690✔
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 (;;) {
3,531,690✔
123
                _cleanup_free_ char *c = NULL;
3,531,690✔
124
                ssize_t n;
3,531,690✔
125

126
                c = new(char, l+1);
3,531,690✔
127
                if (!c)
3,531,690✔
128
                        return -ENOMEM;
129

130
                n = readlinkat(fd, strempty(p), c, l);
3,531,692✔
131
                if (n < 0)
3,531,690✔
132
                        return -errno;
454,256✔
133

134
                if ((size_t) n < l) {
3,077,434✔
135
                        c[n] = 0;
3,077,434✔
136

137
                        if (ret)
3,077,434✔
138
                                *ret = TAKE_PTR(c);
3,077,434✔
139

140
                        return 0;
3,077,434✔
141
                }
142

UNCOV
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

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

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

156
        assert(p);
673,227✔
157
        assert(ret);
673,227✔
158

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

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

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

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

177
        assert(p);
4,009✔
178
        assert(ret);
4,009✔
179

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

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

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

190
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
25,130✔
191

192
        if (path) {
25,130✔
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);
25,130✔
195
                if (fd < 0)
25,130✔
UNCOV
196
                        return -errno;
×
197
                dir_fd = fd;
198

UNCOV
199
        } else if (dir_fd == AT_FDCWD) {
×
200
                /* Let's acquire an O_PATH fd of the current directory */
UNCOV
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);
25,130✔
208
}
209

210
int fchmod_and_chown_with_fallback(int fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
113,296✔
211
        bool do_chown, do_chmod;
113,296✔
212
        struct stat st;
113,296✔
213
        int r;
113,296✔
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)
113,296✔
UNCOV
228
                return -errno;
×
229

230
        do_chown =
226,592✔
231
                (uid != UID_INVALID && st.st_uid != uid) ||
113,296✔
232
                (gid != GID_INVALID && st.st_gid != gid);
15,228✔
233

234
        do_chmod =
226,592✔
235
                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
113,296✔
236
                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
113,288✔
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)
113,296✔
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)
74,283✔
243
                return -EINVAL; /* insist on the right file type if it was specified */
244

245
        if (do_chown && do_chmod) {
113,292✔
246
                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
19,536✔
247

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

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

261
        if (do_chown)
113,292✔
262
                if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0)
19,539✔
263
                        return -errno;
1✔
264

265
        if (do_chmod) {
113,291✔
266
                r = fchmod_opath(fd, mode & 07777);
19,726✔
267
                if (r < 0) {
19,726✔
UNCOV
268
                        if (!path || r != -ENOSYS)
×
269
                                return r;
270

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

277
        return do_chown || do_chmod;
113,291✔
278
}
279

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

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

286
int fchmod_opath(int fd, mode_t m) {
22,844✔
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
         *     but does not support AT_EMPTY_PATH (only supports AT_SYMLINK_NOFOLLOW);
293
         * - fchmodat2(2) supports all the AT_* flags, but is still very recent.
294
         *
295
         * We try to use fchmodat2(), and, if it is not supported, resort
296
         * to the /proc/self/fd dance. */
297

298
        assert(fd >= 0);
22,844✔
299

300
        if (fchmodat2(fd, "", m, AT_EMPTY_PATH) >= 0)
22,844✔
301
                return 0;
302
        if (!IN_SET(errno, ENOSYS, EPERM)) /* Some container managers block unknown syscalls with EPERM */
2✔
303
                return -errno;
2✔
304

UNCOV
305
        if (chmod(FORMAT_PROC_FD_PATH(fd), m) < 0) {
×
306
                if (errno != ENOENT)
×
307
                        return -errno;
×
308

UNCOV
309
                return proc_fd_enoent_errno();
×
310
        }
311

UNCOV
312
        return 0;
×
313
}
314

315
int futimens_opath(int fd, const struct timespec ts[2]) {
108,277✔
316
        /* Similar to fchmod_opath() but for futimens() */
317

318
        assert(fd >= 0);
108,277✔
319

320
        if (utimensat(fd, "", ts, AT_EMPTY_PATH) >= 0)
108,277✔
321
                return 0;
UNCOV
322
        if (errno != EINVAL)
×
323
                return -errno;
×
324

325
        /* Support for AT_EMPTY_PATH is added rather late (kernel 5.8), so fall back to going through /proc/
326
         * if unavailable. */
327

UNCOV
328
        if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, /* flags = */ 0) < 0) {
×
329
                if (errno != ENOENT)
×
330
                        return -errno;
×
331

UNCOV
332
                return proc_fd_enoent_errno();
×
333
        }
334

UNCOV
335
        return 0;
×
336
}
337

338
int stat_warn_permissions(const char *path, const struct stat *st) {
105,835✔
339
        assert(path);
105,835✔
340
        assert(st);
105,835✔
341

342
        /* Don't complain if we are reading something that is not a file, for example /dev/null */
343
        if (!S_ISREG(st->st_mode))
105,835✔
344
                return 0;
345

346
        if (st->st_mode & 0111)
105,834✔
UNCOV
347
                log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path);
×
348

349
        if (st->st_mode & 0002)
105,834✔
UNCOV
350
                log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
×
351

352
        if (getpid_cached() == 1 && (st->st_mode & 0044) != 0044)
105,834✔
353
                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);
12✔
354

355
        return 0;
356
}
357

358
int fd_warn_permissions(const char *path, int fd) {
8,840✔
359
        struct stat st;
8,840✔
360

361
        assert(path);
8,840✔
362
        assert(fd >= 0);
8,840✔
363

364
        if (fstat(fd, &st) < 0)
8,840✔
UNCOV
365
                return -errno;
×
366

367
        return stat_warn_permissions(path, &st);
8,840✔
368
}
369

370
int access_nofollow(const char *path, int mode) {
27,195✔
371
        return RET_NERRNO(faccessat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW));
27,195✔
372
}
373

374
int touch_fd(int fd, usec_t stamp) {
71,701✔
375
        assert(fd >= 0);
71,701✔
376

377
        if (stamp == USEC_INFINITY)
71,701✔
378
                return futimens_opath(fd, /* ts= */ NULL);
71,089✔
379

380
        struct timespec ts[2];
612✔
381
        timespec_store(ts + 0, stamp);
612✔
382
        ts[1] = ts[0];
612✔
383
        return futimens_opath(fd, ts);
612✔
384
}
385

386
int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
71,705✔
387
        _cleanup_close_ int fd = -EBADF;
71,705✔
388
        int ret;
71,705✔
389

390
        assert(path);
71,705✔
391

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

397
        if (parents)
71,705✔
398
                (void) mkdir_parents(path, 0755);
32,152✔
399

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

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

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

420
        return RET_GATHER(ret, touch_fd(fd, stamp));
71,701✔
421
}
422

423
int touch(const char *path) {
38,798✔
424
        return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID);
38,798✔
425
}
426

427
int symlinkat_idempotent(const char *from, int atfd, const char *to, bool make_relative) {
330✔
428
        _cleanup_free_ char *relpath = NULL;
330✔
429
        int r;
330✔
430

431
        assert(from);
330✔
432
        assert(to);
330✔
433

434
        if (make_relative) {
330✔
435
                r = path_make_relative_parent(to, from, &relpath);
185✔
436
                if (r < 0)
185✔
437
                        return r;
438

439
                from = relpath;
185✔
440
        }
441

442
        if (symlinkat(from, atfd, to) < 0) {
330✔
443
                _cleanup_free_ char *p = NULL;
51✔
444

445
                if (errno != EEXIST)
51✔
UNCOV
446
                        return -errno;
×
447

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

454
                if (!streq(p, from)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
51✔
455
                        return -EEXIST;
456
        }
457

458
        return 0;
459
}
460

461
int symlinkat_atomic_full(const char *from, int atfd, const char *to, SymlinkFlags flags) {
160,372✔
462
        int r;
160,372✔
463

464
        assert(from);
160,372✔
465
        assert(to);
160,372✔
466

467
        _cleanup_free_ char *relpath = NULL;
160,372✔
468
        if (FLAGS_SET(flags, SYMLINK_MAKE_RELATIVE)) {
160,372✔
469
                r = path_make_relative_parent(to, from, &relpath);
157,627✔
470
                if (r < 0)
157,627✔
471
                        return r;
472

473
                from = relpath;
157,627✔
474
        }
475

476
        _cleanup_free_ char *t = NULL;
160,372✔
477
        r = tempfn_random(to, NULL, &t);
160,372✔
478
        if (r < 0)
160,372✔
479
                return r;
480

481
        bool call_label_ops_post = false;
160,372✔
482
        if (FLAGS_SET(flags, SYMLINK_LABEL)) {
160,372✔
483
                r = label_ops_pre(atfd, to, S_IFLNK);
159,985✔
484
                if (r < 0)
159,985✔
485
                        return r;
486

487
                call_label_ops_post = true;
488
        }
489

490
        r = RET_NERRNO(symlinkat(from, atfd, t));
160,372✔
491
        if (call_label_ops_post)
160,372✔
492
                RET_GATHER(r, label_ops_post(atfd, t, /* created= */ r >= 0));
159,985✔
493
        if (r < 0)
160,372✔
UNCOV
494
                return r;
×
495

496
        r = RET_NERRNO(renameat(atfd, t, atfd, to));
160,373✔
497
        if (r < 0) {
1✔
498
                (void) unlinkat(atfd, t, 0);
1✔
499
                return r;
1✔
500
        }
501

502
        return 0;
503
}
504

UNCOV
505
int mknodat_atomic(int atfd, const char *path, mode_t mode, dev_t dev) {
×
506
        _cleanup_free_ char *t = NULL;
×
507
        int r;
×
508

UNCOV
509
        assert(path);
×
510

UNCOV
511
        r = tempfn_random(path, NULL, &t);
×
512
        if (r < 0)
×
513
                return r;
514

UNCOV
515
        if (mknodat(atfd, t, mode, dev) < 0)
×
516
                return -errno;
×
517

UNCOV
518
        r = RET_NERRNO(renameat(atfd, t, atfd, path));
×
519
        if (r < 0) {
×
520
                (void) unlinkat(atfd, t, 0);
×
521
                return r;
×
522
        }
523

524
        return 0;
525
}
526

527
int mkfifoat_atomic(int atfd, const char *path, mode_t mode) {
1✔
528
        _cleanup_free_ char *t = NULL;
1✔
529
        int r;
1✔
530

531
        assert(path);
1✔
532

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

538
        if (mkfifoat(atfd, t, mode) < 0)
1✔
UNCOV
539
                return -errno;
×
540

541
        r = RET_NERRNO(renameat(atfd, t, atfd, path));
1✔
UNCOV
542
        if (r < 0) {
×
543
                (void) unlinkat(atfd, t, 0);
×
544
                return r;
×
545
        }
546

547
        return 0;
548
}
549

550
int get_files_in_directory(const char *path, char ***ret_list) {
300✔
UNCOV
551
        _cleanup_strv_free_ char **l = NULL;
×
552
        _cleanup_closedir_ DIR *d = NULL;
300✔
553
        size_t n = 0;
300✔
554

555
        assert(path);
300✔
556

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

561
        d = opendir(path);
300✔
562
        if (!d)
300✔
563
                return -errno;
2✔
564

565
        FOREACH_DIRENT_ALL(de, d, return -errno) {
1,261✔
566
                if (!dirent_is_file(de))
963✔
567
                        continue;
647✔
568

569
                if (ret_list) {
316✔
570
                        /* one extra slot is needed for the terminating NULL */
571
                        if (!GREEDY_REALLOC(l, n + 2))
303✔
572
                                return -ENOMEM;
573

574
                        l[n] = strdup(de->d_name);
303✔
575
                        if (!l[n])
303✔
576
                                return -ENOMEM;
577

578
                        l[++n] = NULL;
303✔
579
                } else
580
                        n++;
13✔
581
        }
582

583
        if (ret_list)
298✔
584
                *ret_list = TAKE_PTR(l);
295✔
585

586
        return n;
298✔
587
}
588

589
static int getenv_tmp_dir(const char **ret_path) {
2,320✔
590
        int r, ret = 0;
2,320✔
591

592
        assert(ret_path);
2,320✔
593

594
        /* We use the same order of environment variables python uses in tempfile.gettempdir():
595
         * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */
596
        FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP") {
9,277✔
597
                const char *e;
6,958✔
598

599
                e = secure_getenv(n);
6,958✔
600
                if (!e)
6,958✔
601
                        continue;
6,956✔
602
                if (!path_is_absolute(e)) {
2✔
UNCOV
603
                        r = -ENOTDIR;
×
604
                        goto next;
×
605
                }
606
                if (!path_is_normalized(e)) {
2✔
UNCOV
607
                        r = -EPERM;
×
608
                        goto next;
×
609
                }
610

611
                r = is_dir(e, true);
2✔
612
                if (r < 0)
2✔
613
                        goto next;
1✔
614
                if (r == 0) {
1✔
UNCOV
615
                        r = -ENOTDIR;
×
616
                        goto next;
×
617
                }
618

619
                *ret_path = e;
1✔
620
                return 1;
1✔
621

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

628
        if (ret < 0)
2,319✔
629
                return ret;
630

631
        *ret_path = NULL;
2,318✔
632
        return ret;
2,318✔
633
}
634

635
static int tmp_dir_internal(const char *def, const char **ret) {
2,320✔
636
        const char *e;
2,320✔
637
        int r, k;
2,320✔
638

639
        assert(def);
2,320✔
640
        assert(ret);
2,320✔
641

642
        r = getenv_tmp_dir(&e);
2,320✔
643
        if (r > 0) {
2,320✔
644
                *ret = e;
1✔
645
                return 0;
1✔
646
        }
647

648
        k = is_dir(def, /* follow = */ true);
2,319✔
649
        if (k == 0)
2,319✔
650
                k = -ENOTDIR;
651
        if (k < 0)
2,319✔
UNCOV
652
                return RET_GATHER(r, k);
×
653

654
        *ret = def;
2,319✔
655
        return 0;
2,319✔
656
}
657

658
int var_tmp_dir(const char **ret) {
2,211✔
659
        assert(ret);
2,211✔
660

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

666
        return tmp_dir_internal("/var/tmp", ret);
2,211✔
667
}
668

669
int tmp_dir(const char **ret) {
109✔
670
        assert(ret);
109✔
671

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

675
        return tmp_dir_internal("/tmp", ret);
109✔
676
}
677

678
int unlink_or_warn(const char *filename) {
183✔
679
        assert(filename);
183✔
680

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

688
        return 0;
689
}
690

691
char *rmdir_and_free(char *p) {
247,218✔
692
        PROTECT_ERRNO;
247,218✔
693

694
        if (!p)
247,218✔
695
                return NULL;
696

697
        (void) rmdir(p);
247,218✔
698
        return mfree(p);
247,218✔
699
}
700

701
char* unlink_and_free(char *p) {
6,478✔
702
        PROTECT_ERRNO;
6,478✔
703

704
        if (!p)
6,478✔
705
                return NULL;
706

707
        (void) unlink(p);
5,886✔
708
        return mfree(p);
5,886✔
709
}
710

711
int access_fd(int fd, int mode) {
28,048✔
712
        assert(fd >= 0);
28,048✔
713

714
        /* Like access() but operates on an already open fd */
715

716
        if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0)
28,048✔
717
                return 0;
718
        if (errno != EINVAL)
485✔
719
                return -errno;
485✔
720

721
        /* Support for AT_EMPTY_PATH is added rather late (kernel 5.8), so fall back to going through /proc/
722
         * if unavailable. */
723

UNCOV
724
        if (access(FORMAT_PROC_FD_PATH(fd), mode) < 0) {
×
725
                if (errno != ENOENT)
×
726
                        return -errno;
×
727

UNCOV
728
                return proc_fd_enoent_errno();
×
729
        }
730

UNCOV
731
        return 0;
×
732
}
733

734
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
75✔
735
        _cleanup_close_ int truncate_fd = -EBADF;
75✔
736
        struct stat st;
75✔
737
        off_t l, bs;
75✔
738

739
        assert(fd >= 0 || fd == AT_FDCWD);
75✔
740
        assert(name);
75✔
741
        assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0);
75✔
742

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

759
        if (!FLAGS_SET(flags, UNLINK_REMOVEDIR)) {
75✔
760
                truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
75✔
761
                if (truncate_fd < 0) {
75✔
762

763
                        /* If this failed because the file doesn't exist propagate the error right-away. Also,
764
                         * AT_REMOVEDIR wasn't set, and we tried to open the file for writing, which means EISDIR is
765
                         * returned when this is a directory but we are not supposed to delete those, hence propagate
766
                         * the error right-away too. */
UNCOV
767
                        if (IN_SET(errno, ENOENT, EISDIR))
×
768
                                return -errno;
×
769

UNCOV
770
                        if (errno != ELOOP) /* don't complain if this is a symlink */
×
771
                                log_debug_errno(errno, "Failed to open file '%s' for deallocation, ignoring: %m", name);
×
772
                }
773
        }
774

775
        if (unlinkat(fd, name, FLAGS_SET(flags, UNLINK_REMOVEDIR) ? AT_REMOVEDIR : 0) < 0)
75✔
UNCOV
776
                return -errno;
×
777

778
        if (truncate_fd < 0) /* Don't have a file handle, can't do more ☹️ */
75✔
779
                return 0;
780

781
        if (fstat(truncate_fd, &st) < 0) {
75✔
UNCOV
782
                log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
783
                return 0;
×
784
        }
785

786
        if (!S_ISREG(st.st_mode))
75✔
787
                return 0;
788

789
        if (FLAGS_SET(flags, UNLINK_ERASE) && st.st_size > 0 && st.st_nlink == 0) {
75✔
790
                uint64_t left = st.st_size;
3✔
791
                char buffer[64 * 1024];
3✔
792

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

801
                random_bytes(buffer, sizeof(buffer));
3✔
802

803
                while (left > 0) {
6✔
804
                        ssize_t n;
3✔
805

806
                        n = write(truncate_fd, buffer, MIN(sizeof(buffer), left));
3✔
807
                        if (n < 0) {
3✔
UNCOV
808
                                log_debug_errno(errno, "Failed to erase data in file '%s', ignoring.", name);
×
809
                                break;
810
                        }
811

812
                        assert(left >= (size_t) n);
3✔
813
                        left -= n;
3✔
814
                }
815

816
                /* Let's refresh metadata */
817
                if (fstat(truncate_fd, &st) < 0) {
3✔
UNCOV
818
                        log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
819
                        return 0;
×
820
                }
821
        }
822

823
        /* Don't deallocate if there's nothing to deallocate or if the file is linked elsewhere */
824
        if (st.st_blocks == 0 || st.st_nlink > 0)
75✔
825
                return 0;
826

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

830
        bs = MAX(st.st_blksize, 512);
75✔
831
        l = ROUND_UP(st.st_size, bs); /* Round up to next block size */
75✔
832

833
        if (fallocate(truncate_fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, 0, l) >= 0)
75✔
834
                return 0; /* Successfully punched a hole! 😊 */
835

836
        /* Fall back to truncation */
UNCOV
837
        if (ftruncate(truncate_fd, 0) < 0) {
×
838
                log_debug_errno(errno, "Failed to truncate file to 0, ignoring: %m");
×
839
                return 0;
×
840
        }
841

842
        return 0;
843
}
844

845
int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
1,748,321✔
846
        _cleanup_free_ char *parent = NULL;
1,748,321✔
847
        int r;
1,748,321✔
848

849
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,748,321✔
850
        assert(path);
1,748,321✔
851

852
        r = path_extract_directory(path, &parent);
1,748,321✔
853
        if (r == -EDESTADDRREQ) {
1,748,321✔
854
                parent = strdup(".");
49✔
855
                if (!parent)
49✔
856
                        return -ENOMEM;
857
        } else if (r == -EADDRNOTAVAIL) {
1,748,272✔
UNCOV
858
                parent = strdup(path);
×
859
                if (!parent)
×
860
                        return -ENOMEM;
861
        } else if (r < 0)
1,748,272✔
862
                return r;
863

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

867
        if (FLAGS_SET(flags, O_PATH))
1,748,321✔
UNCOV
868
                flags |= O_DIRECTORY;
×
869
        else if (!FLAGS_SET(flags, O_TMPFILE))
1,748,321✔
870
                flags |= O_DIRECTORY|O_RDONLY;
1,740,713✔
871

872
        return RET_NERRNO(openat(dir_fd, parent, flags, mode));
1,748,354✔
873
}
874

875
int conservative_renameat(
39,477✔
876
                int olddirfd, const char *oldpath,
877
                int newdirfd, const char *newpath) {
878

879
        _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF;
39,477✔
880
        struct stat old_stat, new_stat;
39,477✔
881

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

889
        old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
39,477✔
890
        if (old_fd < 0)
39,477✔
UNCOV
891
                goto do_rename;
×
892

893
        new_fd = openat(newdirfd, newpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
39,477✔
894
        if (new_fd < 0)
39,477✔
895
                goto do_rename;
3,476✔
896

897
        if (fstat(old_fd, &old_stat) < 0)
36,001✔
UNCOV
898
                goto do_rename;
×
899

900
        if (!S_ISREG(old_stat.st_mode))
36,001✔
UNCOV
901
                goto do_rename;
×
902

903
        if (fstat(new_fd, &new_stat) < 0)
36,001✔
UNCOV
904
                goto do_rename;
×
905

906
        if (stat_inode_same(&new_stat, &old_stat))
36,001✔
907
                goto is_same;
1✔
908

909
        if (old_stat.st_mode != new_stat.st_mode ||
36,000✔
910
            old_stat.st_size != new_stat.st_size ||
36,000✔
911
            old_stat.st_uid != new_stat.st_uid ||
25,226✔
912
            old_stat.st_gid != new_stat.st_gid)
25,226✔
913
                goto do_rename;
10,774✔
914

915
        for (;;) {
6✔
916
                uint8_t buf1[16*1024];
25,232✔
917
                uint8_t buf2[sizeof(buf1)];
25,232✔
918
                ssize_t l1, l2;
25,232✔
919

920
                l1 = read(old_fd, buf1, sizeof(buf1));
25,232✔
921
                if (l1 < 0)
25,232✔
922
                        goto do_rename;
314✔
923

924
                if (l1 == sizeof(buf1))
25,232✔
925
                        /* Read the full block, hence read a full block in the other file too */
926

927
                        l2 = read(new_fd, buf2, l1);
7✔
928
                else {
929
                        assert((size_t) l1 < sizeof(buf1));
25,225✔
930

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

935
                        assert((size_t) (l1 + 1) <= sizeof(buf2));
25,225✔
936
                        l2 = read(new_fd, buf2, l1 + 1);
25,225✔
937
                }
938
                if (l2 != l1)
25,232✔
UNCOV
939
                        goto do_rename;
×
940

941
                if (memcmp(buf1, buf2, l1) != 0)
25,232✔
942
                        goto do_rename;
314✔
943

944
                if ((size_t) l1 < sizeof(buf1)) /* We hit EOF on the first file, and the second file too, hence exit
24,918✔
945
                                                 * now. */
946
                        break;
947
        }
948

949
is_same:
24,913✔
950
        /* Everything matches? Then don't rename, instead remove the source file, and leave the existing
951
         * destination in place */
952

953
        if (unlinkat(olddirfd, oldpath, 0) < 0)
24,913✔
UNCOV
954
                goto do_rename;
×
955

956
        return 0;
957

958
do_rename:
14,564✔
959
        if (renameat(olddirfd, oldpath, newdirfd, newpath) < 0)
14,564✔
UNCOV
960
                return -errno;
×
961

962
        return 1;
963
}
964

965
int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) {
1,715✔
966
        RateLimit rl;
1,715✔
967
        int r;
1,715✔
968

969
        r = posix_fallocate(fd, offset, size); /* returns positive errnos on error */
1,715✔
970
        if (r != EINTR)
1,715✔
971
                return -r; /* Let's return negative errnos, like common in our codebase */
1,715✔
972

973
        /* On EINTR try a couple of times more, but protect against busy looping
974
         * (not more than 16 times per 10s) */
UNCOV
975
        rl = (const RateLimit) { 10 * USEC_PER_SEC, 16 };
×
976
        while (ratelimit_below(&rl)) {
×
977
                r = posix_fallocate(fd, offset, size);
×
978
                if (r != EINTR)
×
979
                        return -r;
×
980
        }
981

982
        return -EINTR;
983
}
984

985
int parse_cifs_service(
15✔
986
                const char *s,
987
                char **ret_host,
988
                char **ret_service,
989
                char **ret_path) {
990

991
        _cleanup_free_ char *h = NULL, *ss = NULL, *x = NULL;
15✔
992
        const char *p, *e, *d;
15✔
993
        char delimiter;
15✔
994

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

999
        if (!s)
15✔
1000
                return -EINVAL;
1001

1002
        p = startswith(s, "//");
14✔
1003
        if (!p) {
14✔
1004
                p = startswith(s, "\\\\");
6✔
1005
                if (!p)
6✔
1006
                        return -EINVAL;
1007
        }
1008

1009
        delimiter = s[0];
11✔
1010
        e = strchr(p, delimiter);
11✔
1011
        if (!e)
11✔
1012
                return -EINVAL;
1013

1014
        h = strndup(p, e - p);
11✔
1015
        if (!h)
11✔
1016
                return -ENOMEM;
1017

1018
        if (!hostname_is_valid(h, 0))
11✔
1019
                return -EINVAL;
1020

1021
        e++;
10✔
1022

1023
        d = strchrnul(e, delimiter);
10✔
1024

1025
        ss = strndup(e, d - e);
10✔
1026
        if (!ss)
10✔
1027
                return -ENOMEM;
1028

1029
        if (!filename_is_valid(ss))
10✔
1030
                return -EINVAL;
1031

1032
        if (!isempty(d)) {
8✔
1033
                x = strdup(skip_leading_chars(d, CHAR_TO_STR(delimiter)));
6✔
1034
                if (!x)
6✔
1035
                        return -EINVAL;
2✔
1036

1037
                /* Make sure to convert Windows-style "\" → Unix-style / */
1038
                for (char *i = x; *i; i++)
33✔
1039
                        if (*i == delimiter)
27✔
1040
                                *i = '/';
3✔
1041

1042
                if (!path_is_valid(x))
6✔
1043
                        return -EINVAL;
1044

1045
                path_simplify(x);
6✔
1046
                if (!path_is_normalized(x))
6✔
1047
                        return -EINVAL;
1048
        }
1049

1050
        if (ret_host)
6✔
1051
                *ret_host = TAKE_PTR(h);
6✔
1052
        if (ret_service)
6✔
1053
                *ret_service = TAKE_PTR(ss);
6✔
1054
        if (ret_path)
6✔
1055
                *ret_path = TAKE_PTR(x);
6✔
1056

1057
        return 0;
1058
}
1059

1060
int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode) {
249,220✔
1061
        _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
249,220✔
1062
        _cleanup_free_ char *fname = NULL, *parent = NULL;
249,220✔
1063
        int r;
249,220✔
1064

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

1069
        if (flags & ~(O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_EXCL|O_NOATIME|O_NOFOLLOW|O_PATH))
249,220✔
1070
                return -EINVAL;
1071
        if ((flags & O_ACCMODE_STRICT) != O_RDONLY)
249,220✔
1072
                return -EINVAL;
1073

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

1077
        /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
1078
         * that we can pin it, and operate below it. */
1079
        r = path_extract_directory(path, &parent);
249,220✔
1080
        if (r < 0) {
249,220✔
1081
                if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
5,572✔
1082
                        return r;
1083
        } else {
1084
                r = path_extract_filename(path, &fname);
243,648✔
1085
                if (r < 0)
243,648✔
1086
                        return r;
1087

1088
                parent_fd = openat(dirfd, parent, O_PATH|O_DIRECTORY|O_CLOEXEC);
243,648✔
1089
                if (parent_fd < 0)
243,648✔
UNCOV
1090
                        return -errno;
×
1091

1092
                dirfd = parent_fd;
243,648✔
1093
                path = fname;
243,648✔
1094
        }
1095

1096
        fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode);
249,220✔
1097
        if (IN_SET(fd, -ELOOP, -ENOTDIR))
249,220✔
1098
                return -EEXIST;
1099
        if (fd < 0)
249,219✔
1100
                return fd;
4,267✔
1101

1102
        return TAKE_FD(fd);
1103
}
1104

1105
int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) {
2,533,643✔
1106
        int fd;
2,533,643✔
1107

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

1117
        if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) {
2,533,643✔
1118
                fd = openat(dirfd, pathname, flags, mode);
535,462✔
1119
                if (fd < 0)
535,462✔
1120
                        return -errno;
39,631✔
1121

1122
                if (ret_newly_created)
495,831✔
1123
                        *ret_newly_created = FLAGS_SET(flags, O_CREAT);
495,831✔
1124
                return fd;
495,831✔
1125
        }
1126

1127
        for (unsigned attempts = 7;;) {
1128
                /* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */
1129
                fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode);
1,998,538✔
1130
                if (fd >= 0) {
1,998,538✔
1131
                        if (ret_newly_created)
1,750,972✔
1132
                                *ret_newly_created = false;
1,750,972✔
1133
                        return fd;
1,750,972✔
1134
                }
1135
                if (errno != ENOENT)
247,566✔
UNCOV
1136
                        return -errno;
×
1137

1138
                /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL/O_NOFOLLOW. */
1139
                fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL | O_NOFOLLOW, mode);
247,566✔
1140
                if (fd >= 0) {
247,566✔
1141
                        if (ret_newly_created)
247,207✔
1142
                                *ret_newly_created = true;
247,206✔
1143
                        return fd;
247,207✔
1144
                }
1145
                if (errno != EEXIST)
359✔
UNCOV
1146
                        return -errno;
×
1147

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

1151
                if (--attempts == 0) /* Give up eventually, somebody is playing with us */
359✔
1152
                        return -EEXIST;
1153
        }
1154
}
1155

1156
int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
1,129,670✔
1157
        _cleanup_close_ int fd = -EBADF;
1,129,670✔
1158
        bool made_dir = false, made_file = false;
1,129,670✔
1159
        int r;
1,129,670✔
1160

1161
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,129,670✔
1162

1163
        /* An inode cannot be both a directory and a regular file at the same time. */
1164
        assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR)));
1,129,670✔
1165

1166
        /* This is like openat(), but has a few tricks up its sleeves, extending behaviour:
1167
         *
1168
         *   • O_DIRECTORY|O_CREAT is supported, which causes a directory to be created, and immediately
1169
         *     opened. When used with the XO_SUBVOLUME flag this will even create a btrfs subvolume.
1170
         *
1171
         *   • If O_CREAT is used with XO_LABEL, any created file will be immediately relabelled.
1172
         *
1173
         *   • If the path is specified NULL or empty, behaves like fd_reopen().
1174
         *
1175
         *   • If XO_NOCOW is specified will turn on the NOCOW btrfs flag on the file, if available.
1176
         *
1177
         *   • if XO_REGULAR is specified will return an error if inode is not a regular file.
1178
         *
1179
         *   • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files.
1180
         */
1181

1182
        if (mode == MODE_INVALID)
1,129,670✔
1183
                mode = (open_flags & O_DIRECTORY) ? 0755 : 0644;
54,646✔
1184

1185
        if (isempty(path)) {
1,129,670✔
1186
                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
6,128✔
1187

1188
                if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
6,128✔
1189
                        r = fd_verify_regular(dir_fd);
47✔
1190
                        if (r < 0)
47✔
1191
                                return r;
1192
                }
1193

1194
                return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
6,128✔
1195
        }
1196

1197
        bool call_label_ops_post = false;
1,123,542✔
1198

1199
        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
1,123,542✔
1200
                r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG);
563✔
1201
                if (r < 0)
563✔
1202
                        return r;
1203

1204
                call_label_ops_post = true;
1205
        }
1206

1207
        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
1,123,542✔
1208
                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
283,914✔
UNCOV
1209
                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
×
1210
                else
1211
                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
283,914✔
1212
                if (r == -EEXIST) {
151,100✔
1213
                        if (FLAGS_SET(open_flags, O_EXCL))
151,096✔
1214
                                return -EEXIST;
1215
                } else if (r < 0)
132,818✔
1216
                        return r;
1217
                else
1218
                        made_dir = true;
1219

1220
                open_flags &= ~(O_EXCL|O_CREAT);
279,646✔
1221
        }
1222

1223
        if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
1,119,274✔
1224
                /* Guarantee we return a regular fd only, and don't open the file unless we verified it
1225
                 * first */
1226

1227
                if (FLAGS_SET(open_flags, O_PATH)) {
402,253✔
1228
                        fd = openat(dir_fd, path, open_flags, mode);
1✔
1229
                        if (fd < 0) {
1✔
UNCOV
1230
                                r = -errno;
×
1231
                                goto error;
×
1232
                        }
1233

1234
                        r = fd_verify_regular(fd);
1✔
1235
                        if (r < 0)
1✔
UNCOV
1236
                                goto error;
×
1237

1238
                } else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) {
402,252✔
1239
                        /* In O_EXCL mode we can just create the thing, everything is dealt with for us */
1240
                        fd = openat(dir_fd, path, open_flags, mode);
2✔
1241
                        if (fd < 0) {
2✔
1242
                                r = -errno;
1✔
1243
                                goto error;
1✔
1244
                        }
1245

1246
                        made_file = true;
1✔
1247
                } else {
1248
                        /* Otherwise pin the inode first via O_PATH */
1249
                        _cleanup_close_ int inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|(open_flags & O_NOFOLLOW));
402,250✔
1250
                        if (inode_fd < 0) {
402,250✔
1251
                                if (errno != ENOENT || !FLAGS_SET(open_flags, O_CREAT)) {
21✔
1252
                                        r = -errno;
1✔
1253
                                        goto error;
1✔
1254
                                }
1255

1256
                                /* Doesn't exist yet, then try to create it */
1257
                                fd = openat(dir_fd, path, open_flags|O_CREAT|O_EXCL, mode);
20✔
1258
                                if (fd < 0) {
20✔
UNCOV
1259
                                        r = -errno;
×
1260
                                        goto error;
×
1261
                                }
1262

1263
                                made_file = true;
20✔
1264
                        } else {
1265
                                /* OK, we pinned it. Now verify it's actually a regular file, and then reopen it */
1266
                                r = fd_verify_regular(inode_fd);
402,229✔
1267
                                if (r < 0)
402,229✔
1268
                                        goto error;
5✔
1269

1270
                                fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT));
402,224✔
1271
                                if (fd < 0) {
402,224✔
UNCOV
1272
                                        r = fd;
×
1273
                                        goto error;
×
1274
                                }
1275
                        }
1276
                }
1277
        } else {
1278
                fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
717,021✔
1279
                if (fd < 0) {
717,021✔
1280
                        r = fd;
38,037✔
1281
                        goto error;
38,037✔
1282
                }
1283
        }
1284

1285
        if (call_label_ops_post) {
1,081,230✔
1286
                call_label_ops_post = false;
563✔
1287

1288
                r = label_ops_post(fd, /* path= */ NULL, made_file || made_dir);
563✔
1289
                if (r < 0)
563✔
UNCOV
1290
                        goto error;
×
1291
        }
1292

1293
        if (FLAGS_SET(xopen_flags, XO_NOCOW)) {
1,081,230✔
1294
                r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
69✔
1295
                if (r < 0 && !ERRNO_IS_IOCTL_NOT_SUPPORTED(r))
69✔
UNCOV
1296
                        goto error;
×
1297
        }
1298

1299
        return TAKE_FD(fd);
1300

1301
error:
38,044✔
1302
        if (call_label_ops_post)
38,044✔
UNCOV
1303
                (void) label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : path, made_dir || made_file);
×
1304

1305
        if (made_dir || made_file)
38,044✔
UNCOV
1306
                (void) unlinkat(dir_fd, path, made_dir ? AT_REMOVEDIR : 0);
×
1307

1308
        return r;
1309
}
1310

1311
int xopenat_lock_full(
278,755✔
1312
                int dir_fd,
1313
                const char *path,
1314
                int open_flags,
1315
                XOpenFlags xopen_flags,
1316
                mode_t mode,
1317
                LockType locktype,
1318
                int operation) {
1319

1320
        _cleanup_close_ int fd = -EBADF;
278,755✔
1321
        int r;
278,755✔
1322

1323
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
278,755✔
1324
        assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
278,755✔
1325

1326
        /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
1327
         * the same error here). */
1328
        if (FLAGS_SET(open_flags, O_DIRECTORY) && !IN_SET(locktype, LOCK_BSD, LOCK_NONE))
278,755✔
1329
                return -EBADF;
1330

1331
        for (;;) {
313,970✔
1332
                struct stat st;
296,362✔
1333

1334
                fd = xopenat_full(dir_fd, path, open_flags, xopen_flags, mode);
296,362✔
1335
                if (fd < 0)
296,362✔
1336
                        return fd;
8✔
1337

1338
                r = lock_generic(fd, locktype, operation);
296,362✔
1339
                if (r < 0)
296,362✔
1340
                        return r;
1341

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

1346
                if (fstat(fd, &st) < 0)
296,354✔
UNCOV
1347
                        return -errno;
×
1348
                if (st.st_nlink > 0)
296,354✔
1349
                        break;
1350

1351
                fd = safe_close(fd);
17,608✔
1352
        }
1353

1354
        return TAKE_FD(fd);
278,746✔
1355
}
1356

1357
int link_fd(int fd, int newdirfd, const char *newpath) {
12,400✔
1358
        int r;
12,400✔
1359

1360
        assert(fd >= 0);
12,400✔
1361
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
12,400✔
1362
        assert(newpath);
12,400✔
1363

1364
        /* Try to link via AT_EMPTY_PATH first. This fails with ENOENT if we don't have CAP_DAC_READ_SEARCH
1365
         * on kernels < 6.10, in which case we'd then resort to /proc/self/fd/ dance.
1366
         *
1367
         * See also: https://github.com/torvalds/linux/commit/42bd2af5950456d46fdaa91c3a8fb02e680f19f5 */
1368
        r = RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
12,400✔
1369
        if (r == -ENOENT) {
4,870✔
UNCOV
1370
                r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
×
1371
                if (r == -ENOENT && proc_mounted() == 0) /* No proc_fd_enoent_errno() here because we don't
×
1372
                                                            know if it's the target path that's missing. */
UNCOV
1373
                        return -ENOSYS;
×
1374
        }
1375

1376
        return r;
1377
}
1378

1379
int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
6,677✔
1380
        _cleanup_close_ int old_fd = -EBADF;
6,677✔
1381
        int r;
6,677✔
1382

1383
        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
6,677✔
1384
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
6,677✔
1385
        assert(!isempty(newpath)); /* source path is optional, but the target path is not */
6,677✔
1386

1387
        /* Like linkat() but replaces the target if needed. Is a NOP if source and target already share the
1388
         * same inode. */
1389

1390
        if (olddirfd == AT_FDCWD && isempty(oldpath)) /* Refuse operating on the cwd (which is a dir, and dirs can't be hardlinked) */
13,354✔
1391
                return -EISDIR;
1392

1393
        if (path_implies_directory(oldpath)) /* Refuse these definite directories early */
6,677✔
1394
                return -EISDIR;
1395

1396
        if (path_implies_directory(newpath))
6,677✔
1397
                return -EISDIR;
1398

1399
        /* First, try to link this directly */
1400
        if (oldpath)
6,677✔
1401
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
35✔
1402
        else
1403
                r = link_fd(olddirfd, newdirfd, newpath);
6,642✔
1404
        if (r >= 0)
6,660✔
1405
                return 0;
1,792✔
1406
        if (r != -EEXIST)
4,885✔
1407
                return r;
1408

1409
        old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC);
4,885✔
1410
        if (old_fd < 0)
4,885✔
1411
                return old_fd;
1412

1413
        struct stat old_st;
4,885✔
1414
        if (fstat(old_fd, &old_st) < 0)
4,885✔
UNCOV
1415
                return -errno;
×
1416

1417
        if (S_ISDIR(old_st.st_mode)) /* Don't bother if we are operating on a directory */
4,885✔
1418
                return -EISDIR;
1419

1420
        struct stat new_st;
4,885✔
1421
        if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0)
4,885✔
UNCOV
1422
                return -errno;
×
1423

1424
        if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */
4,885✔
1425
                return -EEXIST;
1426

1427
        if (stat_inode_same(&old_st, &new_st)) /* Already the same inode? Then shortcut this */
4,885✔
1428
                return 0;
1429

1430
        _cleanup_free_ char *tmp_path = NULL;
4,884✔
1431
        r = tempfn_random(newpath, /* extra= */ NULL, &tmp_path);
4,884✔
1432
        if (r < 0)
4,884✔
1433
                return r;
1434

1435
        r = link_fd(old_fd, newdirfd, tmp_path);
4,884✔
1436
        if (r < 0) {
4,884✔
UNCOV
1437
                if (!ERRNO_IS_PRIVILEGE(r))
×
1438
                        return r;
1439

1440
                /* If that didn't work due to permissions then go via the path of the dentry */
UNCOV
1441
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, tmp_path, 0));
×
1442
                if (r < 0)
×
1443
                        return r;
1444
        }
1445

1446
        r = RET_NERRNO(renameat(newdirfd, tmp_path, newdirfd, newpath));
4,884✔
UNCOV
1447
        if (r < 0) {
×
1448
                (void) unlinkat(newdirfd, tmp_path, /* flags= */ 0);
×
1449
                return r;
×
1450
        }
1451

1452
        return 0;
1453
}
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