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

systemd / systemd / 13533485051

25 Feb 2025 10:20PM UTC coverage: 71.818% (+0.04%) from 71.774%
13533485051

push

github

web-flow
make integritysetup/veritysetup more alike cryptsetup when it comes to remote operation (#36501)

Let's address some asymmetries here.

0 of 12 new or added lines in 1 file covered. (0.0%)

96 existing lines in 30 files now uncovered.

294319 of 409813 relevant lines covered (71.82%)

716680.45 hits per line

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

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

3
#include <errno.h>
4
#include <stddef.h>
5
#include <stdlib.h>
6
#include <sys/file.h>
7
#include <linux/falloc.h>
8
#include <linux/magic.h>
9
#include <unistd.h>
10

11
#include "alloc-util.h"
12
#include "btrfs.h"
13
#include "chattr-util.h"
14
#include "dirent-util.h"
15
#include "fd-util.h"
16
#include "fileio.h"
17
#include "fs-util.h"
18
#include "hostname-util.h"
19
#include "label.h"
20
#include "lock-util.h"
21
#include "log.h"
22
#include "macro.h"
23
#include "missing_fcntl.h"
24
#include "missing_fs.h"
25
#include "missing_syscall.h"
26
#include "mkdir.h"
27
#include "parse-util.h"
28
#include "path-util.h"
29
#include "process-util.h"
30
#include "random-util.h"
31
#include "ratelimit.h"
32
#include "stat-util.h"
33
#include "stdio-util.h"
34
#include "string-util.h"
35
#include "strv.h"
36
#include "time-util.h"
37
#include "tmpfile-util.h"
38
#include "umask-util.h"
39
#include "user-util.h"
40

41
int rmdir_parents(const char *path, const char *stop) {
84,390✔
42
        char *p;
84,390✔
43
        int r;
84,390✔
44

45
        assert(path);
84,390✔
46
        assert(stop);
84,390✔
47

48
        if (!path_is_safe(path))
84,390✔
49
                return -EINVAL;
50

51
        if (!path_is_safe(stop))
84,389✔
52
                return -EINVAL;
53

54
        p = strdupa_safe(path);
84,388✔
55

56
        for (;;) {
3,416✔
57
                char *slash = NULL;
87,804✔
58

59
                /* skip the last component. */
60
                r = path_find_last_component(p, /* accept_dot_dot= */ false, (const char **) &slash, NULL);
87,804✔
61
                if (r <= 0)
87,804✔
62
                        return r;
84,388✔
63
                if (slash == p)
87,804✔
64
                        return 0;
65

66
                assert(*slash == '/');
87,804✔
67
                *slash = '\0';
87,804✔
68

69
                if (path_startswith_full(stop, p, /* accept_dot_dot= */ false))
87,804✔
70
                        return 0;
71

72
                if (rmdir(p) < 0 && errno != ENOENT)
87,668✔
73
                        return -errno;
84,252✔
74
        }
75
}
76

77
int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
91✔
78
        int r;
91✔
79

80
        /* Try the ideal approach first */
81
        if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) >= 0)
91✔
82
                return 0;
83

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

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

94
                r = RET_NERRNO(unlinkat(olddirfd, oldpath, 0));
×
95
                if (r < 0) {
×
96
                        (void) unlinkat(newdirfd, newpath, 0);
×
97
                        return r;
×
98
                }
99

100
                return 0;
×
101
        }
102

103
        if (!ERRNO_IS_NOT_SUPPORTED(errno) && !IN_SET(errno, EINVAL, EPERM)) /* FAT returns EPERM on link()… */
×
104
                return -errno;
×
105

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

109
        if (faccessat(newdirfd, newpath, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
×
110
                return -EEXIST;
111
        if (errno != ENOENT)
×
112
                return -errno;
×
113

114
        return RET_NERRNO(renameat(olddirfd, oldpath, newdirfd, newpath));
×
115
}
116

117
int readlinkat_malloc(int fd, const char *p, char **ret) {
3,861,108✔
118
        size_t l = PATH_MAX;
3,861,108✔
119

120
        assert(fd >= 0 || fd == AT_FDCWD);
3,861,108✔
121

122
        if (fd < 0 && isempty(p))
3,861,108✔
123
                return -EISDIR; /* In this case, the fd points to the current working directory, and is
124
                                 * definitely not a symlink. Let's return earlier. */
125

126
        for (;;) {
3,861,108✔
127
                _cleanup_free_ char *c = NULL;
3,861,108✔
128
                ssize_t n;
3,861,108✔
129

130
                c = new(char, l+1);
3,861,108✔
131
                if (!c)
3,861,108✔
132
                        return -ENOMEM;
133

134
                n = readlinkat(fd, strempty(p), c, l);
3,861,110✔
135
                if (n < 0)
3,861,108✔
136
                        return -errno;
581,785✔
137

138
                if ((size_t) n < l) {
3,279,323✔
139
                        c[n] = 0;
3,279,323✔
140

141
                        if (ret)
3,279,323✔
142
                                *ret = TAKE_PTR(c);
3,279,323✔
143

144
                        return 0;
3,279,323✔
145
                }
146

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

152
                l *= 2;
×
153
        }
154
}
155

156
int readlink_value(const char *p, char **ret) {
765,935✔
157
        _cleanup_free_ char *link = NULL, *name = NULL;
765,935✔
158
        int r;
765,935✔
159

160
        assert(p);
765,935✔
161
        assert(ret);
765,935✔
162

163
        r = readlink_malloc(p, &link);
765,935✔
164
        if (r < 0)
765,935✔
165
                return r;
166

167
        r = path_extract_filename(link, &name);
395,104✔
168
        if (r < 0)
395,104✔
169
                return r;
170
        if (r == O_DIRECTORY)
395,104✔
171
                return -EINVAL;
172

173
        *ret = TAKE_PTR(name);
395,104✔
174
        return 0;
395,104✔
175
}
176

177
int readlink_and_make_absolute(const char *p, char **ret) {
4,205✔
178
        _cleanup_free_ char *target = NULL;
4,205✔
179
        int r;
4,205✔
180

181
        assert(p);
4,205✔
182
        assert(ret);
4,205✔
183

184
        r = readlink_malloc(p, &target);
4,205✔
185
        if (r < 0)
4,205✔
186
                return r;
187

188
        return file_in_same_dir(p, target, ret);
28✔
189
}
190

191
int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
27,049✔
192
        _cleanup_close_ int fd = -EBADF;
27,049✔
193

194
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
27,049✔
195

196
        if (path) {
27,049✔
197
                /* Let's acquire an O_PATH fd, as precaution to change mode/owner on the same file */
198
                fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
27,049✔
199
                if (fd < 0)
27,049✔
UNCOV
200
                        return -errno;
×
201
                dir_fd = fd;
202

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

211
        return fchmod_and_chown(dir_fd, mode, uid, gid);
27,049✔
212
}
213

214
int fchmod_and_chown_with_fallback(int fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
116,930✔
215
        bool do_chown, do_chmod;
116,930✔
216
        struct stat st;
116,930✔
217
        int r;
116,930✔
218

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

231
        if (fstat(fd, &st) < 0)
116,930✔
232
                return -errno;
×
233

234
        do_chown =
233,860✔
235
                (uid != UID_INVALID && st.st_uid != uid) ||
116,930✔
236
                (gid != GID_INVALID && st.st_gid != gid);
15,478✔
237

238
        do_chmod =
233,860✔
239
                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
116,930✔
240
                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
116,922✔
241
                 do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown()
242
                             * modifies the access mode too */
243

244
        if (mode == MODE_INVALID)
116,930✔
245
                mode = st.st_mode; /* If we only shall do a chown(), save original mode, since chown() might break it. */
246
        else if ((mode & S_IFMT) != 0 && ((mode ^ st.st_mode) & S_IFMT) != 0)
77,609✔
247
                return -EINVAL; /* insist on the right file type if it was specified */
248

249
        if (do_chown && do_chmod) {
116,926✔
250
                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
19,846✔
251

252
                if (((minimal ^ st.st_mode) & 07777) != 0) {
19,846✔
253
                        r = fchmod_opath(fd, minimal & 07777);
7✔
254
                        if (r < 0) {
7✔
255
                                if (!path || r != -ENOSYS)
×
256
                                        return r;
257

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

265
        if (do_chown)
116,926✔
266
                if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0)
19,849✔
267
                        return -errno;
1✔
268

269
        if (do_chmod) {
116,925✔
270
                r = fchmod_opath(fd, mode & 07777);
20,106✔
271
                if (r < 0) {
20,106✔
272
                        if (!path || r != -ENOSYS)
×
273
                                return r;
274

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

281
        return do_chown || do_chmod;
116,925✔
282
}
283

284
int fchmod_umask(int fd, mode_t m) {
2,586✔
285
        _cleanup_umask_ mode_t u = umask(0777);
2,586✔
286

287
        return RET_NERRNO(fchmod(fd, m & (~u)));
2,586✔
288
}
289

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

302
        assert(fd >= 0);
24,857✔
303

304
        if (fchmodat2(fd, "", m, AT_EMPTY_PATH) >= 0)
24,857✔
305
                return 0;
306
        if (!IN_SET(errno, ENOSYS, EPERM)) /* Some container managers block unknown syscalls with EPERM */
2✔
307
                return -errno;
2✔
308

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

313
                return proc_fd_enoent_errno();
×
314
        }
315

316
        return 0;
×
317
}
318

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

322
        assert(fd >= 0);
115,023✔
323

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

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

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

336
                return proc_fd_enoent_errno();
×
337
        }
338

339
        return 0;
×
340
}
341

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

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

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

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

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

359
        return 0;
360
}
361

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

365
        assert(path);
10,651✔
366
        assert(fd >= 0);
10,651✔
367

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

371
        return stat_warn_permissions(path, &st);
10,651✔
372
}
373

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

377
        if (stamp == USEC_INFINITY)
72,874✔
378
                return futimens_opath(fd, /* ts= */ NULL);
72,269✔
379

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

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

390
        assert(path);
72,878✔
391

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

397
        if (parents)
72,878✔
398
                (void) mkdir_parents(path, 0755);
33,043✔
399

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

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

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

420
        return RET_GATHER(ret, touch_fd(fd, stamp));
72,874✔
421
}
422

423
int symlinkat_idempotent(const char *from, int atfd, const char *to, bool make_relative) {
517✔
424
        _cleanup_free_ char *relpath = NULL;
517✔
425
        int r;
517✔
426

427
        assert(from);
517✔
428
        assert(to);
517✔
429

430
        if (make_relative) {
517✔
431
                r = path_make_relative_parent(to, from, &relpath);
183✔
432
                if (r < 0)
183✔
433
                        return r;
434

435
                from = relpath;
183✔
436
        }
437

438
        if (symlinkat(from, atfd, to) < 0) {
517✔
439
                _cleanup_free_ char *p = NULL;
64✔
440

441
                if (errno != EEXIST)
64✔
442
                        return -errno;
×
443

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

450
                if (!streq(p, from)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
64✔
451
                        return -EEXIST;
452
        }
453

454
        return 0;
455
}
456

457
int symlinkat_atomic_full(const char *from, int atfd, const char *to, bool make_relative) {
159,944✔
458
        _cleanup_free_ char *relpath = NULL, *t = NULL;
159,944✔
459
        int r;
159,944✔
460

461
        assert(from);
159,944✔
462
        assert(to);
159,944✔
463

464
        if (make_relative) {
159,944✔
465
                r = path_make_relative_parent(to, from, &relpath);
155,889✔
466
                if (r < 0)
155,889✔
467
                        return r;
468

469
                from = relpath;
155,889✔
470
        }
471

472
        r = tempfn_random(to, NULL, &t);
159,944✔
473
        if (r < 0)
159,944✔
474
                return r;
475

476
        if (symlinkat(from, atfd, t) < 0)
159,944✔
477
                return -errno;
×
478

479
        r = RET_NERRNO(renameat(atfd, t, atfd, to));
159,945✔
480
        if (r < 0) {
1✔
481
                (void) unlinkat(atfd, t, 0);
1✔
482
                return r;
1✔
483
        }
484

485
        return 0;
486
}
487

488
int mknodat_atomic(int atfd, const char *path, mode_t mode, dev_t dev) {
×
489
        _cleanup_free_ char *t = NULL;
×
490
        int r;
×
491

492
        assert(path);
×
493

494
        r = tempfn_random(path, NULL, &t);
×
495
        if (r < 0)
×
496
                return r;
497

498
        if (mknodat(atfd, t, mode, dev) < 0)
×
499
                return -errno;
×
500

501
        r = RET_NERRNO(renameat(atfd, t, atfd, path));
×
502
        if (r < 0) {
×
503
                (void) unlinkat(atfd, t, 0);
×
504
                return r;
×
505
        }
506

507
        return 0;
508
}
509

510
int mkfifoat_atomic(int atfd, const char *path, mode_t mode) {
1✔
511
        _cleanup_free_ char *t = NULL;
1✔
512
        int r;
1✔
513

514
        assert(path);
1✔
515

516
        /* We're only interested in the (random) filename.  */
517
        r = tempfn_random(path, NULL, &t);
1✔
518
        if (r < 0)
1✔
519
                return r;
520

521
        if (mkfifoat(atfd, t, mode) < 0)
1✔
522
                return -errno;
×
523

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

530
        return 0;
531
}
532

533
int get_files_in_directory(const char *path, char ***list) {
299✔
534
        _cleanup_strv_free_ char **l = NULL;
×
535
        _cleanup_closedir_ DIR *d = NULL;
299✔
536
        size_t n = 0;
299✔
537

538
        assert(path);
299✔
539

540
        /* Returns all files in a directory in *list, and the number
541
         * of files as return value. If list is NULL returns only the
542
         * number. */
543

544
        d = opendir(path);
299✔
545
        if (!d)
299✔
546
                return -errno;
2✔
547

548
        FOREACH_DIRENT_ALL(de, d, return -errno) {
1,262✔
549
                if (!dirent_is_file(de))
965✔
550
                        continue;
650✔
551

552
                if (list) {
315✔
553
                        /* one extra slot is needed for the terminating NULL */
554
                        if (!GREEDY_REALLOC(l, n + 2))
302✔
555
                                return -ENOMEM;
556

557
                        l[n] = strdup(de->d_name);
302✔
558
                        if (!l[n])
302✔
559
                                return -ENOMEM;
560

561
                        l[++n] = NULL;
302✔
562
                } else
563
                        n++;
13✔
564
        }
565

566
        if (list)
297✔
567
                *list = TAKE_PTR(l);
294✔
568

569
        return n;
297✔
570
}
571

572
static int getenv_tmp_dir(const char **ret_path) {
2,320✔
573
        int r, ret = 0;
2,320✔
574

575
        assert(ret_path);
2,320✔
576

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

582
                e = secure_getenv(n);
6,958✔
583
                if (!e)
6,958✔
584
                        continue;
6,956✔
585
                if (!path_is_absolute(e)) {
2✔
586
                        r = -ENOTDIR;
×
587
                        goto next;
×
588
                }
589
                if (!path_is_normalized(e)) {
2✔
590
                        r = -EPERM;
×
591
                        goto next;
×
592
                }
593

594
                r = is_dir(e, true);
2✔
595
                if (r < 0)
2✔
596
                        goto next;
1✔
597
                if (r == 0) {
1✔
598
                        r = -ENOTDIR;
×
599
                        goto next;
×
600
                }
601

602
                *ret_path = e;
1✔
603
                return 1;
1✔
604

605
        next:
1✔
606
                /* Remember first error, to make this more debuggable */
607
                if (ret >= 0)
1✔
608
                        ret = r;
1✔
609
        }
610

611
        if (ret < 0)
2,319✔
612
                return ret;
613

614
        *ret_path = NULL;
2,318✔
615
        return ret;
2,318✔
616
}
617

618
static int tmp_dir_internal(const char *def, const char **ret) {
2,320✔
619
        const char *e;
2,320✔
620
        int r, k;
2,320✔
621

622
        assert(def);
2,320✔
623
        assert(ret);
2,320✔
624

625
        r = getenv_tmp_dir(&e);
2,320✔
626
        if (r > 0) {
2,320✔
627
                *ret = e;
1✔
628
                return 0;
1✔
629
        }
630

631
        k = is_dir(def, /* follow = */ true);
2,319✔
632
        if (k == 0)
2,319✔
633
                k = -ENOTDIR;
634
        if (k < 0)
2,319✔
635
                return RET_GATHER(r, k);
×
636

637
        *ret = def;
2,319✔
638
        return 0;
2,319✔
639
}
640

641
int var_tmp_dir(const char **ret) {
2,210✔
642
        assert(ret);
2,210✔
643

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

649
        return tmp_dir_internal("/var/tmp", ret);
2,210✔
650
}
651

652
int tmp_dir(const char **ret) {
110✔
653
        assert(ret);
110✔
654

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

658
        return tmp_dir_internal("/tmp", ret);
110✔
659
}
660

661
int unlink_or_warn(const char *filename) {
228✔
662
        assert(filename);
228✔
663

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

671
        return 0;
672
}
673

674
int access_fd(int fd, int mode) {
16,609✔
675
        assert(fd >= 0);
16,609✔
676

677
        /* Like access() but operates on an already open fd */
678

679
        if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0)
16,609✔
680
                return 0;
681
        if (errno != EINVAL)
561✔
682
                return -errno;
561✔
683

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

687
        if (access(FORMAT_PROC_FD_PATH(fd), mode) < 0) {
×
688
                if (errno != ENOENT)
×
689
                        return -errno;
×
690

691
                return proc_fd_enoent_errno();
×
692
        }
693

694
        return 0;
×
695
}
696

697
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
75✔
698
        _cleanup_close_ int truncate_fd = -EBADF;
75✔
699
        struct stat st;
75✔
700
        off_t l, bs;
75✔
701

702
        assert(fd >= 0 || fd == AT_FDCWD);
75✔
703
        assert(name);
75✔
704
        assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0);
75✔
705

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

722
        if (!FLAGS_SET(flags, UNLINK_REMOVEDIR)) {
75✔
723
                truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
75✔
724
                if (truncate_fd < 0) {
75✔
725

726
                        /* If this failed because the file doesn't exist propagate the error right-away. Also,
727
                         * AT_REMOVEDIR wasn't set, and we tried to open the file for writing, which means EISDIR is
728
                         * returned when this is a directory but we are not supposed to delete those, hence propagate
729
                         * the error right-away too. */
730
                        if (IN_SET(errno, ENOENT, EISDIR))
×
731
                                return -errno;
×
732

733
                        if (errno != ELOOP) /* don't complain if this is a symlink */
×
734
                                log_debug_errno(errno, "Failed to open file '%s' for deallocation, ignoring: %m", name);
×
735
                }
736
        }
737

738
        if (unlinkat(fd, name, FLAGS_SET(flags, UNLINK_REMOVEDIR) ? AT_REMOVEDIR : 0) < 0)
75✔
739
                return -errno;
×
740

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

744
        if (fstat(truncate_fd, &st) < 0) {
75✔
745
                log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
746
                return 0;
×
747
        }
748

749
        if (!S_ISREG(st.st_mode))
75✔
750
                return 0;
751

752
        if (FLAGS_SET(flags, UNLINK_ERASE) && st.st_size > 0 && st.st_nlink == 0) {
75✔
753
                uint64_t left = st.st_size;
3✔
754
                char buffer[64 * 1024];
3✔
755

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

764
                random_bytes(buffer, sizeof(buffer));
3✔
765

766
                while (left > 0) {
6✔
767
                        ssize_t n;
3✔
768

769
                        n = write(truncate_fd, buffer, MIN(sizeof(buffer), left));
3✔
770
                        if (n < 0) {
3✔
771
                                log_debug_errno(errno, "Failed to erase data in file '%s', ignoring.", name);
×
772
                                break;
773
                        }
774

775
                        assert(left >= (size_t) n);
3✔
776
                        left -= n;
3✔
777
                }
778

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

786
        /* Don't dallocate if there's nothing to deallocate or if the file is linked elsewhere */
787
        if (st.st_blocks == 0 || st.st_nlink > 0)
75✔
788
                return 0;
789

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

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

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

799
        /* Fall back to truncation */
800
        if (ftruncate(truncate_fd, 0) < 0) {
×
801
                log_debug_errno(errno, "Failed to truncate file to 0, ignoring: %m");
×
802
                return 0;
×
803
        }
804

805
        return 0;
806
}
807

808
int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
1,748,056✔
809
        _cleanup_free_ char *parent = NULL;
1,748,056✔
810
        int r;
1,748,056✔
811

812
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,748,056✔
813
        assert(path);
1,748,056✔
814

815
        r = path_extract_directory(path, &parent);
1,748,056✔
816
        if (r == -EDESTADDRREQ) {
1,748,056✔
817
                parent = strdup(".");
35✔
818
                if (!parent)
35✔
819
                        return -ENOMEM;
820
        } else if (r == -EADDRNOTAVAIL) {
1,748,021✔
821
                parent = strdup(path);
×
822
                if (!parent)
×
823
                        return -ENOMEM;
824
        } else if (r < 0)
1,748,021✔
825
                return r;
826

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

830
        if (FLAGS_SET(flags, O_PATH))
1,748,056✔
831
                flags |= O_DIRECTORY;
×
832
        else if (!FLAGS_SET(flags, O_TMPFILE))
1,748,056✔
833
                flags |= O_DIRECTORY|O_RDONLY;
1,745,290✔
834

835
        return RET_NERRNO(openat(dir_fd, parent, flags, mode));
1,748,089✔
836
}
837

838
int conservative_renameat(
37,203✔
839
                int olddirfd, const char *oldpath,
840
                int newdirfd, const char *newpath) {
841

842
        _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF;
37,203✔
843
        struct stat old_stat, new_stat;
37,203✔
844

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

852
        old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
37,203✔
853
        if (old_fd < 0)
37,203✔
854
                goto do_rename;
×
855

856
        new_fd = openat(newdirfd, newpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
37,203✔
857
        if (new_fd < 0)
37,203✔
858
                goto do_rename;
3,242✔
859

860
        if (fstat(old_fd, &old_stat) < 0)
33,961✔
861
                goto do_rename;
×
862

863
        if (!S_ISREG(old_stat.st_mode))
33,961✔
864
                goto do_rename;
×
865

866
        if (fstat(new_fd, &new_stat) < 0)
33,961✔
867
                goto do_rename;
×
868

869
        if (stat_inode_same(&new_stat, &old_stat))
33,961✔
870
                goto is_same;
1✔
871

872
        if (old_stat.st_mode != new_stat.st_mode ||
33,960✔
873
            old_stat.st_size != new_stat.st_size ||
33,960✔
874
            old_stat.st_uid != new_stat.st_uid ||
23,726✔
875
            old_stat.st_gid != new_stat.st_gid)
23,726✔
876
                goto do_rename;
10,234✔
877

878
        for (;;) {
6✔
879
                uint8_t buf1[16*1024];
23,732✔
880
                uint8_t buf2[sizeof(buf1)];
23,732✔
881
                ssize_t l1, l2;
23,732✔
882

883
                l1 = read(old_fd, buf1, sizeof(buf1));
23,732✔
884
                if (l1 < 0)
23,732✔
885
                        goto do_rename;
277✔
886

887
                if (l1 == sizeof(buf1))
23,732✔
888
                        /* Read the full block, hence read a full block in the other file too */
889

890
                        l2 = read(new_fd, buf2, l1);
7✔
891
                else {
892
                        assert((size_t) l1 < sizeof(buf1));
23,725✔
893

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

898
                        assert((size_t) (l1 + 1) <= sizeof(buf2));
23,725✔
899
                        l2 = read(new_fd, buf2, l1 + 1);
23,725✔
900
                }
901
                if (l2 != l1)
23,732✔
902
                        goto do_rename;
×
903

904
                if (memcmp(buf1, buf2, l1) != 0)
23,732✔
905
                        goto do_rename;
277✔
906

907
                if ((size_t) l1 < sizeof(buf1)) /* We hit EOF on the first file, and the second file too, hence exit
23,455✔
908
                                                 * now. */
909
                        break;
910
        }
911

912
is_same:
23,450✔
913
        /* Everything matches? Then don't rename, instead remove the source file, and leave the existing
914
         * destination in place */
915

916
        if (unlinkat(olddirfd, oldpath, 0) < 0)
23,450✔
917
                goto do_rename;
×
918

919
        return 0;
920

921
do_rename:
13,753✔
922
        if (renameat(olddirfd, oldpath, newdirfd, newpath) < 0)
13,753✔
923
                return -errno;
×
924

925
        return 1;
926
}
927

928
int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) {
1,658✔
929
        RateLimit rl;
1,658✔
930
        int r;
1,658✔
931

932
        r = posix_fallocate(fd, offset, size); /* returns positive errnos on error */
1,658✔
933
        if (r != EINTR)
1,658✔
934
                return -r; /* Let's return negative errnos, like common in our codebase */
1,658✔
935

936
        /* On EINTR try a couple of times more, but protect against busy looping
937
         * (not more than 16 times per 10s) */
938
        rl = (const RateLimit) { 10 * USEC_PER_SEC, 16 };
×
939
        while (ratelimit_below(&rl)) {
×
940
                r = posix_fallocate(fd, offset, size);
×
941
                if (r != EINTR)
×
942
                        return -r;
×
943
        }
944

945
        return -EINTR;
946
}
947

948
int parse_cifs_service(
15✔
949
                const char *s,
950
                char **ret_host,
951
                char **ret_service,
952
                char **ret_path) {
953

954
        _cleanup_free_ char *h = NULL, *ss = NULL, *x = NULL;
15✔
955
        const char *p, *e, *d;
15✔
956
        char delimiter;
15✔
957

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

962
        if (!s)
15✔
963
                return -EINVAL;
964

965
        p = startswith(s, "//");
14✔
966
        if (!p) {
14✔
967
                p = startswith(s, "\\\\");
6✔
968
                if (!p)
6✔
969
                        return -EINVAL;
970
        }
971

972
        delimiter = s[0];
11✔
973
        e = strchr(p, delimiter);
11✔
974
        if (!e)
11✔
975
                return -EINVAL;
976

977
        h = strndup(p, e - p);
11✔
978
        if (!h)
11✔
979
                return -ENOMEM;
980

981
        if (!hostname_is_valid(h, 0))
11✔
982
                return -EINVAL;
983

984
        e++;
10✔
985

986
        d = strchrnul(e, delimiter);
10✔
987

988
        ss = strndup(e, d - e);
10✔
989
        if (!ss)
10✔
990
                return -ENOMEM;
991

992
        if (!filename_is_valid(ss))
10✔
993
                return -EINVAL;
994

995
        if (!isempty(d)) {
8✔
996
                x = strdup(skip_leading_chars(d, CHAR_TO_STR(delimiter)));
6✔
997
                if (!x)
6✔
998
                        return -EINVAL;
2✔
999

1000
                /* Make sure to convert Windows-style "\" → Unix-style / */
1001
                for (char *i = x; *i; i++)
33✔
1002
                        if (*i == delimiter)
27✔
1003
                                *i = '/';
3✔
1004

1005
                if (!path_is_valid(x))
6✔
1006
                        return -EINVAL;
1007

1008
                path_simplify(x);
6✔
1009
                if (!path_is_normalized(x))
6✔
1010
                        return -EINVAL;
1011
        }
1012

1013
        if (ret_host)
6✔
1014
                *ret_host = TAKE_PTR(h);
6✔
1015
        if (ret_service)
6✔
1016
                *ret_service = TAKE_PTR(ss);
6✔
1017
        if (ret_path)
6✔
1018
                *ret_path = TAKE_PTR(x);
6✔
1019

1020
        return 0;
1021
}
1022

1023
int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode) {
260,486✔
1024
        _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
260,486✔
1025
        _cleanup_free_ char *fname = NULL, *parent = NULL;
260,486✔
1026
        int r;
260,486✔
1027

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

1032
        if (flags & ~(O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_EXCL|O_NOATIME|O_NOFOLLOW|O_PATH))
260,486✔
1033
                return -EINVAL;
1034
        if ((flags & O_ACCMODE) != O_RDONLY)
260,486✔
1035
                return -EINVAL;
1036

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

1040
        /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
1041
         * that we can pin it, and operate below it. */
1042
        r = path_extract_directory(path, &parent);
260,486✔
1043
        if (r < 0) {
260,486✔
1044
                if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
6,493✔
1045
                        return r;
1046
        } else {
1047
                r = path_extract_filename(path, &fname);
253,993✔
1048
                if (r < 0)
253,993✔
1049
                        return r;
1050

1051
                parent_fd = openat(dirfd, parent, O_PATH|O_DIRECTORY|O_CLOEXEC);
253,993✔
1052
                if (parent_fd < 0)
253,993✔
1053
                        return -errno;
×
1054

1055
                dirfd = parent_fd;
253,993✔
1056
                path = fname;
253,993✔
1057
        }
1058

1059
        fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode);
260,486✔
1060
        if (IN_SET(fd, -ELOOP, -ENOTDIR))
520,971✔
1061
                return -EEXIST;
1062
        if (fd < 0)
260,485✔
1063
                return fd;
5,041✔
1064

1065
        return TAKE_FD(fd);
1066
}
1067

1068
int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) {
2,537,613✔
1069
        int fd;
2,537,613✔
1070

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

1080
        if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) {
2,537,613✔
1081
                fd = openat(dirfd, pathname, flags, mode);
541,139✔
1082
                if (fd < 0)
541,139✔
1083
                        return -errno;
40,848✔
1084

1085
                if (ret_newly_created)
500,291✔
1086
                        *ret_newly_created = FLAGS_SET(flags, O_CREAT);
500,291✔
1087
                return fd;
500,291✔
1088
        }
1089

1090
        for (unsigned attempts = 7;;) {
1091
                /* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */
1092
                fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode);
1,997,029✔
1093
                if (fd >= 0) {
1,997,029✔
1094
                        if (ret_newly_created)
1,762,385✔
1095
                                *ret_newly_created = false;
1,762,385✔
1096
                        return fd;
1,762,385✔
1097
                }
1098
                if (errno != ENOENT)
234,644✔
1099
                        return -errno;
×
1100

1101
                /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL/O_NOFOLLOW. */
1102
                fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL | O_NOFOLLOW, mode);
234,644✔
1103
                if (fd >= 0) {
234,644✔
1104
                        if (ret_newly_created)
234,087✔
1105
                                *ret_newly_created = true;
234,086✔
1106
                        return fd;
234,087✔
1107
                }
1108
                if (errno != EEXIST)
557✔
1109
                        return -errno;
×
1110

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

1114
                if (--attempts == 0) /* Give up eventually, somebody is playing with us */
557✔
1115
                        return -EEXIST;
1116
        }
1117
}
1118

1119
int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
10,234,029✔
1120
        _cleanup_close_ int fd = -EBADF;
10,234,029✔
1121
        bool made_dir = false, made_file = false;
10,234,029✔
1122
        int r;
10,234,029✔
1123

1124
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
10,234,029✔
1125

1126
        /* An inode cannot be both a directory and a regular file at the same time. */
1127
        assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR)));
10,234,029✔
1128

1129
        /* This is like openat(), but has a few tricks up its sleeves, extending behaviour:
1130
         *
1131
         *   • O_DIRECTORY|O_CREAT is supported, which causes a directory to be created, and immediately
1132
         *     opened. When used with the XO_SUBVOLUME flag this will even create a btrfs subvolume.
1133
         *
1134
         *   • If O_CREAT is used with XO_LABEL, any created file will be immediately relabelled.
1135
         *
1136
         *   • If the path is specified NULL or empty, behaves like fd_reopen().
1137
         *
1138
         *   • If XO_NOCOW is specified will turn on the NOCOW btrfs flag on the file, if available.
1139
         *
1140
         *   • if XO_REGULAR is specified will return an error if inode is not a regular file.
1141
         *
1142
         *   • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files.
1143
         */
1144

1145
        if (mode == MODE_INVALID)
10,234,029✔
1146
                mode = (open_flags & O_DIRECTORY) ? 0755 : 0644;
51,204✔
1147

1148
        if (isempty(path)) {
10,234,029✔
1149
                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
9,119,798✔
1150

1151
                if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
9,119,798✔
1152
                        r = fd_verify_regular(dir_fd);
95✔
1153
                        if (r < 0)
95✔
1154
                                return r;
1155
                }
1156

1157
                return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
9,119,798✔
1158
        }
1159

1160
        bool call_label_ops_post = false;
1,114,231✔
1161

1162
        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
1,114,231✔
1163
                r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG);
490✔
1164
                if (r < 0)
490✔
1165
                        return r;
1166

1167
                call_label_ops_post = true;
1168
        }
1169

1170
        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
1,114,231✔
1171
                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
294,896✔
1172
                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
×
1173
                else
1174
                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
294,896✔
1175
                if (r == -EEXIST) {
169,655✔
1176
                        if (FLAGS_SET(open_flags, O_EXCL))
169,651✔
1177
                                return -EEXIST;
1178
                } else if (r < 0)
125,245✔
1179
                        return r;
1180
                else
1181
                        made_dir = true;
1182

1183
                open_flags &= ~(O_EXCL|O_CREAT);
289,854✔
1184
        }
1185

1186
        if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
1,109,189✔
1187
                /* Guarantee we return a regular fd only, and don't open the file unless we verified it
1188
                 * first */
1189

1190
                if (FLAGS_SET(open_flags, O_PATH)) {
401,703✔
1191
                        fd = openat(dir_fd, path, open_flags, mode);
1✔
1192
                        if (fd < 0) {
1✔
1193
                                r = -errno;
×
1194
                                goto error;
×
1195
                        }
1196

1197
                        r = fd_verify_regular(fd);
1✔
1198
                        if (r < 0)
1✔
1199
                                goto error;
×
1200

1201
                } else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) {
401,702✔
1202
                        /* In O_EXCL mode we can just create the thing, everything is dealt with for us */
1203
                        fd = openat(dir_fd, path, open_flags, mode);
2✔
1204
                        if (fd < 0) {
2✔
1205
                                r = -errno;
1✔
1206
                                goto error;
1✔
1207
                        }
1208

1209
                        made_file = true;
1✔
1210
                } else {
1211
                        /* Otherwise pin the inode first via O_PATH */
1212
                        _cleanup_close_ int inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|(open_flags & O_NOFOLLOW));
401,700✔
1213
                        if (inode_fd < 0) {
401,700✔
1214
                                if (errno != ENOENT || !FLAGS_SET(open_flags, O_CREAT)) {
21✔
1215
                                        r = -errno;
1✔
1216
                                        goto error;
1✔
1217
                                }
1218

1219
                                /* Doesn't exist yet, then try to create it */
1220
                                fd = openat(dir_fd, path, open_flags|O_CREAT|O_EXCL, mode);
20✔
1221
                                if (fd < 0) {
20✔
1222
                                        r = -errno;
×
1223
                                        goto error;
×
1224
                                }
1225

1226
                                made_file = true;
20✔
1227
                        } else {
1228
                                /* OK, we pinned it. Now verify it's actually a regular file, and then reopen it */
1229
                                r = fd_verify_regular(inode_fd);
401,679✔
1230
                                if (r < 0)
401,679✔
1231
                                        goto error;
5✔
1232

1233
                                fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT));
401,674✔
1234
                                if (fd < 0) {
401,674✔
1235
                                        r = fd;
×
1236
                                        goto error;
×
1237
                                }
1238
                        }
1239
                }
1240
        } else {
1241
                fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
707,486✔
1242
                if (fd < 0) {
707,486✔
1243
                        r = fd;
39,253✔
1244
                        goto error;
39,253✔
1245
                }
1246
        }
1247

1248
        if (call_label_ops_post) {
1,069,929✔
1249
                call_label_ops_post = false;
490✔
1250

1251
                r = label_ops_post(fd, /* path= */ NULL, made_file || made_dir);
490✔
1252
                if (r < 0)
490✔
1253
                        goto error;
×
1254
        }
1255

1256
        if (FLAGS_SET(xopen_flags, XO_NOCOW)) {
1,069,929✔
1257
                r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
63✔
1258
                if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(r))
63✔
1259
                        goto error;
×
1260
        }
1261

1262
        return TAKE_FD(fd);
1263

1264
error:
39,260✔
1265
        if (call_label_ops_post)
39,260✔
1266
                (void) label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : path, made_dir || made_file);
×
1267

1268
        if (made_dir || made_file)
39,260✔
1269
                (void) unlinkat(dir_fd, path, made_dir ? AT_REMOVEDIR : 0);
×
1270

1271
        return r;
1272
}
1273

1274
int xopenat_lock_full(
265,661✔
1275
                int dir_fd,
1276
                const char *path,
1277
                int open_flags,
1278
                XOpenFlags xopen_flags,
1279
                mode_t mode,
1280
                LockType locktype,
1281
                int operation) {
1282

1283
        _cleanup_close_ int fd = -EBADF;
265,661✔
1284
        int r;
265,661✔
1285

1286
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
265,661✔
1287
        assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
265,661✔
1288

1289
        /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
1290
         * the same error here). */
1291
        if (FLAGS_SET(open_flags, O_DIRECTORY) && !IN_SET(locktype, LOCK_BSD, LOCK_NONE))
265,661✔
1292
                return -EBADF;
1293

1294
        for (;;) {
314,254✔
1295
                struct stat st;
289,957✔
1296

1297
                fd = xopenat_full(dir_fd, path, open_flags, xopen_flags, mode);
289,957✔
1298
                if (fd < 0)
289,957✔
1299
                        return fd;
8✔
1300

1301
                r = lock_generic(fd, locktype, operation);
289,957✔
1302
                if (r < 0)
289,957✔
1303
                        return r;
1304

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

1309
                if (fstat(fd, &st) < 0)
289,949✔
1310
                        return -errno;
×
1311
                if (st.st_nlink > 0)
289,949✔
1312
                        break;
1313

1314
                fd = safe_close(fd);
24,297✔
1315
        }
1316

1317
        return TAKE_FD(fd);
265,652✔
1318
}
1319

1320
int link_fd(int fd, int newdirfd, const char *newpath) {
3,579✔
1321
        int r, k;
3,579✔
1322

1323
        assert(fd >= 0);
3,579✔
1324
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
3,579✔
1325
        assert(newpath);
3,579✔
1326

1327
        /* Try linking via /proc/self/fd/ first. */
1328
        r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
3,579✔
1329
        if (r != -ENOENT)
902✔
1330
                return r;
3,579✔
1331

1332
        /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a
1333
         * more recent kernel, but does not require /proc/ mounted) */
1334
        k = proc_mounted();
×
1335
        if (k < 0)
×
1336
                return r;
1337
        if (k > 0)
×
1338
                return -EBADF;
1339

1340
        return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
×
1341
}
1342

1343
int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
1,823✔
1344
        _cleanup_close_ int old_fd = -EBADF;
1,823✔
1345
        int r;
1,823✔
1346

1347
        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
1,823✔
1348
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
1,823✔
1349
        assert(!isempty(newpath)); /* source path is optional, but the target path is not */
1,823✔
1350

1351
        /* Like linkat() but replaces the target if needed. Is a NOP if source and target already share the
1352
         * same inode. */
1353

1354
        if (olddirfd == AT_FDCWD && isempty(oldpath)) /* Refuse operating on the cwd (which is a dir, and dirs can't be hardlinked) */
3,646✔
1355
                return -EISDIR;
1356

1357
        if (path_implies_directory(oldpath)) /* Refuse these definite directories early */
1,823✔
1358
                return -EISDIR;
1359

1360
        if (path_implies_directory(newpath))
1,823✔
1361
                return -EISDIR;
1362

1363
        /* First, try to link this directly */
1364
        if (oldpath)
1,823✔
1365
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
24✔
1366
        else
1367
                r = link_fd(olddirfd, newdirfd, newpath);
1,799✔
1368
        if (r >= 0)
1,807✔
1369
                return 0;
916✔
1370
        if (r != -EEXIST)
907✔
1371
                return r;
1372

1373
        old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC);
907✔
1374
        if (old_fd < 0)
907✔
1375
                return old_fd;
1376

1377
        struct stat old_st;
907✔
1378
        if (fstat(old_fd, &old_st) < 0)
907✔
1379
                return -errno;
×
1380

1381
        if (S_ISDIR(old_st.st_mode)) /* Don't bother if we are operating on a directory */
907✔
1382
                return -EISDIR;
1383

1384
        struct stat new_st;
907✔
1385
        if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0)
907✔
1386
                return -errno;
×
1387

1388
        if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */
907✔
1389
                return -EEXIST;
1390

1391
        if (stat_inode_same(&old_st, &new_st)) /* Already the same inode? Then shortcut this */
907✔
1392
                return 0;
1393

1394
        _cleanup_free_ char *tmp_path = NULL;
906✔
1395
        r = tempfn_random(newpath, /* extra= */ NULL, &tmp_path);
906✔
1396
        if (r < 0)
906✔
1397
                return r;
1398

1399
        r = link_fd(old_fd, newdirfd, tmp_path);
906✔
1400
        if (r < 0) {
906✔
1401
                if (!ERRNO_IS_PRIVILEGE(r))
×
1402
                        return r;
1403

1404
                /* If that didn't work due to permissions then go via the path of the dentry */
1405
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, tmp_path, 0));
×
1406
                if (r < 0)
×
1407
                        return r;
1408
        }
1409

1410
        r = RET_NERRNO(renameat(newdirfd, tmp_path, newdirfd, newpath));
906✔
1411
        if (r < 0) {
×
1412
                (void) unlinkat(newdirfd, tmp_path, /* flags= */ 0);
×
1413
                return r;
×
1414
        }
1415

1416
        return 0;
1417
}
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