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

systemd / systemd / 16791678039

06 Aug 2025 11:10PM UTC coverage: 72.181% (-0.04%) from 72.223%
16791678039

push

github

yuwata
logging: Improve logging messages related to NFTSet.

The 'NFTSet' directive in various units adds and removes entries in nftables
sets, it does not add or remove entire sets. The logging messages should
indicate that an entry was added or removed, not that a set was added or
removed.

2 of 6 new or added lines in 3 files covered. (33.33%)

496 existing lines in 52 files now uncovered.

302228 of 418708 relevant lines covered (72.18%)

647735.83 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) {
88,915✔
32
        char *p;
88,915✔
33
        int r;
88,915✔
34

35
        assert(path);
88,915✔
36
        assert(stop);
88,915✔
37

38
        if (!path_is_safe(path))
88,915✔
39
                return -EINVAL;
40

41
        if (!path_is_safe(stop))
88,914✔
42
                return -EINVAL;
43

44
        p = strdupa_safe(path);
88,913✔
45

46
        for (;;) {
4,260✔
47
                char *slash = NULL;
93,173✔
48

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

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

59
                if (path_startswith_full(stop, p, PATH_STARTSWITH_REFUSE_DOT_DOT))
93,173✔
60
                        return 0;
61

62
                if (rmdir(p) < 0 && errno != ENOENT)
93,037✔
63
                        return -errno;
88,777✔
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,851,019✔
113
        size_t l = PATH_MAX;
3,851,019✔
114

115
        assert(fd >= 0 || fd == AT_FDCWD);
3,851,019✔
116

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

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

129
                n = readlinkat(fd, strempty(p), c, l);
3,851,021✔
130
                if (n < 0)
3,851,019✔
131
                        return -errno;
387,494✔
132

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

136
                        if (ret)
3,463,525✔
137
                                *ret = TAKE_PTR(c);
3,463,525✔
138

139
                        return 0;
3,463,525✔
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) {
535,938✔
152
        _cleanup_free_ char *link = NULL, *name = NULL;
535,938✔
153
        int r;
535,938✔
154

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

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

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

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

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

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

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

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

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

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

229
        do_chown =
208,812✔
230
                (uid != UID_INVALID && st.st_uid != uid) ||
104,406✔
231
                (gid != GID_INVALID && st.st_gid != gid);
10,634✔
232

233
        do_chmod =
208,812✔
234
                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
104,406✔
235
                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
104,398✔
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)
104,406✔
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)
65,482✔
242
                return -EINVAL; /* insist on the right file type if it was specified */
243

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

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

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

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

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

285
int fchmod_opath(int fd, mode_t m) {
17,651✔
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,651✔
298

299
        if (fchmodat2(fd, "", m, AT_EMPTY_PATH) >= 0)
17,651✔
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]) {
100,656✔
315
        /* Similar to fchmod_opath() but for futimens() */
316

317
        assert(fd >= 0);
100,656✔
318

319
        if (utimensat(fd, "", ts, AT_EMPTY_PATH) >= 0)
100,656✔
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) {
87,089✔
338
        assert(path);
87,089✔
339
        assert(st);
87,089✔
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))
87,089✔
343
                return 0;
344

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

348
        if (st->st_mode & 0002)
87,089✔
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)
87,089✔
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,364✔
370
        return RET_NERRNO(faccessat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW));
23,364✔
371
}
372

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

376
        if (stamp == USEC_INFINITY)
68,370✔
377
                return futimens_opath(fd, /* ts= */ NULL);
67,817✔
378

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

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

389
        assert(path);
68,374✔
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)
68,374✔
397
                (void) mkdir_parents(path, 0755);
28,937✔
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);
68,374✔
403
        if (fd < 0) {
68,374✔
404
                if (errno != ENOENT)
53,002✔
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,002✔
410
                if (fd < 0)
53,002✔
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);
68,370✔
418

419
        return RET_GATHER(ret, touch_fd(fd, stamp));
68,370✔
420
}
421

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

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

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

433
        if (make_relative) {
288✔
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) {
288✔
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) {
139,403✔
461
        int r;
139,403✔
462

463
        assert(from);
139,403✔
464
        assert(to);
139,403✔
465

466
        _cleanup_free_ char *relpath = NULL;
139,403✔
467
        if (FLAGS_SET(flags, SYMLINK_MAKE_RELATIVE)) {
139,403✔
468
                r = path_make_relative_parent(to, from, &relpath);
136,977✔
469
                if (r < 0)
136,977✔
470
                        return r;
471

472
                from = relpath;
136,977✔
473
        }
474

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

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

486
                call_label_ops_post = true;
487
        }
488

489
        r = RET_NERRNO(symlinkat(from, atfd, t));
139,403✔
490
        if (call_label_ops_post)
139,403✔
491
                RET_GATHER(r, label_ops_post(atfd, t, /* created= */ r >= 0));
139,016✔
492
        if (r < 0)
139,403✔
UNCOV
493
                return r;
×
494

495
        r = RET_NERRNO(renameat(atfd, t, atfd, to));
139,404✔
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,320✔
589
        int r, ret = 0;
2,320✔
590

591
        assert(ret_path);
2,320✔
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,277✔
596
                const char *e;
6,958✔
597

598
                e = secure_getenv(n);
6,958✔
599
                if (!e)
6,958✔
600
                        continue;
6,956✔
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,319✔
628
                return ret;
629

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

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

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

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

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

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

657
int var_tmp_dir(const char **ret) {
2,211✔
658
        assert(ret);
2,211✔
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,211✔
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) {
184✔
678
        assert(filename);
184✔
679

680
        if (unlink(filename) < 0 && errno != ENOENT)
184✔
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) {
212,667✔
691
        PROTECT_ERRNO;
212,667✔
692

693
        if (!p)
212,667✔
694
                return NULL;
695

696
        (void) rmdir(p);
212,667✔
697
        return mfree(p);
212,667✔
698
}
699

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

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

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

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

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

715
        if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0)
26,658✔
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,868,536✔
845
        _cleanup_free_ char *parent = NULL;
1,868,536✔
846
        int r;
1,868,536✔
847

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

851
        r = path_extract_directory(path, &parent);
1,868,536✔
852
        if (r == -EDESTADDRREQ) {
1,868,536✔
853
                parent = strdup(".");
49✔
854
                if (!parent)
49✔
855
                        return -ENOMEM;
856
        } else if (r == -EADDRNOTAVAIL) {
1,868,487✔
857
                parent = strdup(path);
×
858
                if (!parent)
×
859
                        return -ENOMEM;
860
        } else if (r < 0)
1,868,487✔
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,868,536✔
867
                flags |= O_DIRECTORY;
×
868
        else if (!FLAGS_SET(flags, O_TMPFILE))
1,868,536✔
869
                flags |= O_DIRECTORY|O_RDONLY;
1,860,760✔
870

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

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

878
        _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF;
39,216✔
879
        struct stat old_stat, new_stat;
39,216✔
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,216✔
889
        if (old_fd < 0)
39,216✔
890
                goto do_rename;
×
891

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

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

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

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

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

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

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

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

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

926
                        l2 = read(new_fd, buf2, l1);
7✔
927
                else {
928
                        assert((size_t) l1 < sizeof(buf1));
24,983✔
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,983✔
935
                        l2 = read(new_fd, buf2, l1 + 1);
24,983✔
936
                }
937
                if (l2 != l1)
24,990✔
938
                        goto do_rename;
×
939

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

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

948
is_same:
24,681✔
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,681✔
953
                goto do_rename;
×
954

955
        return 0;
956

957
do_rename:
14,535✔
958
        if (renameat(olddirfd, oldpath, newdirfd, newpath) < 0)
14,535✔
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) {
214,958✔
1060
        _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
214,958✔
1061
        _cleanup_free_ char *fname = NULL, *parent = NULL;
214,958✔
1062
        int r;
214,958✔
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))
214,958✔
1069
                return -EINVAL;
1070
        if ((flags & O_ACCMODE_STRICT) != O_RDONLY)
214,958✔
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);
214,958✔
1079
        if (r < 0) {
214,958✔
1080
                if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
5,598✔
1081
                        return r;
1082
        } else {
1083
                r = path_extract_filename(path, &fname);
209,360✔
1084
                if (r < 0)
209,360✔
1085
                        return r;
1086

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

1091
                dirfd = parent_fd;
209,360✔
1092
                path = fname;
209,360✔
1093
        }
1094

1095
        fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode);
214,958✔
1096
        if (IN_SET(fd, -ELOOP, -ENOTDIR))
214,958✔
1097
                return -EEXIST;
1098
        if (fd < 0)
214,957✔
1099
                return fd;
4,283✔
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,568,542✔
1105
        int fd;
2,568,542✔
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,568,542✔
1117
                fd = openat(dirfd, pathname, flags, mode);
483,806✔
1118
                if (fd < 0)
483,806✔
1119
                        return -errno;
39,617✔
1120

1121
                if (ret_newly_created)
444,189✔
1122
                        *ret_newly_created = FLAGS_SET(flags, O_CREAT);
444,189✔
1123
                return fd;
444,189✔
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);
2,084,905✔
1129
                if (fd >= 0) {
2,084,905✔
1130
                        if (ret_newly_created)
1,871,741✔
1131
                                *ret_newly_created = false;
1,871,741✔
1132
                        return fd;
1,871,741✔
1133
                }
1134
                if (errno != ENOENT)
213,164✔
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);
213,164✔
1139
                if (fd >= 0) {
213,164✔
1140
                        if (ret_newly_created)
212,993✔
1141
                                *ret_newly_created = true;
212,992✔
1142
                        return fd;
212,993✔
1143
                }
1144
                if (errno != EEXIST)
171✔
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 */
171✔
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,045,359✔
1156
        _cleanup_close_ int fd = -EBADF;
1,045,359✔
1157
        bool made_dir = false, made_file = false;
1,045,359✔
1158
        int r;
1,045,359✔
1159

1160
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,045,359✔
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,045,359✔
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,045,359✔
1182
                mode = (open_flags & O_DIRECTORY) ? 0755 : 0644;
41,418✔
1183

1184
        if (isempty(path)) {
1,045,359✔
1185
                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
6,161✔
1186

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

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

1196
        bool call_label_ops_post = false;
1,039,198✔
1197

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

1203
                call_label_ops_post = true;
1204
        }
1205

1206
        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
1,039,198✔
1207
                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
249,691✔
1208
                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
×
1209
                else
1210
                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
249,691✔
1211
                if (r == -EEXIST) {
134,942✔
1212
                        if (FLAGS_SET(open_flags, O_EXCL))
134,938✔
1213
                                return -EEXIST;
1214
                } else if (r < 0)
114,753✔
1215
                        return r;
1216
                else
1217
                        made_dir = true;
1218

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

1222
        if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
1,034,914✔
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,036✔
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,035✔
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,033✔
1249
                        if (inode_fd < 0) {
403,033✔
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,012✔
1266
                                if (r < 0)
403,012✔
1267
                                        goto error;
5✔
1268

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

1284
        if (call_label_ops_post) {
996,886✔
1285
                call_label_ops_post = false;
682✔
1286

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

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

1298
        return TAKE_FD(fd);
1299

1300
error:
38,028✔
1301
        if (call_label_ops_post)
38,028✔
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,028✔
1305
                (void) unlinkat(dir_fd, path, made_dir ? AT_REMOVEDIR : 0);
×
1306

1307
        return r;
1308
}
1309

1310
int xopenat_lock_full(
244,529✔
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;
244,529✔
1320
        int r;
244,529✔
1321

1322
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
244,529✔
1323
        assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
244,529✔
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))
244,529✔
1328
                return -EBADF;
1329

1330
        for (;;) {
281,102✔
1331
                struct stat st;
262,815✔
1332

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

1337
                r = lock_generic(fd, locktype, operation);
262,815✔
1338
                if (r < 0)
262,815✔
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)
262,807✔
1346
                        return -errno;
×
1347
                if (st.st_nlink > 0)
262,807✔
1348
                        break;
1349

1350
                fd = safe_close(fd);
18,287✔
1351
        }
1352

1353
        return TAKE_FD(fd);
244,520✔
1354
}
1355

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

1359
        assert(fd >= 0);
12,605✔
1360
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
12,605✔
1361
        assert(newpath);
12,605✔
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,605✔
1368
        if (r == -ENOENT) {
4,925✔
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,823✔
1379
        _cleanup_close_ int old_fd = -EBADF;
6,823✔
1380
        int r;
6,823✔
1381

1382
        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
6,823✔
1383
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
6,823✔
1384
        assert(!isempty(newpath)); /* source path is optional, but the target path is not */
6,823✔
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,646✔
1390
                return -EISDIR;
1391

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

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

1398
        /* First, try to link this directly */
1399
        if (oldpath)
6,823✔
1400
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
19✔
1401
        else
1402
                r = link_fd(olddirfd, newdirfd, newpath);
6,804✔
1403
        if (r >= 0)
6,806✔
1404
                return 0;
1,899✔
1405
        if (r != -EEXIST)
4,924✔
1406
                return r;
1407

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

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

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

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

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

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

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

1434
        r = link_fd(old_fd, newdirfd, tmp_path);
4,923✔
1435
        if (r < 0) {
4,923✔
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));
4,923✔
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