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

systemd / systemd / 15746219612

18 Jun 2025 03:36PM UTC coverage: 72.045% (-0.04%) from 72.087%
15746219612

push

github

bluca
test-cpu-set-util: use ASSERT_XYZ() macros

119 of 121 new or added lines in 1 file covered. (98.35%)

1096 existing lines in 62 files now uncovered.

300222 of 416715 relevant lines covered (72.04%)

712541.77 hits per line

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

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

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

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

33
int rmdir_parents(const char *path, const char *stop) {
94,622✔
34
        char *p;
94,622✔
35
        int r;
94,622✔
36

37
        assert(path);
94,622✔
38
        assert(stop);
94,622✔
39

40
        if (!path_is_safe(path))
94,622✔
41
                return -EINVAL;
42

43
        if (!path_is_safe(stop))
94,621✔
44
                return -EINVAL;
45

46
        p = strdupa_safe(path);
94,620✔
47

48
        for (;;) {
3,603✔
49
                char *slash = NULL;
98,223✔
50

51
                /* skip the last component. */
52
                r = path_find_last_component(p, /* accept_dot_dot= */ false, (const char **) &slash, NULL);
98,223✔
53
                if (r <= 0)
98,223✔
54
                        return r;
94,620✔
55
                if (slash == p)
98,223✔
56
                        return 0;
57

58
                assert(*slash == '/');
98,223✔
59
                *slash = '\0';
98,223✔
60

61
                if (path_startswith_full(stop, p, PATH_STARTSWITH_REFUSE_DOT_DOT))
98,223✔
62
                        return 0;
63

64
                if (rmdir(p) < 0 && errno != ENOENT)
98,087✔
65
                        return -errno;
94,484✔
66
        }
67
}
68

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

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

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

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

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

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

97
                return 0;
×
98
        }
99

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

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

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

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

114
int readlinkat_malloc(int fd, const char *p, char **ret) {
3,578,742✔
115
        size_t l = PATH_MAX;
3,578,742✔
116

117
        assert(fd >= 0 || fd == AT_FDCWD);
3,578,742✔
118

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

123
        for (;;) {
3,578,742✔
124
                _cleanup_free_ char *c = NULL;
3,578,742✔
125
                ssize_t n;
3,578,742✔
126

127
                c = new(char, l+1);
3,578,742✔
128
                if (!c)
3,578,742✔
129
                        return -ENOMEM;
130

131
                n = readlinkat(fd, strempty(p), c, l);
3,578,744✔
132
                if (n < 0)
3,578,742✔
133
                        return -errno;
436,504✔
134

135
                if ((size_t) n < l) {
3,142,238✔
136
                        c[n] = 0;
3,142,238✔
137

138
                        if (ret)
3,142,238✔
139
                                *ret = TAKE_PTR(c);
3,142,238✔
140

141
                        return 0;
3,142,238✔
142
                }
143

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

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

153
int readlink_value(const char *p, char **ret) {
651,092✔
154
        _cleanup_free_ char *link = NULL, *name = NULL;
651,092✔
155
        int r;
651,092✔
156

157
        assert(p);
651,092✔
158
        assert(ret);
651,092✔
159

160
        r = readlink_malloc(p, &link);
651,092✔
161
        if (r < 0)
651,092✔
162
                return r;
163

164
        r = path_extract_filename(link, &name);
389,231✔
165
        if (r < 0)
389,231✔
166
                return r;
167
        if (r == O_DIRECTORY)
389,231✔
168
                return -EINVAL;
169

170
        *ret = TAKE_PTR(name);
389,231✔
171
        return 0;
389,231✔
172
}
173

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

178
        assert(p);
3,939✔
179
        assert(ret);
3,939✔
180

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

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

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

191
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
24,395✔
192

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

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

208
        return fchmod_and_chown(dir_fd, mode, uid, gid);
24,395✔
209
}
210

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

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

228
        if (fstat(fd, &st) < 0)
110,838✔
229
                return -errno;
×
230

231
        do_chown =
221,676✔
232
                (uid != UID_INVALID && st.st_uid != uid) ||
110,838✔
233
                (gid != GID_INVALID && st.st_gid != gid);
14,331✔
234

235
        do_chmod =
221,676✔
236
                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
110,838✔
237
                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
110,830✔
238
                 do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown()
239
                             * modifies the access mode too */
240

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

246
        if (do_chown && do_chmod) {
110,834✔
247
                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
18,334✔
248

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

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

262
        if (do_chown)
110,834✔
263
                if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0)
18,337✔
264
                        return -errno;
1✔
265

266
        if (do_chmod) {
110,833✔
267
                r = fchmod_opath(fd, mode & 07777);
18,517✔
268
                if (r < 0) {
18,517✔
269
                        if (!path || r != -ENOSYS)
×
270
                                return r;
271

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

278
        return do_chown || do_chmod;
110,833✔
279
}
280

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

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

287
int fchmod_opath(int fd, mode_t m) {
21,444✔
288
        /* This function operates also on fd that might have been opened with
289
         * O_PATH. The tool set we have is non-intuitive:
290
         * - fchmod(2) only operates on open files (i. e., fds with an open file description);
291
         * - fchmodat(2) does not have a flag arg like fchownat(2) does, so no way to pass AT_EMPTY_PATH;
292
         *   + it should not be confused with the libc fchmodat(3) interface, which adds 4th flag argument,
293
         *     but does not support AT_EMPTY_PATH (only supports AT_SYMLINK_NOFOLLOW);
294
         * - fchmodat2(2) supports all the AT_* flags, but is still very recent.
295
         *
296
         * We try to use fchmodat2(), and, if it is not supported, resort
297
         * to the /proc/self/fd dance. */
298

299
        assert(fd >= 0);
21,444✔
300

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

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

310
                return proc_fd_enoent_errno();
×
311
        }
312

313
        return 0;
×
314
}
315

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

319
        assert(fd >= 0);
106,606✔
320

321
        if (utimensat(fd, "", ts, AT_EMPTY_PATH) >= 0)
106,606✔
322
                return 0;
323
        if (errno != EINVAL)
×
324
                return -errno;
×
325

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

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

333
                return proc_fd_enoent_errno();
×
334
        }
335

336
        return 0;
×
337
}
338

339
int stat_warn_permissions(const char *path, const struct stat *st) {
102,497✔
340
        assert(path);
102,497✔
341
        assert(st);
102,497✔
342

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

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

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

353
        if (getpid_cached() == 1 && (st->st_mode & 0044) != 0044)
102,496✔
354
                log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path);
12✔
355

356
        return 0;
357
}
358

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

362
        assert(path);
8,766✔
363
        assert(fd >= 0);
8,766✔
364

365
        if (fstat(fd, &st) < 0)
8,766✔
366
                return -errno;
×
367

368
        return stat_warn_permissions(path, &st);
8,766✔
369
}
370

371
int access_nofollow(const char *path, mode_t mode) {
26,790✔
372
        return RET_NERRNO(faccessat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW));
26,790✔
373
}
374

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

378
        if (stamp == USEC_INFINITY)
71,144✔
379
                return futimens_opath(fd, /* ts= */ NULL);
70,541✔
380

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

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

391
        assert(path);
71,148✔
392

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

398
        if (parents)
71,148✔
399
                (void) mkdir_parents(path, 0755);
31,782✔
400

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

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

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

421
        return RET_GATHER(ret, touch_fd(fd, stamp));
71,144✔
422
}
423

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

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

432
        assert(from);
318✔
433
        assert(to);
318✔
434

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

440
                from = relpath;
185✔
441
        }
442

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

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

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

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

459
        return 0;
460
}
461

462
int symlinkat_atomic_full(const char *from, int atfd, const char *to, bool make_relative) {
159,421✔
463
        _cleanup_free_ char *relpath = NULL, *t = NULL;
159,421✔
464
        int r;
159,421✔
465

466
        assert(from);
159,421✔
467
        assert(to);
159,421✔
468

469
        if (make_relative) {
159,421✔
470
                r = path_make_relative_parent(to, from, &relpath);
156,858✔
471
                if (r < 0)
156,858✔
472
                        return r;
473

474
                from = relpath;
156,858✔
475
        }
476

477
        r = tempfn_random(to, NULL, &t);
159,421✔
478
        if (r < 0)
159,421✔
479
                return r;
480

481
        if (symlinkat(from, atfd, t) < 0)
159,421✔
UNCOV
482
                return -errno;
×
483

484
        r = RET_NERRNO(renameat(atfd, t, atfd, to));
159,422✔
485
        if (r < 0) {
1✔
486
                (void) unlinkat(atfd, t, 0);
1✔
487
                return r;
1✔
488
        }
489

490
        return 0;
491
}
492

493
int mknodat_atomic(int atfd, const char *path, mode_t mode, dev_t dev) {
×
494
        _cleanup_free_ char *t = NULL;
×
495
        int r;
×
496

497
        assert(path);
×
498

499
        r = tempfn_random(path, NULL, &t);
×
500
        if (r < 0)
×
501
                return r;
502

503
        if (mknodat(atfd, t, mode, dev) < 0)
×
504
                return -errno;
×
505

506
        r = RET_NERRNO(renameat(atfd, t, atfd, path));
×
507
        if (r < 0) {
×
508
                (void) unlinkat(atfd, t, 0);
×
509
                return r;
×
510
        }
511

512
        return 0;
513
}
514

515
int mkfifoat_atomic(int atfd, const char *path, mode_t mode) {
1✔
516
        _cleanup_free_ char *t = NULL;
1✔
517
        int r;
1✔
518

519
        assert(path);
1✔
520

521
        /* We're only interested in the (random) filename.  */
522
        r = tempfn_random(path, NULL, &t);
1✔
523
        if (r < 0)
1✔
524
                return r;
525

526
        if (mkfifoat(atfd, t, mode) < 0)
1✔
527
                return -errno;
×
528

529
        r = RET_NERRNO(renameat(atfd, t, atfd, path));
1✔
530
        if (r < 0) {
×
531
                (void) unlinkat(atfd, t, 0);
×
532
                return r;
×
533
        }
534

535
        return 0;
536
}
537

538
int get_files_in_directory(const char *path, char ***ret_list) {
299✔
539
        _cleanup_strv_free_ char **l = NULL;
×
540
        _cleanup_closedir_ DIR *d = NULL;
299✔
541
        size_t n = 0;
299✔
542

543
        assert(path);
299✔
544

545
        /* Returns all files in a directory in *list, and the number
546
         * of files as return value. If list is NULL returns only the
547
         * number. */
548

549
        d = opendir(path);
299✔
550
        if (!d)
299✔
551
                return -errno;
2✔
552

553
        FOREACH_DIRENT_ALL(de, d, return -errno) {
1,257✔
554
                if (!dirent_is_file(de))
960✔
555
                        continue;
645✔
556

557
                if (ret_list) {
315✔
558
                        /* one extra slot is needed for the terminating NULL */
559
                        if (!GREEDY_REALLOC(l, n + 2))
302✔
560
                                return -ENOMEM;
561

562
                        l[n] = strdup(de->d_name);
302✔
563
                        if (!l[n])
302✔
564
                                return -ENOMEM;
565

566
                        l[++n] = NULL;
302✔
567
                } else
568
                        n++;
13✔
569
        }
570

571
        if (ret_list)
297✔
572
                *ret_list = TAKE_PTR(l);
294✔
573

574
        return n;
297✔
575
}
576

577
static int getenv_tmp_dir(const char **ret_path) {
2,320✔
578
        int r, ret = 0;
2,320✔
579

580
        assert(ret_path);
2,320✔
581

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

587
                e = secure_getenv(n);
6,958✔
588
                if (!e)
6,958✔
589
                        continue;
6,956✔
590
                if (!path_is_absolute(e)) {
2✔
591
                        r = -ENOTDIR;
×
592
                        goto next;
×
593
                }
594
                if (!path_is_normalized(e)) {
2✔
595
                        r = -EPERM;
×
596
                        goto next;
×
597
                }
598

599
                r = is_dir(e, true);
2✔
600
                if (r < 0)
2✔
601
                        goto next;
1✔
602
                if (r == 0) {
1✔
603
                        r = -ENOTDIR;
×
604
                        goto next;
×
605
                }
606

607
                *ret_path = e;
1✔
608
                return 1;
1✔
609

610
        next:
1✔
611
                /* Remember first error, to make this more debuggable */
612
                if (ret >= 0)
1✔
613
                        ret = r;
1✔
614
        }
615

616
        if (ret < 0)
2,319✔
617
                return ret;
618

619
        *ret_path = NULL;
2,318✔
620
        return ret;
2,318✔
621
}
622

623
static int tmp_dir_internal(const char *def, const char **ret) {
2,320✔
624
        const char *e;
2,320✔
625
        int r, k;
2,320✔
626

627
        assert(def);
2,320✔
628
        assert(ret);
2,320✔
629

630
        r = getenv_tmp_dir(&e);
2,320✔
631
        if (r > 0) {
2,320✔
632
                *ret = e;
1✔
633
                return 0;
1✔
634
        }
635

636
        k = is_dir(def, /* follow = */ true);
2,319✔
637
        if (k == 0)
2,319✔
638
                k = -ENOTDIR;
639
        if (k < 0)
2,319✔
640
                return RET_GATHER(r, k);
×
641

642
        *ret = def;
2,319✔
643
        return 0;
2,319✔
644
}
645

646
int var_tmp_dir(const char **ret) {
2,211✔
647
        assert(ret);
2,211✔
648

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

654
        return tmp_dir_internal("/var/tmp", ret);
2,211✔
655
}
656

657
int tmp_dir(const char **ret) {
109✔
658
        assert(ret);
109✔
659

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

663
        return tmp_dir_internal("/tmp", ret);
109✔
664
}
665

666
int unlink_or_warn(const char *filename) {
178✔
667
        assert(filename);
178✔
668

669
        if (unlink(filename) < 0 && errno != ENOENT)
178✔
670
                /* If the file doesn't exist and the fs simply was read-only (in which
671
                 * case unlink() returns EROFS even if the file doesn't exist), don't
672
                 * complain */
673
                if (errno != EROFS || access(filename, F_OK) >= 0)
×
674
                        return log_error_errno(errno, "Failed to remove \"%s\": %m", filename);
×
675

676
        return 0;
677
}
678

679
char *rmdir_and_free(char *p) {
248,627✔
680
        PROTECT_ERRNO;
248,627✔
681

682
        if (!p)
248,627✔
683
                return NULL;
684

685
        (void) rmdir(p);
248,627✔
686
        return mfree(p);
248,627✔
687
}
688

689
char* unlink_and_free(char *p) {
6,498✔
690
        PROTECT_ERRNO;
6,498✔
691

692
        if (!p)
6,498✔
693
                return NULL;
694

695
        (void) unlink(p);
5,906✔
696
        return mfree(p);
5,906✔
697
}
698

699
int access_fd(int fd, int mode) {
27,524✔
700
        assert(fd >= 0);
27,524✔
701

702
        /* Like access() but operates on an already open fd */
703

704
        if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0)
27,524✔
705
                return 0;
706
        if (errno != EINVAL)
477✔
707
                return -errno;
477✔
708

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

712
        if (access(FORMAT_PROC_FD_PATH(fd), mode) < 0) {
×
713
                if (errno != ENOENT)
×
714
                        return -errno;
×
715

716
                return proc_fd_enoent_errno();
×
717
        }
718

719
        return 0;
×
720
}
721

722
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
75✔
723
        _cleanup_close_ int truncate_fd = -EBADF;
75✔
724
        struct stat st;
75✔
725
        off_t l, bs;
75✔
726

727
        assert(fd >= 0 || fd == AT_FDCWD);
75✔
728
        assert(name);
75✔
729
        assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0);
75✔
730

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

747
        if (!FLAGS_SET(flags, UNLINK_REMOVEDIR)) {
75✔
748
                truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
75✔
749
                if (truncate_fd < 0) {
75✔
750

751
                        /* If this failed because the file doesn't exist propagate the error right-away. Also,
752
                         * AT_REMOVEDIR wasn't set, and we tried to open the file for writing, which means EISDIR is
753
                         * returned when this is a directory but we are not supposed to delete those, hence propagate
754
                         * the error right-away too. */
755
                        if (IN_SET(errno, ENOENT, EISDIR))
×
756
                                return -errno;
×
757

758
                        if (errno != ELOOP) /* don't complain if this is a symlink */
×
759
                                log_debug_errno(errno, "Failed to open file '%s' for deallocation, ignoring: %m", name);
×
760
                }
761
        }
762

763
        if (unlinkat(fd, name, FLAGS_SET(flags, UNLINK_REMOVEDIR) ? AT_REMOVEDIR : 0) < 0)
75✔
764
                return -errno;
×
765

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

769
        if (fstat(truncate_fd, &st) < 0) {
75✔
770
                log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
771
                return 0;
×
772
        }
773

774
        if (!S_ISREG(st.st_mode))
75✔
775
                return 0;
776

777
        if (FLAGS_SET(flags, UNLINK_ERASE) && st.st_size > 0 && st.st_nlink == 0) {
75✔
778
                uint64_t left = st.st_size;
3✔
779
                char buffer[64 * 1024];
3✔
780

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

789
                random_bytes(buffer, sizeof(buffer));
3✔
790

791
                while (left > 0) {
6✔
792
                        ssize_t n;
3✔
793

794
                        n = write(truncate_fd, buffer, MIN(sizeof(buffer), left));
3✔
795
                        if (n < 0) {
3✔
796
                                log_debug_errno(errno, "Failed to erase data in file '%s', ignoring.", name);
×
797
                                break;
798
                        }
799

800
                        assert(left >= (size_t) n);
3✔
801
                        left -= n;
3✔
802
                }
803

804
                /* Let's refresh metadata */
805
                if (fstat(truncate_fd, &st) < 0) {
3✔
806
                        log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
807
                        return 0;
×
808
                }
809
        }
810

811
        /* Don't deallocate if there's nothing to deallocate or if the file is linked elsewhere */
812
        if (st.st_blocks == 0 || st.st_nlink > 0)
75✔
813
                return 0;
814

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

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

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

824
        /* Fall back to truncation */
825
        if (ftruncate(truncate_fd, 0) < 0) {
×
826
                log_debug_errno(errno, "Failed to truncate file to 0, ignoring: %m");
×
827
                return 0;
×
828
        }
829

830
        return 0;
831
}
832

833
int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
1,812,364✔
834
        _cleanup_free_ char *parent = NULL;
1,812,364✔
835
        int r;
1,812,364✔
836

837
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,812,364✔
838
        assert(path);
1,812,364✔
839

840
        r = path_extract_directory(path, &parent);
1,812,364✔
841
        if (r == -EDESTADDRREQ) {
1,812,364✔
842
                parent = strdup(".");
45✔
843
                if (!parent)
45✔
844
                        return -ENOMEM;
845
        } else if (r == -EADDRNOTAVAIL) {
1,812,319✔
846
                parent = strdup(path);
×
847
                if (!parent)
×
848
                        return -ENOMEM;
849
        } else if (r < 0)
1,812,319✔
850
                return r;
851

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

855
        if (FLAGS_SET(flags, O_PATH))
1,812,364✔
856
                flags |= O_DIRECTORY;
×
857
        else if (!FLAGS_SET(flags, O_TMPFILE))
1,812,364✔
858
                flags |= O_DIRECTORY|O_RDONLY;
1,804,867✔
859

860
        return RET_NERRNO(openat(dir_fd, parent, flags, mode));
1,812,397✔
861
}
862

863
int conservative_renameat(
40,151✔
864
                int olddirfd, const char *oldpath,
865
                int newdirfd, const char *newpath) {
866

867
        _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF;
40,151✔
868
        struct stat old_stat, new_stat;
40,151✔
869

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

877
        old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
40,151✔
878
        if (old_fd < 0)
40,151✔
879
                goto do_rename;
×
880

881
        new_fd = openat(newdirfd, newpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
40,151✔
882
        if (new_fd < 0)
40,151✔
883
                goto do_rename;
3,456✔
884

885
        if (fstat(old_fd, &old_stat) < 0)
36,695✔
886
                goto do_rename;
×
887

888
        if (!S_ISREG(old_stat.st_mode))
36,695✔
889
                goto do_rename;
×
890

891
        if (fstat(new_fd, &new_stat) < 0)
36,695✔
892
                goto do_rename;
×
893

894
        if (stat_inode_same(&new_stat, &old_stat))
36,695✔
895
                goto is_same;
1✔
896

897
        if (old_stat.st_mode != new_stat.st_mode ||
36,694✔
898
            old_stat.st_size != new_stat.st_size ||
36,694✔
899
            old_stat.st_uid != new_stat.st_uid ||
25,778✔
900
            old_stat.st_gid != new_stat.st_gid)
25,778✔
901
                goto do_rename;
10,916✔
902

903
        for (;;) {
3✔
904
                uint8_t buf1[16*1024];
25,781✔
905
                uint8_t buf2[sizeof(buf1)];
25,781✔
906
                ssize_t l1, l2;
25,781✔
907

908
                l1 = read(old_fd, buf1, sizeof(buf1));
25,781✔
909
                if (l1 < 0)
25,781✔
910
                        goto do_rename;
317✔
911

912
                if (l1 == sizeof(buf1))
25,781✔
913
                        /* Read the full block, hence read a full block in the other file too */
914

915
                        l2 = read(new_fd, buf2, l1);
4✔
916
                else {
917
                        assert((size_t) l1 < sizeof(buf1));
25,777✔
918

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

923
                        assert((size_t) (l1 + 1) <= sizeof(buf2));
25,777✔
924
                        l2 = read(new_fd, buf2, l1 + 1);
25,777✔
925
                }
926
                if (l2 != l1)
25,781✔
927
                        goto do_rename;
×
928

929
                if (memcmp(buf1, buf2, l1) != 0)
25,781✔
930
                        goto do_rename;
317✔
931

932
                if ((size_t) l1 < sizeof(buf1)) /* We hit EOF on the first file, and the second file too, hence exit
25,464✔
933
                                                 * now. */
934
                        break;
935
        }
936

937
is_same:
25,462✔
938
        /* Everything matches? Then don't rename, instead remove the source file, and leave the existing
939
         * destination in place */
940

941
        if (unlinkat(olddirfd, oldpath, 0) < 0)
25,462✔
942
                goto do_rename;
×
943

944
        return 0;
945

946
do_rename:
14,689✔
947
        if (renameat(olddirfd, oldpath, newdirfd, newpath) < 0)
14,689✔
948
                return -errno;
×
949

950
        return 1;
951
}
952

953
int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) {
1,687✔
954
        RateLimit rl;
1,687✔
955
        int r;
1,687✔
956

957
        r = posix_fallocate(fd, offset, size); /* returns positive errnos on error */
1,687✔
958
        if (r != EINTR)
1,687✔
959
                return -r; /* Let's return negative errnos, like common in our codebase */
1,687✔
960

961
        /* On EINTR try a couple of times more, but protect against busy looping
962
         * (not more than 16 times per 10s) */
963
        rl = (const RateLimit) { 10 * USEC_PER_SEC, 16 };
×
964
        while (ratelimit_below(&rl)) {
×
965
                r = posix_fallocate(fd, offset, size);
×
966
                if (r != EINTR)
×
967
                        return -r;
×
968
        }
969

970
        return -EINTR;
971
}
972

973
int parse_cifs_service(
15✔
974
                const char *s,
975
                char **ret_host,
976
                char **ret_service,
977
                char **ret_path) {
978

979
        _cleanup_free_ char *h = NULL, *ss = NULL, *x = NULL;
15✔
980
        const char *p, *e, *d;
15✔
981
        char delimiter;
15✔
982

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

987
        if (!s)
15✔
988
                return -EINVAL;
989

990
        p = startswith(s, "//");
14✔
991
        if (!p) {
14✔
992
                p = startswith(s, "\\\\");
6✔
993
                if (!p)
6✔
994
                        return -EINVAL;
995
        }
996

997
        delimiter = s[0];
11✔
998
        e = strchr(p, delimiter);
11✔
999
        if (!e)
11✔
1000
                return -EINVAL;
1001

1002
        h = strndup(p, e - p);
11✔
1003
        if (!h)
11✔
1004
                return -ENOMEM;
1005

1006
        if (!hostname_is_valid(h, 0))
11✔
1007
                return -EINVAL;
1008

1009
        e++;
10✔
1010

1011
        d = strchrnul(e, delimiter);
10✔
1012

1013
        ss = strndup(e, d - e);
10✔
1014
        if (!ss)
10✔
1015
                return -ENOMEM;
1016

1017
        if (!filename_is_valid(ss))
10✔
1018
                return -EINVAL;
1019

1020
        if (!isempty(d)) {
8✔
1021
                x = strdup(skip_leading_chars(d, CHAR_TO_STR(delimiter)));
6✔
1022
                if (!x)
6✔
1023
                        return -EINVAL;
2✔
1024

1025
                /* Make sure to convert Windows-style "\" → Unix-style / */
1026
                for (char *i = x; *i; i++)
33✔
1027
                        if (*i == delimiter)
27✔
1028
                                *i = '/';
3✔
1029

1030
                if (!path_is_valid(x))
6✔
1031
                        return -EINVAL;
1032

1033
                path_simplify(x);
6✔
1034
                if (!path_is_normalized(x))
6✔
1035
                        return -EINVAL;
1036
        }
1037

1038
        if (ret_host)
6✔
1039
                *ret_host = TAKE_PTR(h);
6✔
1040
        if (ret_service)
6✔
1041
                *ret_service = TAKE_PTR(ss);
6✔
1042
        if (ret_path)
6✔
1043
                *ret_path = TAKE_PTR(x);
6✔
1044

1045
        return 0;
1046
}
1047

1048
int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode) {
250,329✔
1049
        _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
250,329✔
1050
        _cleanup_free_ char *fname = NULL, *parent = NULL;
250,329✔
1051
        int r;
250,329✔
1052

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

1057
        if (flags & ~(O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_EXCL|O_NOATIME|O_NOFOLLOW|O_PATH))
250,329✔
1058
                return -EINVAL;
1059
        if ((flags & O_ACCMODE_STRICT) != O_RDONLY)
250,329✔
1060
                return -EINVAL;
1061

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

1065
        /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
1066
         * that we can pin it, and operate below it. */
1067
        r = path_extract_directory(path, &parent);
250,329✔
1068
        if (r < 0) {
250,329✔
1069
                if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
5,203✔
1070
                        return r;
1071
        } else {
1072
                r = path_extract_filename(path, &fname);
245,126✔
1073
                if (r < 0)
245,126✔
1074
                        return r;
1075

1076
                parent_fd = openat(dirfd, parent, O_PATH|O_DIRECTORY|O_CLOEXEC);
245,126✔
1077
                if (parent_fd < 0)
245,126✔
1078
                        return -errno;
×
1079

1080
                dirfd = parent_fd;
245,126✔
1081
                path = fname;
245,126✔
1082
        }
1083

1084
        fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode);
250,329✔
1085
        if (IN_SET(fd, -ELOOP, -ENOTDIR))
250,329✔
1086
                return -EEXIST;
1087
        if (fd < 0)
250,328✔
1088
                return fd;
3,989✔
1089

1090
        return TAKE_FD(fd);
1091
}
1092

1093
int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) {
2,593,943✔
1094
        int fd;
2,593,943✔
1095

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

1105
        if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) {
2,593,943✔
1106
                fd = openat(dirfd, pathname, flags, mode);
531,118✔
1107
                if (fd < 0)
531,118✔
1108
                        return -errno;
39,569✔
1109

1110
                if (ret_newly_created)
491,549✔
1111
                        *ret_newly_created = FLAGS_SET(flags, O_CREAT);
491,549✔
1112
                return fd;
491,549✔
1113
        }
1114

1115
        for (unsigned attempts = 7;;) {
1116
                /* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */
1117
                fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode);
2,063,195✔
1118
                if (fd >= 0) {
2,063,195✔
1119
                        if (ret_newly_created)
1,814,307✔
1120
                                *ret_newly_created = false;
1,814,307✔
1121
                        return fd;
1,814,307✔
1122
                }
1123
                if (errno != ENOENT)
248,888✔
1124
                        return -errno;
×
1125

1126
                /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL/O_NOFOLLOW. */
1127
                fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL | O_NOFOLLOW, mode);
248,888✔
1128
                if (fd >= 0) {
248,888✔
1129
                        if (ret_newly_created)
248,516✔
1130
                                *ret_newly_created = true;
248,515✔
1131
                        return fd;
248,516✔
1132
                }
1133
                if (errno != EEXIST)
372✔
1134
                        return -errno;
×
1135

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

1139
                if (--attempts == 0) /* Give up eventually, somebody is playing with us */
372✔
1140
                        return -EEXIST;
1141
        }
1142
}
1143

1144
int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
1,129,707✔
1145
        _cleanup_close_ int fd = -EBADF;
1,129,707✔
1146
        bool made_dir = false, made_file = false;
1,129,707✔
1147
        int r;
1,129,707✔
1148

1149
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,129,707✔
1150

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

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

1170
        if (mode == MODE_INVALID)
1,129,707✔
1171
                mode = (open_flags & O_DIRECTORY) ? 0755 : 0644;
53,222✔
1172

1173
        if (isempty(path)) {
1,129,707✔
1174
                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
5,983✔
1175

1176
                if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
5,983✔
1177
                        r = fd_verify_regular(dir_fd);
44✔
1178
                        if (r < 0)
44✔
1179
                                return r;
1180
                }
1181

1182
                return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
5,983✔
1183
        }
1184

1185
        bool call_label_ops_post = false;
1,123,724✔
1186

1187
        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
1,123,724✔
1188
                r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG);
538✔
1189
                if (r < 0)
538✔
1190
                        return r;
1191

1192
                call_label_ops_post = true;
1193
        }
1194

1195
        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
1,123,724✔
1196
                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
284,980✔
1197
                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
×
1198
                else
1199
                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
284,980✔
1200
                if (r == -EEXIST) {
151,519✔
1201
                        if (FLAGS_SET(open_flags, O_EXCL))
151,515✔
1202
                                return -EEXIST;
1203
                } else if (r < 0)
133,465✔
1204
                        return r;
1205
                else
1206
                        made_dir = true;
1207

1208
                open_flags &= ~(O_EXCL|O_CREAT);
280,990✔
1209
        }
1210

1211
        if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
1,119,734✔
1212
                /* Guarantee we return a regular fd only, and don't open the file unless we verified it
1213
                 * first */
1214

1215
                if (FLAGS_SET(open_flags, O_PATH)) {
401,756✔
1216
                        fd = openat(dir_fd, path, open_flags, mode);
1✔
1217
                        if (fd < 0) {
1✔
1218
                                r = -errno;
×
1219
                                goto error;
×
1220
                        }
1221

1222
                        r = fd_verify_regular(fd);
1✔
1223
                        if (r < 0)
1✔
1224
                                goto error;
×
1225

1226
                } else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) {
401,755✔
1227
                        /* In O_EXCL mode we can just create the thing, everything is dealt with for us */
1228
                        fd = openat(dir_fd, path, open_flags, mode);
2✔
1229
                        if (fd < 0) {
2✔
1230
                                r = -errno;
1✔
1231
                                goto error;
1✔
1232
                        }
1233

1234
                        made_file = true;
1✔
1235
                } else {
1236
                        /* Otherwise pin the inode first via O_PATH */
1237
                        _cleanup_close_ int inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|(open_flags & O_NOFOLLOW));
401,753✔
1238
                        if (inode_fd < 0) {
401,753✔
1239
                                if (errno != ENOENT || !FLAGS_SET(open_flags, O_CREAT)) {
21✔
1240
                                        r = -errno;
1✔
1241
                                        goto error;
1✔
1242
                                }
1243

1244
                                /* Doesn't exist yet, then try to create it */
1245
                                fd = openat(dir_fd, path, open_flags|O_CREAT|O_EXCL, mode);
20✔
1246
                                if (fd < 0) {
20✔
1247
                                        r = -errno;
×
1248
                                        goto error;
×
1249
                                }
1250

1251
                                made_file = true;
20✔
1252
                        } else {
1253
                                /* OK, we pinned it. Now verify it's actually a regular file, and then reopen it */
1254
                                r = fd_verify_regular(inode_fd);
401,732✔
1255
                                if (r < 0)
401,732✔
1256
                                        goto error;
5✔
1257

1258
                                fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT));
401,727✔
1259
                                if (fd < 0) {
401,727✔
1260
                                        r = fd;
×
1261
                                        goto error;
×
1262
                                }
1263
                        }
1264
                }
1265
        } else {
1266
                fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
717,978✔
1267
                if (fd < 0) {
717,978✔
1268
                        r = fd;
37,951✔
1269
                        goto error;
37,951✔
1270
                }
1271
        }
1272

1273
        if (call_label_ops_post) {
1,081,776✔
1274
                call_label_ops_post = false;
538✔
1275

1276
                r = label_ops_post(fd, /* path= */ NULL, made_file || made_dir);
538✔
1277
                if (r < 0)
538✔
1278
                        goto error;
×
1279
        }
1280

1281
        if (FLAGS_SET(xopen_flags, XO_NOCOW)) {
1,081,776✔
1282
                r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
69✔
1283
                if (r < 0 && !ERRNO_IS_IOCTL_NOT_SUPPORTED(r))
69✔
1284
                        goto error;
×
1285
        }
1286

1287
        return TAKE_FD(fd);
1288

1289
error:
37,958✔
1290
        if (call_label_ops_post)
37,958✔
1291
                (void) label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : path, made_dir || made_file);
×
1292

1293
        if (made_dir || made_file)
37,958✔
1294
                (void) unlinkat(dir_fd, path, made_dir ? AT_REMOVEDIR : 0);
×
1295

1296
        return r;
1297
}
1298

1299
int xopenat_lock_full(
280,157✔
1300
                int dir_fd,
1301
                const char *path,
1302
                int open_flags,
1303
                XOpenFlags xopen_flags,
1304
                mode_t mode,
1305
                LockType locktype,
1306
                int operation) {
1307

1308
        _cleanup_close_ int fd = -EBADF;
280,157✔
1309
        int r;
280,157✔
1310

1311
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
280,157✔
1312
        assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
280,157✔
1313

1314
        /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
1315
         * the same error here). */
1316
        if (FLAGS_SET(open_flags, O_DIRECTORY) && !IN_SET(locktype, LOCK_BSD, LOCK_NONE))
280,157✔
1317
                return -EBADF;
1318

1319
        for (;;) {
313,642✔
1320
                struct stat st;
296,899✔
1321

1322
                fd = xopenat_full(dir_fd, path, open_flags, xopen_flags, mode);
296,899✔
1323
                if (fd < 0)
296,899✔
1324
                        return fd;
8✔
1325

1326
                r = lock_generic(fd, locktype, operation);
296,899✔
1327
                if (r < 0)
296,899✔
1328
                        return r;
1329

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

1334
                if (fstat(fd, &st) < 0)
296,891✔
1335
                        return -errno;
×
1336
                if (st.st_nlink > 0)
296,891✔
1337
                        break;
1338

1339
                fd = safe_close(fd);
16,743✔
1340
        }
1341

1342
        return TAKE_FD(fd);
280,148✔
1343
}
1344

1345
int link_fd(int fd, int newdirfd, const char *newpath) {
12,191✔
1346
        int r;
12,191✔
1347

1348
        assert(fd >= 0);
12,191✔
1349
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
12,191✔
1350
        assert(newpath);
12,191✔
1351

1352
        /* Try to link via AT_EMPTY_PATH first. This fails with ENOENT if we don't have CAP_DAC_READ_SEARCH
1353
         * on kernels < 6.10, in which case we'd then resort to /proc/self/fd/ dance.
1354
         *
1355
         * See also: https://github.com/torvalds/linux/commit/42bd2af5950456d46fdaa91c3a8fb02e680f19f5 */
1356
        r = RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
12,191✔
1357
        if (r == -ENOENT) {
4,780✔
1358
                r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
×
1359
                if (r == -ENOENT && proc_mounted() == 0) /* No proc_fd_enoent_errno() here because we don't
×
1360
                                                            know if it's the target path that's missing. */
1361
                        return -ENOSYS;
×
1362
        }
1363

1364
        return r;
1365
}
1366

1367
int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
6,556✔
1368
        _cleanup_close_ int old_fd = -EBADF;
6,556✔
1369
        int r;
6,556✔
1370

1371
        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
6,556✔
1372
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
6,556✔
1373
        assert(!isempty(newpath)); /* source path is optional, but the target path is not */
6,556✔
1374

1375
        /* Like linkat() but replaces the target if needed. Is a NOP if source and target already share the
1376
         * same inode. */
1377

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

1381
        if (path_implies_directory(oldpath)) /* Refuse these definite directories early */
6,556✔
1382
                return -EISDIR;
1383

1384
        if (path_implies_directory(newpath))
6,556✔
1385
                return -EISDIR;
1386

1387
        /* First, try to link this directly */
1388
        if (oldpath)
6,556✔
1389
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
31✔
1390
        else
1391
                r = link_fd(olddirfd, newdirfd, newpath);
6,525✔
1392
        if (r >= 0)
6,539✔
1393
                return 0;
1,765✔
1394
        if (r != -EEXIST)
4,791✔
1395
                return r;
1396

1397
        old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC);
4,791✔
1398
        if (old_fd < 0)
4,791✔
1399
                return old_fd;
1400

1401
        struct stat old_st;
4,791✔
1402
        if (fstat(old_fd, &old_st) < 0)
4,791✔
1403
                return -errno;
×
1404

1405
        if (S_ISDIR(old_st.st_mode)) /* Don't bother if we are operating on a directory */
4,791✔
1406
                return -EISDIR;
1407

1408
        struct stat new_st;
4,791✔
1409
        if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0)
4,791✔
1410
                return -errno;
×
1411

1412
        if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */
4,791✔
1413
                return -EEXIST;
1414

1415
        if (stat_inode_same(&old_st, &new_st)) /* Already the same inode? Then shortcut this */
4,791✔
1416
                return 0;
1417

1418
        _cleanup_free_ char *tmp_path = NULL;
4,790✔
1419
        r = tempfn_random(newpath, /* extra= */ NULL, &tmp_path);
4,790✔
1420
        if (r < 0)
4,790✔
1421
                return r;
1422

1423
        r = link_fd(old_fd, newdirfd, tmp_path);
4,790✔
1424
        if (r < 0) {
4,790✔
1425
                if (!ERRNO_IS_PRIVILEGE(r))
×
1426
                        return r;
1427

1428
                /* If that didn't work due to permissions then go via the path of the dentry */
1429
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, tmp_path, 0));
×
1430
                if (r < 0)
×
1431
                        return r;
1432
        }
1433

1434
        r = RET_NERRNO(renameat(newdirfd, tmp_path, newdirfd, newpath));
4,790✔
1435
        if (r < 0) {
×
1436
                (void) unlinkat(newdirfd, tmp_path, /* flags= */ 0);
×
1437
                return r;
×
1438
        }
1439

1440
        return 0;
1441
}
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