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

systemd / systemd / 21121026098

18 Jan 2026 06:15PM UTC coverage: 72.736% (+0.2%) from 72.561%
21121026098

push

github

YHNdnzj
cryptenroll,cryptsetup,shutdown: only call mlockall if we have CAP_IPC_LOCK

Calling mlockall in an unprivileged process most notably had the effect
of making systemd-cryptenroll OOM while trying to open a normal-sized
argon2 keyslot due to it hitting RLIMIT_MEMLOCK.

9 of 14 new or added lines in 4 files covered. (64.29%)

1479 existing lines in 62 files now uncovered.

310610 of 427035 relevant lines covered (72.74%)

1127441.3 hits per line

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

80.49
/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 "mkdir.h"
20
#include "path-util.h"
21
#include "process-util.h"
22
#include "random-util.h"
23
#include "ratelimit.h"
24
#include "stat-util.h"
25
#include "string-util.h"
26
#include "strv.h"
27
#include "time-util.h"
28
#include "tmpfile-util.h"
29
#include "umask-util.h"
30

31
int rmdir_parents(const char *path, const char *stop) {
61,044✔
32
        char *p;
61,044✔
33
        int r;
61,044✔
34

35
        assert(path);
61,044✔
36
        assert(stop);
61,044✔
37

38
        if (!path_is_safe(path))
61,044✔
39
                return -EINVAL;
40

41
        if (!path_is_safe(stop))
61,043✔
42
                return -EINVAL;
43

44
        p = strdupa_safe(path);
61,042✔
45

46
        for (;;) {
3,064✔
47
                char *slash = NULL;
64,106✔
48

49
                /* skip the last component. */
50
                r = path_find_last_component(p, /* accept_dot_dot= */ false, (const char **) &slash, NULL);
64,106✔
51
                if (r <= 0)
64,106✔
52
                        return r;
61,042✔
53
                if (slash == p)
64,106✔
54
                        return 0;
55

56
                assert(*slash == '/');
64,106✔
57
                *slash = '\0';
64,106✔
58

59
                if (path_startswith_full(stop, p, PATH_STARTSWITH_REFUSE_DOT_DOT))
64,106✔
60
                        return 0;
61

62
                if (rmdir(p) < 0 && errno != ENOENT)
63,975✔
63
                        return -errno;
60,911✔
64
        }
65
}
66

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

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

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

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

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

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

95
                return 0;
×
96
        }
97

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

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

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

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

112
int readlinkat_malloc(int fd, const char *p, char **ret) {
5,784,029✔
113
        size_t l = PATH_MAX;
5,784,029✔
114

115
        assert(fd >= 0 || fd == AT_FDCWD);
5,784,029✔
116

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

121
        for (;;) {
5,784,029✔
122
                _cleanup_free_ char *c = NULL;
5,784,029✔
123
                ssize_t n;
5,784,029✔
124

125
                c = new(char, l+1);
5,784,029✔
126
                if (!c)
5,784,029✔
127
                        return -ENOMEM;
128

129
                n = readlinkat(fd, strempty(p), c, l);
5,784,031✔
130
                if (n < 0)
5,784,029✔
131
                        return -errno;
361,497✔
132

133
                if ((size_t) n < l) {
5,422,532✔
134
                        c[n] = 0;
5,422,532✔
135

136
                        if (ret)
5,422,532✔
137
                                *ret = TAKE_PTR(c);
5,422,532✔
138

139
                        return 0;
5,422,532✔
140
                }
141

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

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

151
int readlink_value(const char *p, char **ret) {
594,938✔
152
        _cleanup_free_ char *link = NULL, *name = NULL;
594,938✔
153
        int r;
594,938✔
154

155
        assert(p);
594,938✔
156
        assert(ret);
594,938✔
157

158
        r = readlink_malloc(p, &link);
594,938✔
159
        if (r < 0)
594,938✔
160
                return r;
161

162
        r = path_extract_filename(link, &name);
353,393✔
163
        if (r < 0)
353,393✔
164
                return r;
165
        if (r == O_DIRECTORY)
353,393✔
166
                return -EINVAL;
167

168
        *ret = TAKE_PTR(name);
353,393✔
169
        return 0;
353,393✔
170
}
171

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

176
        assert(p);
3,128✔
177
        assert(ret);
3,128✔
178

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

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

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

189
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
20,975✔
190

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

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

206
        return fchmod_and_chown(dir_fd, mode, uid, gid);
20,975✔
207
}
208

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

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

226
        if (fstat(fd, &st) < 0)
125,386✔
227
                return -errno;
×
228

229
        do_chown =
250,772✔
230
                (uid != UID_INVALID && st.st_uid != uid) ||
125,386✔
231
                (gid != GID_INVALID && st.st_gid != gid);
29,439✔
232

233
        do_chmod =
250,772✔
234
                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
125,386✔
235
                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
120,701✔
236
                 do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown()
237
                             * modifies the access mode too */
238

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

244
        if (do_chown && do_chmod) {
125,382✔
245
                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
12,878✔
246

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

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

260
        if (do_chown)
125,382✔
261
                if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0)
12,881✔
262
                        return -errno;
1✔
263

264
        if (do_chmod) {
125,381✔
265
                r = fchmod_opath(fd, mode & 07777);
27,829✔
266
                if (r < 0) {
27,829✔
267
                        if (!path || r != -ENOSYS)
×
268
                                return r;
269

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

276
        return do_chown || do_chmod;
125,381✔
277
}
278

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

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

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

300
        assert(fd >= 0);
30,593✔
301

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

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

313
                return proc_fd_enoent_errno();
×
314
        }
315

316
        return 0;
×
317
}
318

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

322
        assert(fd >= 0);
129,366✔
323

324
        if (utimensat(fd, "", ts, AT_EMPTY_PATH) >= 0)
129,366✔
325
                return 0;
326
        if (errno != EINVAL)
×
327
                return -errno;
×
328

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

332
        if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, /* flags= */ 0) < 0) {
×
333
                if (errno != ENOENT)
×
334
                        return -errno;
×
335

336
                return proc_fd_enoent_errno();
×
337
        }
338

339
        return 0;
×
340
}
341

342
int stat_warn_permissions(const char *path, const struct stat *st) {
94,320✔
343
        assert(path);
94,320✔
344
        assert(st);
94,320✔
345

346
        /* Don't complain if we are reading something that is not a file, for example /dev/null */
347
        if (!S_ISREG(st->st_mode))
94,320✔
348
                return 0;
349

350
        if (st->st_mode & 0111)
94,320✔
351
                log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path);
×
352

353
        if (st->st_mode & 0002)
94,320✔
354
                log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
×
355

356
        if (getpid_cached() == 1 && (st->st_mode & 0044) != 0044)
94,320✔
357
                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✔
358

359
        return 0;
360
}
361

362
int fd_warn_permissions(const char *path, int fd) {
×
363
        struct stat st;
×
364

365
        assert(path);
×
366
        assert(fd >= 0);
×
367

368
        if (fstat(fd, &st) < 0)
×
369
                return -errno;
×
370

371
        return stat_warn_permissions(path, &st);
×
372
}
373

374
int access_nofollow(const char *path, int mode) {
24,466✔
375
        return RET_NERRNO(faccessat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW));
24,466✔
376
}
377

378
int touch_fd(int fd, usec_t stamp) {
74,572✔
379
        assert(fd >= 0);
74,572✔
380

381
        if (stamp == USEC_INFINITY)
74,572✔
382
                return futimens_opath(fd, /* ts= */ NULL);
73,987✔
383

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

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

394
        assert(path);
74,576✔
395

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

401
        if (parents)
74,576✔
402
                (void) mkdir_parents(path, 0755);
34,250✔
403

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

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

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

424
        return RET_GATHER(ret, touch_fd(fd, stamp));
74,572✔
425
}
426

427
int touch(const char *path) {
39,720✔
428
        return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID);
39,720✔
429
}
430

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

435
        assert(target);
296✔
436
        assert(linkpath);
296✔
437

438
        if (make_relative) {
296✔
439
                r = path_make_relative_parent(linkpath, target, &relpath);
186✔
440
                if (r < 0)
186✔
441
                        return r;
442

443
                target = relpath;
186✔
444
        }
445

446
        if (symlinkat(target, atfd, linkpath) < 0) {
296✔
447
                _cleanup_free_ char *p = NULL;
48✔
448

449
                if (errno != EEXIST)
48✔
450
                        return -errno;
×
451

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

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

462
        return 0;
463
}
464

465
int symlinkat_atomic_full(const char *target, int atfd, const char *linkpath, SymlinkFlags flags) {
133,839✔
466
        int r;
133,839✔
467

468
        assert(target);
133,839✔
469
        assert(linkpath);
133,839✔
470

471
        _cleanup_free_ char *relpath = NULL;
133,839✔
472
        if (FLAGS_SET(flags, SYMLINK_MAKE_RELATIVE)) {
133,839✔
473
                r = path_make_relative_parent(linkpath, target, &relpath);
126,304✔
474
                if (r < 0)
126,304✔
475
                        return r;
476

477
                target = relpath;
126,304✔
478
        }
479

480
        _cleanup_free_ char *t = NULL;
133,839✔
481
        r = tempfn_random(linkpath, NULL, &t);
133,839✔
482
        if (r < 0)
133,839✔
483
                return r;
484

485
        bool call_label_ops_post = false;
133,839✔
486
        if (FLAGS_SET(flags, SYMLINK_LABEL)) {
133,839✔
487
                r = label_ops_pre(atfd, linkpath, S_IFLNK);
128,775✔
488
                if (r < 0)
128,775✔
489
                        return r;
490

491
                call_label_ops_post = true;
492
        }
493

494
        r = RET_NERRNO(symlinkat(target, atfd, t));
133,839✔
495
        if (call_label_ops_post)
133,839✔
496
                RET_GATHER(r, label_ops_post(atfd, t, /* created= */ r >= 0));
128,775✔
497
        if (r < 0)
133,839✔
498
                return r;
×
499

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

506
        return 0;
507
}
508

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

513
        assert(path);
×
514

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

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

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

528
        return 0;
529
}
530

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

535
        assert(path);
1✔
536

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

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

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

551
        return 0;
552
}
553

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

559
        assert(path);
469✔
560

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

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

569
        FOREACH_DIRENT_ALL(de, d, return -errno) {
1,941✔
570
                if (!dirent_is_file(de))
1,474✔
571
                        continue;
984✔
572

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

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

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

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

590
        return n;
467✔
591
}
592

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

596
        assert(ret_path);
2,396✔
597

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

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

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

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

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

632
        if (ret < 0)
2,395✔
633
                return ret;
634

635
        *ret_path = NULL;
2,394✔
636
        return ret;
2,394✔
637
}
638

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

643
        assert(def);
2,396✔
644
        assert(ret);
2,396✔
645

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

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

658
        *ret = def;
2,395✔
659
        return 0;
2,395✔
660
}
661

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

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

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

673
int tmp_dir(const char **ret) {
161✔
674
        assert(ret);
161✔
675

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

679
        return tmp_dir_internal("/tmp", ret);
161✔
680
}
681

682
int unlink_or_warn(const char *filename) {
189✔
683
        assert(filename);
189✔
684

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

692
        return 0;
693
}
694

695
char *rmdir_and_free(char *p) {
215,031✔
696
        PROTECT_ERRNO;
215,031✔
697

698
        if (!p)
215,031✔
699
                return NULL;
700

701
        (void) rmdir(p);
215,031✔
702
        return mfree(p);
215,031✔
703
}
704

705
char* unlink_and_free(char *p) {
6,734✔
706
        PROTECT_ERRNO;
6,734✔
707

708
        if (!p)
6,734✔
709
                return NULL;
710

711
        (void) unlink(p);
6,060✔
712
        return mfree(p);
6,060✔
713
}
714

715
int access_fd(int fd, int mode) {
27,646✔
716
        /* Like access() but operates on an already open fd */
717

718
        if (fd == AT_FDCWD)
27,646✔
UNCOV
719
                return RET_NERRNO(access(".", mode));
×
720
        if (fd == XAT_FDROOT)
27,646✔
721
                return RET_NERRNO(access("/", mode));
1✔
722

723
        assert(fd >= 0);
27,645✔
724

725
        if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0)
27,645✔
726
                return 0;
727
        if (errno != EINVAL)
1✔
728
                return -errno;
1✔
729

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

UNCOV
733
        if (access(FORMAT_PROC_FD_PATH(fd), mode) < 0) {
×
UNCOV
734
                if (errno != ENOENT)
×
735
                        return -errno;
×
736

UNCOV
737
                return proc_fd_enoent_errno();
×
738
        }
739

UNCOV
740
        return 0;
×
741
}
742

743
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
83✔
744
        _cleanup_close_ int truncate_fd = -EBADF;
83✔
745
        struct stat st;
83✔
746
        off_t l, bs;
83✔
747

748
        assert(fd >= 0 || fd == AT_FDCWD);
83✔
749
        assert(name);
83✔
750
        assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0);
83✔
751

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

768
        if (!FLAGS_SET(flags, UNLINK_REMOVEDIR)) {
83✔
769
                truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
83✔
770
                if (truncate_fd < 0) {
83✔
771

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

UNCOV
779
                        if (errno != ELOOP) /* don't complain if this is a symlink */
×
780
                                log_debug_errno(errno, "Failed to open file '%s' for deallocation, ignoring: %m", name);
×
781
                }
782
        }
783

784
        if (unlinkat(fd, name, FLAGS_SET(flags, UNLINK_REMOVEDIR) ? AT_REMOVEDIR : 0) < 0)
83✔
UNCOV
785
                return -errno;
×
786

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

790
        if (fstat(truncate_fd, &st) < 0) {
83✔
UNCOV
791
                log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
UNCOV
792
                return 0;
×
793
        }
794

795
        if (!S_ISREG(st.st_mode))
83✔
796
                return 0;
797

798
        if (FLAGS_SET(flags, UNLINK_ERASE) && st.st_size > 0 && st.st_nlink == 0) {
83✔
799
                uint64_t left = st.st_size;
3✔
800
                char buffer[64 * 1024];
3✔
801

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

810
                random_bytes(buffer, sizeof(buffer));
3✔
811

812
                while (left > 0) {
6✔
813
                        ssize_t n;
3✔
814

815
                        n = write(truncate_fd, buffer, MIN(sizeof(buffer), left));
3✔
816
                        if (n < 0) {
3✔
UNCOV
817
                                log_debug_errno(errno, "Failed to erase data in file '%s', ignoring.", name);
×
818
                                break;
819
                        }
820

821
                        assert(left >= (size_t) n);
3✔
822
                        left -= n;
3✔
823
                }
824

825
                /* Let's refresh metadata */
826
                if (fstat(truncate_fd, &st) < 0) {
3✔
UNCOV
827
                        log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
UNCOV
828
                        return 0;
×
829
                }
830
        }
831

832
        /* Don't deallocate if there's nothing to deallocate or if the file is linked elsewhere */
833
        if (st.st_blocks == 0 || st.st_nlink > 0)
83✔
834
                return 0;
835

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

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

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

845
        /* Fall back to truncation */
UNCOV
846
        if (ftruncate(truncate_fd, 0) < 0) {
×
UNCOV
847
                log_debug_errno(errno, "Failed to truncate file to 0, ignoring: %m");
×
UNCOV
848
                return 0;
×
849
        }
850

851
        return 0;
852
}
853

854
int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
1,867,561✔
855
        _cleanup_free_ char *parent = NULL;
1,867,561✔
856
        int r;
1,867,561✔
857

858
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,867,561✔
859
        assert(path);
1,867,561✔
860

861
        r = path_extract_directory(path, &parent);
1,867,561✔
862
        if (r == -EDESTADDRREQ) {
1,867,561✔
863
                parent = strdup(".");
13,710✔
864
                if (!parent)
13,710✔
865
                        return -ENOMEM;
866
        } else if (r == -EADDRNOTAVAIL) {
1,853,851✔
UNCOV
867
                parent = strdup(path);
×
UNCOV
868
                if (!parent)
×
869
                        return -ENOMEM;
870
        } else if (r < 0)
1,853,851✔
871
                return r;
872

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

876
        if (!FLAGS_SET(flags, O_TMPFILE))
1,867,561✔
877
                flags |= O_DIRECTORY;
1,845,761✔
878

879
        return RET_NERRNO(openat(dir_fd, parent, flags, mode));
1,867,608✔
880
}
881

882
int conservative_renameat(
40,170✔
883
                int olddirfd, const char *oldpath,
884
                int newdirfd, const char *newpath) {
885

886
        _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF;
40,170✔
887
        struct stat old_stat, new_stat;
40,170✔
888

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

896
        old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
40,170✔
897
        if (old_fd < 0)
40,170✔
UNCOV
898
                goto do_rename;
×
899

900
        new_fd = openat(newdirfd, newpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
40,170✔
901
        if (new_fd < 0)
40,170✔
902
                goto do_rename;
3,482✔
903

904
        if (fstat(old_fd, &old_stat) < 0)
36,688✔
UNCOV
905
                goto do_rename;
×
906

907
        if (!S_ISREG(old_stat.st_mode))
36,688✔
UNCOV
908
                goto do_rename;
×
909

910
        if (fstat(new_fd, &new_stat) < 0)
36,688✔
UNCOV
911
                goto do_rename;
×
912

913
        if (stat_inode_same(&new_stat, &old_stat))
36,688✔
914
                goto is_same;
1✔
915

916
        if (old_stat.st_mode != new_stat.st_mode ||
36,687✔
917
            old_stat.st_size != new_stat.st_size ||
36,687✔
918
            old_stat.st_uid != new_stat.st_uid ||
25,701✔
919
            old_stat.st_gid != new_stat.st_gid)
25,701✔
920
                goto do_rename;
10,986✔
921

922
        for (;;) {
3✔
923
                uint8_t buf1[16*1024];
25,704✔
924
                uint8_t buf2[sizeof(buf1)];
25,704✔
925
                ssize_t l1, l2;
25,704✔
926

927
                l1 = read(old_fd, buf1, sizeof(buf1));
25,704✔
928
                if (l1 < 0)
25,704✔
929
                        goto do_rename;
329✔
930

931
                if (l1 == sizeof(buf1))
25,704✔
932
                        /* Read the full block, hence read a full block in the other file too */
933

934
                        l2 = read(new_fd, buf2, l1);
4✔
935
                else {
936
                        assert((size_t) l1 < sizeof(buf1));
25,700✔
937

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

942
                        assert((size_t) (l1 + 1) <= sizeof(buf2));
25,700✔
943
                        l2 = read(new_fd, buf2, l1 + 1);
25,700✔
944
                }
945
                if (l2 != l1)
25,704✔
UNCOV
946
                        goto do_rename;
×
947

948
                if (memcmp(buf1, buf2, l1) != 0)
25,704✔
949
                        goto do_rename;
329✔
950

951
                if ((size_t) l1 < sizeof(buf1)) /* We hit EOF on the first file, and the second file too, hence exit
25,375✔
952
                                                 * now. */
953
                        break;
954
        }
955

956
is_same:
25,373✔
957
        /* Everything matches? Then don't rename, instead remove the source file, and leave the existing
958
         * destination in place */
959

960
        if (unlinkat(olddirfd, oldpath, 0) < 0)
25,373✔
UNCOV
961
                goto do_rename;
×
962

963
        return 0;
964

965
do_rename:
14,797✔
966
        if (renameat(olddirfd, oldpath, newdirfd, newpath) < 0)
14,797✔
UNCOV
967
                return -errno;
×
968

969
        return 1;
970
}
971

972
int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) {
1,706✔
973
        RateLimit rl;
1,706✔
974
        int r;
1,706✔
975

976
        r = posix_fallocate(fd, offset, size); /* returns positive errnos on error */
1,706✔
977
        if (r != EINTR)
1,706✔
978
                return -r; /* Let's return negative errnos, like common in our codebase */
1,706✔
979

980
        /* On EINTR try a couple of times more, but protect against busy looping
981
         * (not more than 16 times per 10s) */
UNCOV
982
        rl = (const RateLimit) { 10 * USEC_PER_SEC, 16 };
×
UNCOV
983
        while (ratelimit_below(&rl)) {
×
UNCOV
984
                r = posix_fallocate(fd, offset, size);
×
UNCOV
985
                if (r != EINTR)
×
UNCOV
986
                        return -r;
×
987
        }
988

989
        return -EINTR;
990
}
991

992
int parse_cifs_service(
15✔
993
                const char *s,
994
                char **ret_host,
995
                char **ret_service,
996
                char **ret_path) {
997

998
        _cleanup_free_ char *h = NULL, *ss = NULL, *x = NULL;
15✔
999
        const char *p, *e, *d;
15✔
1000
        char delimiter;
15✔
1001

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

1006
        if (!s)
15✔
1007
                return -EINVAL;
1008

1009
        p = startswith(s, "//");
14✔
1010
        if (!p) {
14✔
1011
                p = startswith(s, "\\\\");
6✔
1012
                if (!p)
6✔
1013
                        return -EINVAL;
1014
        }
1015

1016
        delimiter = s[0];
11✔
1017
        e = strchr(p, delimiter);
11✔
1018
        if (!e)
11✔
1019
                return -EINVAL;
1020

1021
        h = strndup(p, e - p);
11✔
1022
        if (!h)
11✔
1023
                return -ENOMEM;
1024

1025
        if (!hostname_is_valid(h, 0))
11✔
1026
                return -EINVAL;
1027

1028
        e++;
10✔
1029

1030
        d = strchrnul(e, delimiter);
10✔
1031

1032
        ss = strndup(e, d - e);
10✔
1033
        if (!ss)
10✔
1034
                return -ENOMEM;
1035

1036
        if (!filename_is_valid(ss))
10✔
1037
                return -EINVAL;
1038

1039
        if (!isempty(d)) {
8✔
1040
                x = strdup(skip_leading_chars(d, CHAR_TO_STR(delimiter)));
6✔
1041
                if (!x)
6✔
1042
                        return -EINVAL;
2✔
1043

1044
                /* Make sure to convert Windows-style "\" → Unix-style / */
1045
                for (char *i = x; *i; i++)
33✔
1046
                        if (*i == delimiter)
27✔
1047
                                *i = '/';
3✔
1048

1049
                if (!path_is_valid(x))
6✔
1050
                        return -EINVAL;
1051

1052
                path_simplify(x);
6✔
1053
                if (!path_is_normalized(x))
6✔
1054
                        return -EINVAL;
1055
        }
1056

1057
        if (ret_host)
6✔
1058
                *ret_host = TAKE_PTR(h);
6✔
1059
        if (ret_service)
6✔
1060
                *ret_service = TAKE_PTR(ss);
6✔
1061
        if (ret_path)
6✔
1062
                *ret_path = TAKE_PTR(x);
6✔
1063

1064
        return 0;
1065
}
1066

1067
int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode) {
220,190✔
1068
        _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
220,190✔
1069
        _cleanup_free_ char *fname = NULL, *parent = NULL;
220,190✔
1070
        int r;
220,190✔
1071

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

1076
        if (flags & ~(O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_EXCL|O_NOATIME|O_NOFOLLOW|O_PATH))
220,190✔
1077
                return -EINVAL;
1078
        if ((flags & O_ACCMODE_STRICT) != O_RDONLY)
220,190✔
1079
                return -EINVAL;
1080

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

1084
        /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
1085
         * that we can pin it, and operate below it. */
1086
        r = path_extract_directory(path, &parent);
220,190✔
1087
        if (r < 0) {
220,190✔
1088
                if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
7,728✔
1089
                        return r;
1090
        } else {
1091
                r = path_extract_filename(path, &fname);
212,462✔
1092
                if (r < 0)
212,462✔
1093
                        return r;
1094

1095
                parent_fd = openat(dirfd, parent, O_PATH|O_DIRECTORY|O_CLOEXEC);
212,462✔
1096
                if (parent_fd < 0)
212,462✔
UNCOV
1097
                        return -errno;
×
1098

1099
                dirfd = parent_fd;
212,462✔
1100
                path = fname;
212,462✔
1101
        }
1102

1103
        fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode);
220,190✔
1104
        if (IN_SET(fd, -ELOOP, -ENOTDIR))
220,190✔
1105
                return -EEXIST;
1106
        if (fd < 0)
220,189✔
1107
                return fd;
4,526✔
1108

1109
        return TAKE_FD(fd);
1110
}
1111

1112
int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) {
2,674,013✔
1113
        int fd;
2,674,013✔
1114

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

1124
        if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) {
2,674,013✔
1125
                fd = openat(dirfd, pathname, flags, mode);
604,230✔
1126
                if (fd < 0)
604,230✔
1127
                        return -errno;
58,468✔
1128

1129
                if (ret_newly_created)
545,762✔
1130
                        *ret_newly_created = FLAGS_SET(flags, O_CREAT);
545,762✔
1131
                return fd;
545,762✔
1132
        }
1133

1134
        for (unsigned attempts = 7;;) {
1135
                /* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */
1136
                fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode);
2,069,974✔
1137
                if (fd >= 0) {
2,069,974✔
1138
                        if (ret_newly_created)
1,854,249✔
1139
                                *ret_newly_created = false;
1,854,249✔
1140
                        return fd;
1,854,249✔
1141
                }
1142
                if (errno != ENOENT)
215,725✔
UNCOV
1143
                        return -errno;
×
1144

1145
                /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL/O_NOFOLLOW. */
1146
                fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL | O_NOFOLLOW, mode);
215,725✔
1147
                if (fd >= 0) {
215,725✔
1148
                        if (ret_newly_created)
215,532✔
1149
                                *ret_newly_created = true;
215,531✔
1150
                        return fd;
215,532✔
1151
                }
1152
                if (errno != EEXIST)
193✔
UNCOV
1153
                        return -errno;
×
1154

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

1158
                if (--attempts == 0) /* Give up eventually, somebody is playing with us */
193✔
1159
                        return -EEXIST;
1160
        }
1161
}
1162

1163
int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
1,173,896✔
1164
        _cleanup_close_ int fd = -EBADF;
1,173,896✔
1165
        bool made_dir = false, made_file = false;
1,173,896✔
1166
        int r;
1,173,896✔
1167

1168
        assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
1,173,896✔
1169

1170
        /* An inode cannot be both a directory and a regular file at the same time. */
1171
        assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR)));
1,173,896✔
1172

1173
        /* This is like openat(), but has a few tricks up its sleeves, extending behaviour:
1174
         *
1175
         *   • O_DIRECTORY|O_CREAT is supported, which causes a directory to be created, and immediately
1176
         *     opened. When used with the XO_SUBVOLUME flag this will even create a btrfs subvolume.
1177
         *
1178
         *   • If O_CREAT is used with XO_LABEL, any created file will be immediately relabelled.
1179
         *
1180
         *   • If the path is specified NULL or empty, behaves like fd_reopen().
1181
         *
1182
         *   • If XO_NOCOW is specified will turn on the NOCOW btrfs flag on the file, if available.
1183
         *
1184
         *   • if XO_REGULAR is specified will return an error if inode is not a regular file.
1185
         *
1186
         *   • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files.
1187
         *
1188
         *   • The dir fd can be passed as XAT_FDROOT, in which case any relative paths will be taken relative to the root fs.
1189
         */
1190

1191
        _cleanup_close_ int _dir_fd = -EBADF;
1,173,896✔
1192
        if (dir_fd == XAT_FDROOT) {
1,173,896✔
1193
                if (path_is_absolute(path))
3✔
1194
                        dir_fd = AT_FDCWD;
1195
                else {
1196
                        _dir_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_RDONLY);
2✔
1197
                        if (_dir_fd < 0)
2✔
UNCOV
1198
                                return -errno;
×
1199

1200
                        dir_fd = _dir_fd;
1201
                }
1202
        }
1203

1204
        if (mode == MODE_INVALID)
1,173,896✔
1205
                mode = (open_flags & O_DIRECTORY) ? 0755 : 0644;
274,754✔
1206

1207
        if (isempty(path)) {
1,173,896✔
1208
                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
6,545✔
1209

1210
                if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
6,545✔
1211
                        r = fd_verify_regular(dir_fd);
46✔
1212
                        if (r < 0)
46✔
1213
                                return r;
1214
                }
1215

1216
                return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
6,545✔
1217
        }
1218

1219
        bool call_label_ops_post = false;
1,167,351✔
1220

1221
        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
1,167,351✔
1222
                r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG);
581✔
1223
                if (r < 0)
581✔
1224
                        return r;
1225

1226
                call_label_ops_post = true;
1227
        }
1228

1229
        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
1,167,351✔
1230
                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
256,472✔
1231
                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
2✔
1232
                else
1233
                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
256,470✔
1234
                if (r == -EEXIST) {
163,736✔
1235
                        if (FLAGS_SET(open_flags, O_EXCL))
163,730✔
1236
                                return -EEXIST;
1237
                } else if (r < 0)
92,742✔
1238
                        return r;
1239
                else
1240
                        made_dir = true;
1241

1242
                open_flags &= ~(O_EXCL|O_CREAT);
251,873✔
1243
        }
1244

1245
        if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
1,162,752✔
1246
                /* Guarantee we return a regular fd only, and don't open the file unless we verified it
1247
                 * first */
1248

1249
                if (FLAGS_SET(open_flags, O_PATH)) {
414,711✔
1250
                        fd = openat(dir_fd, path, open_flags, mode);
1✔
1251
                        if (fd < 0) {
1✔
UNCOV
1252
                                r = -errno;
×
UNCOV
1253
                                goto error;
×
1254
                        }
1255

1256
                        r = fd_verify_regular(fd);
1✔
1257
                        if (r < 0)
1✔
UNCOV
1258
                                goto error;
×
1259

1260
                } else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) {
414,710✔
1261
                        /* In O_EXCL mode we can just create the thing, everything is dealt with for us */
1262
                        fd = openat(dir_fd, path, open_flags, mode);
2✔
1263
                        if (fd < 0) {
2✔
1264
                                r = -errno;
1✔
1265
                                goto error;
1✔
1266
                        }
1267

1268
                        made_file = true;
1✔
1269
                } else {
1270
                        /* Otherwise pin the inode first via O_PATH */
1271
                        _cleanup_close_ int inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|(open_flags & O_NOFOLLOW));
414,708✔
1272
                        if (inode_fd < 0) {
414,708✔
1273
                                if (errno != ENOENT || !FLAGS_SET(open_flags, O_CREAT)) {
73✔
1274
                                        r = -errno;
51✔
1275
                                        goto error;
51✔
1276
                                }
1277

1278
                                /* Doesn't exist yet, then try to create it */
1279
                                fd = openat(dir_fd, path, open_flags|O_CREAT|O_EXCL, mode);
22✔
1280
                                if (fd < 0) {
22✔
UNCOV
1281
                                        r = -errno;
×
UNCOV
1282
                                        goto error;
×
1283
                                }
1284

1285
                                made_file = true;
22✔
1286
                        } else {
1287
                                /* OK, we pinned it. Now verify it's actually a regular file, and then reopen it */
1288
                                r = fd_verify_regular(inode_fd);
414,635✔
1289
                                if (r < 0)
414,635✔
1290
                                        goto error;
34✔
1291

1292
                                fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT));
414,601✔
1293
                                if (fd < 0) {
414,601✔
UNCOV
1294
                                        r = fd;
×
UNCOV
1295
                                        goto error;
×
1296
                                }
1297
                        }
1298
                }
1299
        } else {
1300
                fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
748,041✔
1301
                if (fd < 0) {
748,041✔
1302
                        r = fd;
56,848✔
1303
                        goto error;
56,848✔
1304
                }
1305
        }
1306

1307
        if (call_label_ops_post) {
1,105,818✔
1308
                call_label_ops_post = false;
581✔
1309

1310
                r = label_ops_post(fd, /* path= */ NULL, made_file || made_dir);
581✔
1311
                if (r < 0)
581✔
UNCOV
1312
                        goto error;
×
1313
        }
1314

1315
        if (FLAGS_SET(xopen_flags, XO_NOCOW)) {
1,105,818✔
1316
                r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
91✔
1317
                if (r < 0 && !ERRNO_IS_IOCTL_NOT_SUPPORTED(r))
91✔
UNCOV
1318
                        goto error;
×
1319
        }
1320

1321
        return TAKE_FD(fd);
1322

1323
error:
56,934✔
1324
        if (call_label_ops_post)
56,934✔
UNCOV
1325
                (void) label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : path, made_dir || made_file);
×
1326

1327
        if (made_dir || made_file)
56,934✔
UNCOV
1328
                (void) unlinkat(dir_fd, path, made_dir ? AT_REMOVEDIR : 0);
×
1329

1330
        return r;
1331
}
1332

1333
int xopenat_lock_full(
248,347✔
1334
                int dir_fd,
1335
                const char *path,
1336
                int open_flags,
1337
                XOpenFlags xopen_flags,
1338
                mode_t mode,
1339
                LockType locktype,
1340
                int operation) {
1341

1342
        _cleanup_close_ int fd = -EBADF;
248,347✔
1343
        int r;
248,347✔
1344

1345
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
248,347✔
1346
        assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
248,347✔
1347

1348
        /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
1349
         * the same error here). */
1350
        if (FLAGS_SET(open_flags, O_DIRECTORY) && !IN_SET(locktype, LOCK_BSD, LOCK_NONE))
248,347✔
1351
                return -EBADF;
1352

1353
        for (;;) {
279,886✔
1354
                struct stat st;
264,116✔
1355

1356
                fd = xopenat_full(dir_fd, path, open_flags, xopen_flags, mode);
264,116✔
1357
                if (fd < 0)
264,116✔
1358
                        return fd;
8✔
1359

1360
                r = lock_generic(fd, locktype, operation);
264,116✔
1361
                if (r < 0)
264,116✔
1362
                        return r;
1363

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

1368
                if (fstat(fd, &st) < 0)
264,108✔
UNCOV
1369
                        return -errno;
×
1370
                if (st.st_nlink > 0)
264,108✔
1371
                        break;
1372

1373
                fd = safe_close(fd);
15,770✔
1374
        }
1375

1376
        return TAKE_FD(fd);
248,338✔
1377
}
1378

1379
int link_fd(int fd, int newdirfd, const char *newpath) {
26,970✔
1380
        int r;
26,970✔
1381

1382
        assert(fd >= 0);
26,970✔
1383
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
26,970✔
1384
        assert(newpath);
26,970✔
1385

1386
        /* Try to link via AT_EMPTY_PATH first. This fails with ENOENT if we don't have CAP_DAC_READ_SEARCH
1387
         * on kernels < 6.10, in which case we'd then resort to /proc/self/fd/ dance.
1388
         *
1389
         * See also: https://github.com/torvalds/linux/commit/42bd2af5950456d46fdaa91c3a8fb02e680f19f5 */
1390
        r = RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
26,970✔
1391
        if (r == -ENOENT) {
5,230✔
UNCOV
1392
                r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
×
UNCOV
1393
                if (r == -ENOENT && proc_mounted() == 0) /* No proc_fd_enoent_errno() here because we don't
×
1394
                                                            know if it's the target path that's missing. */
UNCOV
1395
                        return -ENOSYS;
×
1396
        }
1397

1398
        return r;
1399
}
1400

1401
int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
20,945✔
1402
        _cleanup_close_ int old_fd = -EBADF;
20,945✔
1403
        int r;
20,945✔
1404

1405
        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
20,945✔
1406
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
20,945✔
1407
        assert(!isempty(newpath)); /* source path is optional, but the target path is not */
20,945✔
1408

1409
        /* Like linkat() but replaces the target if needed. Is a NOP if source and target already share the
1410
         * same inode. */
1411

1412
        if (olddirfd == AT_FDCWD && isempty(oldpath)) /* Refuse operating on the cwd (which is a dir, and dirs can't be hardlinked) */
41,890✔
1413
                return -EISDIR;
1414

1415
        if (path_implies_directory(oldpath)) /* Refuse these definite directories early */
20,945✔
1416
                return -EISDIR;
1417

1418
        if (path_implies_directory(newpath))
20,945✔
1419
                return -EISDIR;
1420

1421
        /* First, try to link this directly */
1422
        if (oldpath)
20,945✔
1423
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
139✔
1424
        else
1425
                r = link_fd(olddirfd, newdirfd, newpath);
20,806✔
1426
        if (r >= 0)
20,864✔
1427
                return 0;
15,660✔
1428
        if (r != -EEXIST)
5,285✔
1429
                return r;
1430

1431
        old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC);
5,285✔
1432
        if (old_fd < 0)
5,285✔
1433
                return old_fd;
1434

1435
        struct stat old_st;
5,285✔
1436
        if (fstat(old_fd, &old_st) < 0)
5,285✔
UNCOV
1437
                return -errno;
×
1438

1439
        if (S_ISDIR(old_st.st_mode)) /* Don't bother if we are operating on a directory */
5,285✔
1440
                return -EISDIR;
1441

1442
        struct stat new_st;
5,285✔
1443
        if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0)
5,285✔
1444
                return -errno;
×
1445

1446
        if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */
5,285✔
1447
                return -EEXIST;
1448

1449
        if (stat_inode_same(&old_st, &new_st)) /* Already the same inode? Then shortcut this */
5,285✔
1450
                return 0;
1451

1452
        _cleanup_free_ char *tmp_path = NULL;
5,284✔
1453
        r = tempfn_random(newpath, /* extra= */ NULL, &tmp_path);
5,284✔
1454
        if (r < 0)
5,284✔
1455
                return r;
1456

1457
        r = link_fd(old_fd, newdirfd, tmp_path);
5,284✔
1458
        if (r < 0) {
5,284✔
UNCOV
1459
                if (!ERRNO_IS_PRIVILEGE(r))
×
1460
                        return r;
1461

1462
                /* If that didn't work due to permissions then go via the path of the dentry */
UNCOV
1463
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, tmp_path, 0));
×
UNCOV
1464
                if (r < 0)
×
1465
                        return r;
1466
        }
1467

1468
        r = RET_NERRNO(renameat(newdirfd, tmp_path, newdirfd, newpath));
5,284✔
UNCOV
1469
        if (r < 0) {
×
UNCOV
1470
                (void) unlinkat(newdirfd, tmp_path, /* flags= */ 0);
×
UNCOV
1471
                return r;
×
1472
        }
1473

1474
        return 0;
1475
}
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