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

systemd / systemd / 17750153392

15 Sep 2025 06:38PM UTC coverage: 72.241% (-0.03%) from 72.266%
17750153392

push

github

bluca
Revert "TEST-55-OOMD: Verify that ExecStopPost= runs on oom-kill"

The test consistently fails on CentOS 9:

TEST-55-OOMD.sh[678]: + test -f /run/testbloat-exec-stop-post
[FAILED] Failed to start TEST-55-OOMD.service.

https://github.com/systemd/systemd/actions/runs/17689186773/job/50293446228?pr=38911

This reverts commit 5bf7438ff.

302589 of 418863 relevant lines covered (72.24%)

1047733.04 hits per line

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

80.23
/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) {
93,769✔
32
        char *p;
93,769✔
33
        int r;
93,769✔
34

35
        assert(path);
93,769✔
36
        assert(stop);
93,769✔
37

38
        if (!path_is_safe(path))
93,769✔
39
                return -EINVAL;
40

41
        if (!path_is_safe(stop))
93,768✔
42
                return -EINVAL;
43

44
        p = strdupa_safe(path);
93,767✔
45

46
        for (;;) {
4,414✔
47
                char *slash = NULL;
98,181✔
48

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

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

59
                if (path_startswith_full(stop, p, PATH_STARTSWITH_REFUSE_DOT_DOT))
98,181✔
60
                        return 0;
61

62
                if (rmdir(p) < 0 && errno != ENOENT)
98,045✔
63
                        return -errno;
93,631✔
64
        }
65
}
66

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

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

75
        /* Try the ideal approach first */
76
        if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) >= 0)
94✔
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)
36✔
82
                return -errno;
36✔
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) {
3,862,661✔
113
        size_t l = PATH_MAX;
3,862,661✔
114

115
        assert(fd >= 0 || fd == AT_FDCWD);
3,862,661✔
116

117
        if (fd < 0 && isempty(p))
3,862,661✔
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 (;;) {
3,862,661✔
122
                _cleanup_free_ char *c = NULL;
3,862,661✔
123
                ssize_t n;
3,862,661✔
124

125
                c = new(char, l+1);
3,862,661✔
126
                if (!c)
3,862,661✔
127
                        return -ENOMEM;
128

129
                n = readlinkat(fd, strempty(p), c, l);
3,862,663✔
130
                if (n < 0)
3,862,661✔
131
                        return -errno;
393,059✔
132

133
                if ((size_t) n < l) {
3,469,602✔
134
                        c[n] = 0;
3,469,602✔
135

136
                        if (ret)
3,469,602✔
137
                                *ret = TAKE_PTR(c);
3,469,602✔
138

139
                        return 0;
3,469,602✔
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) {
541,480✔
152
        _cleanup_free_ char *link = NULL, *name = NULL;
541,480✔
153
        int r;
541,480✔
154

155
        assert(p);
541,480✔
156
        assert(ret);
541,480✔
157

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

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

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

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

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

179
        r = readlink_malloc(p, &target);
3,822✔
180
        if (r < 0)
3,822✔
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) {
24,689✔
187
        _cleanup_close_ int fd = -EBADF;
24,689✔
188

189
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
24,689✔
190

191
        if (path) {
24,689✔
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);
24,689✔
194
                if (fd < 0)
24,689✔
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);
24,689✔
207
}
208

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

229
        do_chown =
212,038✔
230
                (uid != UID_INVALID && st.st_uid != uid) ||
106,019✔
231
                (gid != GID_INVALID && st.st_gid != gid);
10,644✔
232

233
        do_chmod =
212,038✔
234
                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
106,019✔
235
                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
106,011✔
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)
106,019✔
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)
66,978✔
242
                return -EINVAL; /* insist on the right file type if it was specified */
243

244
        if (do_chown && do_chmod) {
106,015✔
245
                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
14,696✔
246

247
                if (((minimal ^ st.st_mode) & 07777) != 0) {
14,696✔
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)
106,015✔
261
                if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0)
14,699✔
262
                        return -errno;
1✔
263

264
        if (do_chmod) {
106,014✔
265
                r = fchmod_opath(fd, mode & 07777);
14,880✔
266
                if (r < 0) {
14,880✔
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;
106,014✔
277
}
278

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

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

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

297
        assert(fd >= 0);
17,617✔
298

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

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

308
                return proc_fd_enoent_errno();
×
309
        }
310

311
        return 0;
×
312
}
313

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

317
        assert(fd >= 0);
103,031✔
318

319
        if (utimensat(fd, "", ts, AT_EMPTY_PATH) >= 0)
103,031✔
320
                return 0;
321
        if (errno != EINVAL)
×
322
                return -errno;
×
323

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

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

331
                return proc_fd_enoent_errno();
×
332
        }
333

334
        return 0;
×
335
}
336

337
int stat_warn_permissions(const char *path, const struct stat *st) {
88,740✔
338
        assert(path);
88,740✔
339
        assert(st);
88,740✔
340

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

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

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

351
        if (getpid_cached() == 1 && (st->st_mode & 0044) != 0044)
88,740✔
352
                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);
13✔
353

354
        return 0;
355
}
356

357
int fd_warn_permissions(const char *path, int fd) {
×
358
        struct stat st;
×
359

360
        assert(path);
×
361
        assert(fd >= 0);
×
362

363
        if (fstat(fd, &st) < 0)
×
364
                return -errno;
×
365

366
        return stat_warn_permissions(path, &st);
×
367
}
368

369
int access_nofollow(const char *path, int mode) {
23,661✔
370
        return RET_NERRNO(faccessat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW));
23,661✔
371
}
372

373
int touch_fd(int fd, usec_t stamp) {
69,714✔
374
        assert(fd >= 0);
69,714✔
375

376
        if (stamp == USEC_INFINITY)
69,714✔
377
                return futimens_opath(fd, /* ts= */ NULL);
69,156✔
378

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

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

389
        assert(path);
69,718✔
390

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

396
        if (parents)
69,718✔
397
                (void) mkdir_parents(path, 0755);
30,160✔
398

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

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

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

419
        return RET_GATHER(ret, touch_fd(fd, stamp));
69,714✔
420
}
421

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

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

430
        assert(from);
289✔
431
        assert(to);
289✔
432

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

438
                from = relpath;
185✔
439
        }
440

441
        if (symlinkat(from, atfd, to) < 0) {
289✔
442
                _cleanup_free_ char *p = NULL;
48✔
443

444
                if (errno != EEXIST)
48✔
445
                        return -errno;
×
446

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

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

457
        return 0;
458
}
459

460
int symlinkat_atomic_full(const char *from, int atfd, const char *to, SymlinkFlags flags) {
146,020✔
461
        int r;
146,020✔
462

463
        assert(from);
146,020✔
464
        assert(to);
146,020✔
465

466
        _cleanup_free_ char *relpath = NULL;
146,020✔
467
        if (FLAGS_SET(flags, SYMLINK_MAKE_RELATIVE)) {
146,020✔
468
                r = path_make_relative_parent(to, from, &relpath);
143,551✔
469
                if (r < 0)
143,551✔
470
                        return r;
471

472
                from = relpath;
143,551✔
473
        }
474

475
        _cleanup_free_ char *t = NULL;
146,020✔
476
        r = tempfn_random(to, NULL, &t);
146,020✔
477
        if (r < 0)
146,020✔
478
                return r;
479

480
        bool call_label_ops_post = false;
146,020✔
481
        if (FLAGS_SET(flags, SYMLINK_LABEL)) {
146,020✔
482
                r = label_ops_pre(atfd, to, S_IFLNK);
145,633✔
483
                if (r < 0)
145,633✔
484
                        return r;
485

486
                call_label_ops_post = true;
487
        }
488

489
        r = RET_NERRNO(symlinkat(from, atfd, t));
146,020✔
490
        if (call_label_ops_post)
146,020✔
491
                RET_GATHER(r, label_ops_post(atfd, t, /* created= */ r >= 0));
145,633✔
492
        if (r < 0)
146,020✔
493
                return r;
×
494

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

501
        return 0;
502
}
503

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

508
        assert(path);
×
509

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

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

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

523
        return 0;
524
}
525

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

530
        assert(path);
1✔
531

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

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

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

546
        return 0;
547
}
548

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

554
        assert(path);
301✔
555

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

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

564
        FOREACH_DIRENT_ALL(de, d, return -errno) {
1,268✔
565
                if (!dirent_is_file(de))
969✔
566
                        continue;
649✔
567

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

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

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

582
        if (ret_list)
299✔
583
                *ret_list = TAKE_PTR(l);
296✔
584

585
        return n;
299✔
586
}
587

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

591
        assert(ret_path);
2,322✔
592

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

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

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

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

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

627
        if (ret < 0)
2,321✔
628
                return ret;
629

630
        *ret_path = NULL;
2,320✔
631
        return ret;
2,320✔
632
}
633

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

638
        assert(def);
2,322✔
639
        assert(ret);
2,322✔
640

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

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

653
        *ret = def;
2,321✔
654
        return 0;
2,321✔
655
}
656

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

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

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

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

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

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

677
int unlink_or_warn(const char *filename) {
185✔
678
        assert(filename);
185✔
679

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

687
        return 0;
688
}
689

690
char *rmdir_and_free(char *p) {
223,984✔
691
        PROTECT_ERRNO;
223,984✔
692

693
        if (!p)
223,984✔
694
                return NULL;
695

696
        (void) rmdir(p);
223,984✔
697
        return mfree(p);
223,984✔
698
}
699

700
char* unlink_and_free(char *p) {
6,523✔
701
        PROTECT_ERRNO;
6,523✔
702

703
        if (!p)
6,523✔
704
                return NULL;
705

706
        (void) unlink(p);
5,921✔
707
        return mfree(p);
5,921✔
708
}
709

710
int access_fd(int fd, int mode) {
27,024✔
711
        assert(fd >= 0);
27,024✔
712

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

715
        if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0)
27,024✔
716
                return 0;
717
        if (errno != EINVAL)
1✔
718
                return -errno;
1✔
719

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

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

727
                return proc_fd_enoent_errno();
×
728
        }
729

730
        return 0;
×
731
}
732

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

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

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

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

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

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

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

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

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

785
        if (!S_ISREG(st.st_mode))
81✔
786
                return 0;
787

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

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

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

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

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

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

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

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

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

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

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

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

841
        return 0;
842
}
843

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

848
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,705,548✔
849
        assert(path);
1,705,548✔
850

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

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

866
        if (FLAGS_SET(flags, O_PATH))
1,705,548✔
867
                flags |= O_DIRECTORY;
×
868
        else if (!FLAGS_SET(flags, O_TMPFILE))
1,705,548✔
869
                flags |= O_DIRECTORY|O_RDONLY;
1,697,682✔
870

871
        return RET_NERRNO(openat(dir_fd, parent, flags, mode));
1,705,579✔
872
}
873

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

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

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

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

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

896
        if (fstat(old_fd, &old_stat) < 0)
35,882✔
897
                goto do_rename;
×
898

899
        if (!S_ISREG(old_stat.st_mode))
35,882✔
900
                goto do_rename;
×
901

902
        if (fstat(new_fd, &new_stat) < 0)
35,882✔
903
                goto do_rename;
×
904

905
        if (stat_inode_same(&new_stat, &old_stat))
35,882✔
906
                goto is_same;
1✔
907

908
        if (old_stat.st_mode != new_stat.st_mode ||
35,881✔
909
            old_stat.st_size != new_stat.st_size ||
35,881✔
910
            old_stat.st_uid != new_stat.st_uid ||
24,994✔
911
            old_stat.st_gid != new_stat.st_gid)
24,994✔
912
                goto do_rename;
10,887✔
913

914
        for (;;) {
3✔
915
                uint8_t buf1[16*1024];
24,997✔
916
                uint8_t buf2[sizeof(buf1)];
24,997✔
917
                ssize_t l1, l2;
24,997✔
918

919
                l1 = read(old_fd, buf1, sizeof(buf1));
24,997✔
920
                if (l1 < 0)
24,997✔
921
                        goto do_rename;
325✔
922

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

926
                        l2 = read(new_fd, buf2, l1);
4✔
927
                else {
928
                        assert((size_t) l1 < sizeof(buf1));
24,993✔
929

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

934
                        assert((size_t) (l1 + 1) <= sizeof(buf2));
24,993✔
935
                        l2 = read(new_fd, buf2, l1 + 1);
24,993✔
936
                }
937
                if (l2 != l1)
24,997✔
938
                        goto do_rename;
×
939

940
                if (memcmp(buf1, buf2, l1) != 0)
24,997✔
941
                        goto do_rename;
325✔
942

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

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

952
        if (unlinkat(olddirfd, oldpath, 0) < 0)
24,670✔
953
                goto do_rename;
×
954

955
        return 0;
956

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

961
        return 1;
962
}
963

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

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

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

981
        return -EINTR;
982
}
983

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

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

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

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

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

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

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

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

1020
        e++;
10✔
1021

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

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

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

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

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

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

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

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

1056
        return 0;
1057
}
1058

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

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

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

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

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

1087
                parent_fd = openat(dirfd, parent, O_PATH|O_DIRECTORY|O_CLOEXEC);
220,753✔
1088
                if (parent_fd < 0)
220,753✔
1089
                        return -errno;
×
1090

1091
                dirfd = parent_fd;
220,753✔
1092
                path = fname;
220,753✔
1093
        }
1094

1095
        fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode);
226,693✔
1096
        if (IN_SET(fd, -ELOOP, -ENOTDIR))
226,693✔
1097
                return -EEXIST;
1098
        if (fd < 0)
226,692✔
1099
                return fd;
4,549✔
1100

1101
        return TAKE_FD(fd);
1102
}
1103

1104
int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) {
2,434,084✔
1105
        int fd;
2,434,084✔
1106

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

1116
        if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) {
2,434,084✔
1117
                fd = openat(dirfd, pathname, flags, mode);
496,838✔
1118
                if (fd < 0)
496,838✔
1119
                        return -errno;
39,666✔
1120

1121
                if (ret_newly_created)
457,172✔
1122
                        *ret_newly_created = FLAGS_SET(flags, O_CREAT);
457,172✔
1123
                return fd;
457,172✔
1124
        }
1125

1126
        for (unsigned attempts = 7;;) {
1127
                /* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */
1128
                fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode);
1,937,451✔
1129
                if (fd >= 0) {
1,937,451✔
1130
                        if (ret_newly_created)
1,712,860✔
1131
                                *ret_newly_created = false;
1,712,860✔
1132
                        return fd;
1,712,860✔
1133
                }
1134
                if (errno != ENOENT)
224,591✔
1135
                        return -errno;
×
1136

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

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

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

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

1160
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,073,965✔
1161

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

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

1181
        if (mode == MODE_INVALID)
1,073,965✔
1182
                mode = (open_flags & O_DIRECTORY) ? 0755 : 0644;
41,612✔
1183

1184
        if (isempty(path)) {
1,073,965✔
1185
                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
6,228✔
1186

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

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

1196
        bool call_label_ops_post = false;
1,067,737✔
1197

1198
        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
1,067,737✔
1199
                r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG);
703✔
1200
                if (r < 0)
703✔
1201
                        return r;
1202

1203
                call_label_ops_post = true;
1204
        }
1205

1206
        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
1,067,737✔
1207
                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
261,465✔
1208
                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
×
1209
                else
1210
                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
261,465✔
1211
                if (r == -EEXIST) {
142,710✔
1212
                        if (FLAGS_SET(open_flags, O_EXCL))
142,706✔
1213
                                return -EEXIST;
1214
                } else if (r < 0)
118,759✔
1215
                        return r;
1216
                else
1217
                        made_dir = true;
1218

1219
                open_flags &= ~(O_EXCL|O_CREAT);
256,915✔
1220
        }
1221

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

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

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

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

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

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

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

1269
                                fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT));
403,282✔
1270
                                if (fd < 0) {
403,282✔
1271
                                        r = fd;
×
1272
                                        goto error;
×
1273
                                }
1274
                        }
1275
                }
1276
        } else {
1277
                fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
659,876✔
1278
                if (fd < 0) {
659,876✔
1279
                        r = fd;
38,068✔
1280
                        goto error;
38,068✔
1281
                }
1282
        }
1283

1284
        if (call_label_ops_post) {
1,025,112✔
1285
                call_label_ops_post = false;
703✔
1286

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

1292
        if (FLAGS_SET(xopen_flags, XO_NOCOW)) {
1,025,112✔
1293
                r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
70✔
1294
                if (r < 0 && !ERRNO_IS_IOCTL_NOT_SUPPORTED(r))
70✔
1295
                        goto error;
×
1296
        }
1297

1298
        return TAKE_FD(fd);
1299

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

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

1307
        return r;
1308
}
1309

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

1319
        _cleanup_close_ int fd = -EBADF;
255,904✔
1320
        int r;
255,904✔
1321

1322
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
255,904✔
1323
        assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
255,904✔
1324

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

1330
        for (;;) {
300,861✔
1331
                struct stat st;
278,382✔
1332

1333
                fd = xopenat_full(dir_fd, path, open_flags, xopen_flags, mode);
278,382✔
1334
                if (fd < 0)
278,382✔
1335
                        return fd;
8✔
1336

1337
                r = lock_generic(fd, locktype, operation);
278,382✔
1338
                if (r < 0)
278,382✔
1339
                        return r;
1340

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

1345
                if (fstat(fd, &st) < 0)
278,374✔
1346
                        return -errno;
×
1347
                if (st.st_nlink > 0)
278,374✔
1348
                        break;
1349

1350
                fd = safe_close(fd);
22,479✔
1351
        }
1352

1353
        return TAKE_FD(fd);
255,895✔
1354
}
1355

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

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

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

1375
        return r;
1376
}
1377

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

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

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

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

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

1395
        if (path_implies_directory(newpath))
6,932✔
1396
                return -EISDIR;
1397

1398
        /* First, try to link this directly */
1399
        if (oldpath)
6,932✔
1400
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
39✔
1401
        else
1402
                r = link_fd(olddirfd, newdirfd, newpath);
6,893✔
1403
        if (r >= 0)
6,907✔
1404
                return 0;
1,927✔
1405
        if (r != -EEXIST)
5,005✔
1406
                return r;
1407

1408
        old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC);
5,005✔
1409
        if (old_fd < 0)
5,005✔
1410
                return old_fd;
1411

1412
        struct stat old_st;
5,005✔
1413
        if (fstat(old_fd, &old_st) < 0)
5,005✔
1414
                return -errno;
×
1415

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

1419
        struct stat new_st;
5,005✔
1420
        if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0)
5,005✔
1421
                return -errno;
×
1422

1423
        if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */
5,005✔
1424
                return -EEXIST;
1425

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

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

1434
        r = link_fd(old_fd, newdirfd, tmp_path);
5,004✔
1435
        if (r < 0) {
5,004✔
1436
                if (!ERRNO_IS_PRIVILEGE(r))
×
1437
                        return r;
1438

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

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

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