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

systemd / systemd / 14119061320

27 Mar 2025 08:38PM UTC coverage: 71.976% (+0.02%) from 71.954%
14119061320

push

github

web-flow
test: Make it possible to run the integration tests standalone (#36868)

Currently, to run the integration tests, it's still necessary to
install various other build tools besides meson: A compiler, gperf,
libcap, ... which we want to avoid in CI systems where we receive
prebuilt systemd packages and only want to test them. Examples are
Debian's autopkgtest CI and Fedora CI. Let's make it possible for
these systems to run the integration tests without having to install
any other build dependency besides meson by extracting the logic
required to run the integration tests with meson into a separate
subdirectory and adding a standalone top-level meson.build file which
can be used to configure a meson tree with as its only purpose running
the integration tests.

Practically, we do the following:
- all the integration test directories and integration-test-wrapper.py
  are moved from test/ to test/integration-tests/.
- All the installation logic is kept out of test/integration-tests/ or
  any of its subdirectories and moved into test/meson.build instead.
- We add test/integration-tests/standalone/meson.build to run the
  integration tests standalone. This meson file includes
  test/integration-tests via a cute symlink hack to trick meson into
  including a parent directory with subdir().
- Documentation is included on how to use the new standalone mode.

296746 of 412283 relevant lines covered (71.98%)

719287.73 hits per line

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

80.39
/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) {
70,527✔
42
        char *p;
70,527✔
43
        int r;
70,527✔
44

45
        assert(path);
70,527✔
46
        assert(stop);
70,527✔
47

48
        if (!path_is_safe(path))
70,527✔
49
                return -EINVAL;
50

51
        if (!path_is_safe(stop))
70,526✔
52
                return -EINVAL;
53

54
        p = strdupa_safe(path);
70,525✔
55

56
        for (;;) {
3,277✔
57
                char *slash = NULL;
73,802✔
58

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

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

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

72
                if (rmdir(p) < 0 && errno != ENOENT)
73,666✔
73
                        return -errno;
70,389✔
74
        }
75
}
76

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

80
        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
95✔
81
        assert(oldpath);
95✔
82
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
95✔
83
        assert(newpath);
95✔
84

85
        /* Try the ideal approach first */
86
        if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) >= 0)
95✔
87
                return 0;
88

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

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

99
                r = RET_NERRNO(unlinkat(olddirfd, oldpath, 0));
×
100
                if (r < 0) {
×
101
                        (void) unlinkat(newdirfd, newpath, 0);
×
102
                        return r;
×
103
                }
104

105
                return 0;
×
106
        }
107

108
        if (!ERRNO_IS_NOT_SUPPORTED(errno) && !IN_SET(errno, EINVAL, EPERM)) /* FAT returns EPERM on link()… */
×
109
                return -errno;
×
110

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

114
        if (faccessat(newdirfd, newpath, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
×
115
                return -EEXIST;
116
        if (errno != ENOENT)
×
117
                return -errno;
×
118

119
        return RET_NERRNO(renameat(olddirfd, oldpath, newdirfd, newpath));
×
120
}
121

122
int readlinkat_malloc(int fd, const char *p, char **ret) {
3,835,616✔
123
        size_t l = PATH_MAX;
3,835,616✔
124

125
        assert(fd >= 0 || fd == AT_FDCWD);
3,835,616✔
126

127
        if (fd < 0 && isempty(p))
3,835,616✔
128
                return -EISDIR; /* In this case, the fd points to the current working directory, and is
129
                                 * definitely not a symlink. Let's return earlier. */
130

131
        for (;;) {
3,835,616✔
132
                _cleanup_free_ char *c = NULL;
3,835,616✔
133
                ssize_t n;
3,835,616✔
134

135
                c = new(char, l+1);
3,835,616✔
136
                if (!c)
3,835,616✔
137
                        return -ENOMEM;
138

139
                n = readlinkat(fd, strempty(p), c, l);
3,835,618✔
140
                if (n < 0)
3,835,616✔
141
                        return -errno;
562,650✔
142

143
                if ((size_t) n < l) {
3,272,966✔
144
                        c[n] = 0;
3,272,966✔
145

146
                        if (ret)
3,272,966✔
147
                                *ret = TAKE_PTR(c);
3,272,966✔
148

149
                        return 0;
3,272,966✔
150
                }
151

152
                if (l > (SSIZE_MAX-1)/2) /* readlinkat() returns an ssize_t, and we want an extra byte for a
×
153
                                          * trailing NUL, hence do an overflow check relative to SSIZE_MAX-1
154
                                          * here */
155
                        return -EFBIG;
156

157
                l *= 2;
×
158
        }
159
}
160

161
int readlink_value(const char *p, char **ret) {
759,354✔
162
        _cleanup_free_ char *link = NULL, *name = NULL;
759,354✔
163
        int r;
759,354✔
164

165
        assert(p);
759,354✔
166
        assert(ret);
759,354✔
167

168
        r = readlink_malloc(p, &link);
759,354✔
169
        if (r < 0)
759,354✔
170
                return r;
171

172
        r = path_extract_filename(link, &name);
399,067✔
173
        if (r < 0)
399,067✔
174
                return r;
175
        if (r == O_DIRECTORY)
399,067✔
176
                return -EINVAL;
177

178
        *ret = TAKE_PTR(name);
399,067✔
179
        return 0;
399,067✔
180
}
181

182
int readlink_and_make_absolute(const char *p, char **ret) {
4,220✔
183
        _cleanup_free_ char *target = NULL;
4,220✔
184
        int r;
4,220✔
185

186
        assert(p);
4,220✔
187
        assert(ret);
4,220✔
188

189
        r = readlink_malloc(p, &target);
4,220✔
190
        if (r < 0)
4,220✔
191
                return r;
192

193
        return file_in_same_dir(p, target, ret);
28✔
194
}
195

196
int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
26,797✔
197
        _cleanup_close_ int fd = -EBADF;
26,797✔
198

199
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
26,797✔
200

201
        if (path) {
26,797✔
202
                /* Let's acquire an O_PATH fd, as precaution to change mode/owner on the same file */
203
                fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
26,797✔
204
                if (fd < 0)
26,797✔
205
                        return -errno;
×
206
                dir_fd = fd;
207

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

216
        return fchmod_and_chown(dir_fd, mode, uid, gid);
26,797✔
217
}
218

219
int fchmod_and_chown_with_fallback(int fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
117,148✔
220
        bool do_chown, do_chmod;
117,148✔
221
        struct stat st;
117,148✔
222
        int r;
117,148✔
223

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

236
        if (fstat(fd, &st) < 0)
117,148✔
237
                return -errno;
×
238

239
        do_chown =
234,296✔
240
                (uid != UID_INVALID && st.st_uid != uid) ||
117,148✔
241
                (gid != GID_INVALID && st.st_gid != gid);
15,745✔
242

243
        do_chmod =
234,296✔
244
                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
117,148✔
245
                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
117,140✔
246
                 do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown()
247
                             * modifies the access mode too */
248

249
        if (mode == MODE_INVALID)
117,148✔
250
                mode = st.st_mode; /* If we only shall do a chown(), save original mode, since chown() might break it. */
251
        else if ((mode & S_IFMT) != 0 && ((mode ^ st.st_mode) & S_IFMT) != 0)
77,594✔
252
                return -EINVAL; /* insist on the right file type if it was specified */
253

254
        if (do_chown && do_chmod) {
117,144✔
255
                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
20,154✔
256

257
                if (((minimal ^ st.st_mode) & 07777) != 0) {
20,154✔
258
                        r = fchmod_opath(fd, minimal & 07777);
7✔
259
                        if (r < 0) {
7✔
260
                                if (!path || r != -ENOSYS)
×
261
                                        return r;
262

263
                                /* Fallback path which doesn't use /proc/self/fd/. */
264
                                if (chmod(path, minimal & 07777) < 0)
×
265
                                        return -errno;
×
266
                        }
267
                }
268
        }
269

270
        if (do_chown)
117,144✔
271
                if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0)
20,157✔
272
                        return -errno;
1✔
273

274
        if (do_chmod) {
117,143✔
275
                r = fchmod_opath(fd, mode & 07777);
20,415✔
276
                if (r < 0) {
20,415✔
277
                        if (!path || r != -ENOSYS)
×
278
                                return r;
279

280
                        /* Fallback path which doesn't use /proc/self/fd/. */
281
                        if (chmod(path, mode & 07777) < 0)
×
282
                                return -errno;
×
283
                }
284
        }
285

286
        return do_chown || do_chmod;
117,143✔
287
}
288

289
int fchmod_umask(int fd, mode_t m) {
2,915✔
290
        _cleanup_umask_ mode_t u = umask(0777);
2,915✔
291

292
        return RET_NERRNO(fchmod(fd, m & (~u)));
2,915✔
293
}
294

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

307
        assert(fd >= 0);
25,232✔
308

309
        if (fchmodat2(fd, "", m, AT_EMPTY_PATH) >= 0)
25,232✔
310
                return 0;
311
        if (!IN_SET(errno, ENOSYS, EPERM)) /* Some container managers block unknown syscalls with EPERM */
2✔
312
                return -errno;
2✔
313

314
        if (chmod(FORMAT_PROC_FD_PATH(fd), m) < 0) {
×
315
                if (errno != ENOENT)
×
316
                        return -errno;
×
317

318
                return proc_fd_enoent_errno();
×
319
        }
320

321
        return 0;
×
322
}
323

324
int futimens_opath(int fd, const struct timespec ts[2]) {
114,823✔
325
        /* Similar to fchmod_opath() but for futimens() */
326

327
        assert(fd >= 0);
114,823✔
328

329
        if (utimensat(fd, "", ts, AT_EMPTY_PATH) >= 0)
114,823✔
330
                return 0;
331
        if (errno != EINVAL)
×
332
                return -errno;
×
333

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

337
        if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, /* flags = */ 0) < 0) {
×
338
                if (errno != ENOENT)
×
339
                        return -errno;
×
340

341
                return proc_fd_enoent_errno();
×
342
        }
343

344
        return 0;
×
345
}
346

347
int stat_warn_permissions(const char *path, const struct stat *st) {
113,878✔
348
        assert(path);
113,878✔
349
        assert(st);
113,878✔
350

351
        /* Don't complain if we are reading something that is not a file, for example /dev/null */
352
        if (!S_ISREG(st->st_mode))
113,878✔
353
                return 0;
354

355
        if (st->st_mode & 0111)
113,877✔
356
                log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path);
×
357

358
        if (st->st_mode & 0002)
113,877✔
359
                log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
×
360

361
        if (getpid_cached() == 1 && (st->st_mode & 0044) != 0044)
113,877✔
362
                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✔
363

364
        return 0;
365
}
366

367
int fd_warn_permissions(const char *path, int fd) {
11,176✔
368
        struct stat st;
11,176✔
369

370
        assert(path);
11,176✔
371
        assert(fd >= 0);
11,176✔
372

373
        if (fstat(fd, &st) < 0)
11,176✔
374
                return -errno;
×
375

376
        return stat_warn_permissions(path, &st);
11,176✔
377
}
378

379
int touch_fd(int fd, usec_t stamp) {
73,088✔
380
        assert(fd >= 0);
73,088✔
381

382
        if (stamp == USEC_INFINITY)
73,088✔
383
                return futimens_opath(fd, /* ts= */ NULL);
72,387✔
384

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

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

395
        assert(path);
73,092✔
396

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

402
        if (parents)
73,092✔
403
                (void) mkdir_parents(path, 0755);
32,928✔
404

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

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

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

425
        return RET_GATHER(ret, touch_fd(fd, stamp));
73,088✔
426
}
427

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

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

435
        if (make_relative) {
519✔
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) {
519✔
444
                _cleanup_free_ char *p = NULL;
66✔
445

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

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

455
                if (!streq(p, from)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
66✔
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) {
148,381✔
463
        _cleanup_free_ char *relpath = NULL, *t = NULL;
148,381✔
464
        int r;
148,381✔
465

466
        assert(from);
148,381✔
467
        assert(to);
148,381✔
468

469
        if (make_relative) {
148,381✔
470
                r = path_make_relative_parent(to, from, &relpath);
144,270✔
471
                if (r < 0)
144,270✔
472
                        return r;
473

474
                from = relpath;
144,270✔
475
        }
476

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

481
        if (symlinkat(from, atfd, t) < 0)
148,381✔
482
                return -errno;
×
483

484
        r = RET_NERRNO(renameat(atfd, t, atfd, to));
148,382✔
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 ***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,264✔
554
                if (!dirent_is_file(de))
967✔
555
                        continue;
652✔
556

557
                if (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 (list)
297✔
572
                *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,210✔
647
        assert(ret);
2,210✔
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,210✔
655
}
656

657
int tmp_dir(const char **ret) {
110✔
658
        assert(ret);
110✔
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);
110✔
664
}
665

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

669
        if (unlink(filename) < 0 && errno != ENOENT)
228✔
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
int access_fd(int fd, int mode) {
16,715✔
680
        assert(fd >= 0);
16,715✔
681

682
        /* Like access() but operates on an already open fd */
683

684
        if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0)
16,715✔
685
                return 0;
686
        if (errno != EINVAL)
563✔
687
                return -errno;
563✔
688

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

692
        if (access(FORMAT_PROC_FD_PATH(fd), mode) < 0) {
×
693
                if (errno != ENOENT)
×
694
                        return -errno;
×
695

696
                return proc_fd_enoent_errno();
×
697
        }
698

699
        return 0;
×
700
}
701

702
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
75✔
703
        _cleanup_close_ int truncate_fd = -EBADF;
75✔
704
        struct stat st;
75✔
705
        off_t l, bs;
75✔
706

707
        assert(fd >= 0 || fd == AT_FDCWD);
75✔
708
        assert(name);
75✔
709
        assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0);
75✔
710

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

727
        if (!FLAGS_SET(flags, UNLINK_REMOVEDIR)) {
75✔
728
                truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
75✔
729
                if (truncate_fd < 0) {
75✔
730

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

738
                        if (errno != ELOOP) /* don't complain if this is a symlink */
×
739
                                log_debug_errno(errno, "Failed to open file '%s' for deallocation, ignoring: %m", name);
×
740
                }
741
        }
742

743
        if (unlinkat(fd, name, FLAGS_SET(flags, UNLINK_REMOVEDIR) ? AT_REMOVEDIR : 0) < 0)
75✔
744
                return -errno;
×
745

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

749
        if (fstat(truncate_fd, &st) < 0) {
75✔
750
                log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
×
751
                return 0;
×
752
        }
753

754
        if (!S_ISREG(st.st_mode))
75✔
755
                return 0;
756

757
        if (FLAGS_SET(flags, UNLINK_ERASE) && st.st_size > 0 && st.st_nlink == 0) {
75✔
758
                uint64_t left = st.st_size;
3✔
759
                char buffer[64 * 1024];
3✔
760

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

769
                random_bytes(buffer, sizeof(buffer));
3✔
770

771
                while (left > 0) {
6✔
772
                        ssize_t n;
3✔
773

774
                        n = write(truncate_fd, buffer, MIN(sizeof(buffer), left));
3✔
775
                        if (n < 0) {
3✔
776
                                log_debug_errno(errno, "Failed to erase data in file '%s', ignoring.", name);
×
777
                                break;
778
                        }
779

780
                        assert(left >= (size_t) n);
3✔
781
                        left -= n;
3✔
782
                }
783

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

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

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

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

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

804
        /* Fall back to truncation */
805
        if (ftruncate(truncate_fd, 0) < 0) {
×
806
                log_debug_errno(errno, "Failed to truncate file to 0, ignoring: %m");
×
807
                return 0;
×
808
        }
809

810
        return 0;
811
}
812

813
int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
1,808,026✔
814
        _cleanup_free_ char *parent = NULL;
1,808,026✔
815
        int r;
1,808,026✔
816

817
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,808,026✔
818
        assert(path);
1,808,026✔
819

820
        r = path_extract_directory(path, &parent);
1,808,026✔
821
        if (r == -EDESTADDRREQ) {
1,808,026✔
822
                parent = strdup(".");
35✔
823
                if (!parent)
35✔
824
                        return -ENOMEM;
825
        } else if (r == -EADDRNOTAVAIL) {
1,807,991✔
826
                parent = strdup(path);
×
827
                if (!parent)
×
828
                        return -ENOMEM;
829
        } else if (r < 0)
1,807,991✔
830
                return r;
831

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

835
        if (FLAGS_SET(flags, O_PATH))
1,808,026✔
836
                flags |= O_DIRECTORY;
×
837
        else if (!FLAGS_SET(flags, O_TMPFILE))
1,808,026✔
838
                flags |= O_DIRECTORY|O_RDONLY;
1,805,255✔
839

840
        return RET_NERRNO(openat(dir_fd, parent, flags, mode));
1,808,059✔
841
}
842

843
int conservative_renameat(
37,404✔
844
                int olddirfd, const char *oldpath,
845
                int newdirfd, const char *newpath) {
846

847
        _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF;
37,404✔
848
        struct stat old_stat, new_stat;
37,404✔
849

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

857
        old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
37,404✔
858
        if (old_fd < 0)
37,404✔
859
                goto do_rename;
×
860

861
        new_fd = openat(newdirfd, newpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
37,404✔
862
        if (new_fd < 0)
37,404✔
863
                goto do_rename;
3,260✔
864

865
        if (fstat(old_fd, &old_stat) < 0)
34,144✔
866
                goto do_rename;
×
867

868
        if (!S_ISREG(old_stat.st_mode))
34,144✔
869
                goto do_rename;
×
870

871
        if (fstat(new_fd, &new_stat) < 0)
34,144✔
872
                goto do_rename;
×
873

874
        if (stat_inode_same(&new_stat, &old_stat))
34,144✔
875
                goto is_same;
1✔
876

877
        if (old_stat.st_mode != new_stat.st_mode ||
34,143✔
878
            old_stat.st_size != new_stat.st_size ||
34,143✔
879
            old_stat.st_uid != new_stat.st_uid ||
23,805✔
880
            old_stat.st_gid != new_stat.st_gid)
23,805✔
881
                goto do_rename;
10,338✔
882

883
        for (;;) {
3✔
884
                uint8_t buf1[16*1024];
23,808✔
885
                uint8_t buf2[sizeof(buf1)];
23,808✔
886
                ssize_t l1, l2;
23,808✔
887

888
                l1 = read(old_fd, buf1, sizeof(buf1));
23,808✔
889
                if (l1 < 0)
23,808✔
890
                        goto do_rename;
285✔
891

892
                if (l1 == sizeof(buf1))
23,808✔
893
                        /* Read the full block, hence read a full block in the other file too */
894

895
                        l2 = read(new_fd, buf2, l1);
4✔
896
                else {
897
                        assert((size_t) l1 < sizeof(buf1));
23,804✔
898

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

903
                        assert((size_t) (l1 + 1) <= sizeof(buf2));
23,804✔
904
                        l2 = read(new_fd, buf2, l1 + 1);
23,804✔
905
                }
906
                if (l2 != l1)
23,808✔
907
                        goto do_rename;
×
908

909
                if (memcmp(buf1, buf2, l1) != 0)
23,808✔
910
                        goto do_rename;
285✔
911

912
                if ((size_t) l1 < sizeof(buf1)) /* We hit EOF on the first file, and the second file too, hence exit
23,523✔
913
                                                 * now. */
914
                        break;
915
        }
916

917
is_same:
23,521✔
918
        /* Everything matches? Then don't rename, instead remove the source file, and leave the existing
919
         * destination in place */
920

921
        if (unlinkat(olddirfd, oldpath, 0) < 0)
23,521✔
922
                goto do_rename;
×
923

924
        return 0;
925

926
do_rename:
13,883✔
927
        if (renameat(olddirfd, oldpath, newdirfd, newpath) < 0)
13,883✔
928
                return -errno;
×
929

930
        return 1;
931
}
932

933
int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) {
1,659✔
934
        RateLimit rl;
1,659✔
935
        int r;
1,659✔
936

937
        r = posix_fallocate(fd, offset, size); /* returns positive errnos on error */
1,659✔
938
        if (r != EINTR)
1,659✔
939
                return -r; /* Let's return negative errnos, like common in our codebase */
1,659✔
940

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

950
        return -EINTR;
951
}
952

953
int parse_cifs_service(
15✔
954
                const char *s,
955
                char **ret_host,
956
                char **ret_service,
957
                char **ret_path) {
958

959
        _cleanup_free_ char *h = NULL, *ss = NULL, *x = NULL;
15✔
960
        const char *p, *e, *d;
15✔
961
        char delimiter;
15✔
962

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

967
        if (!s)
15✔
968
                return -EINVAL;
969

970
        p = startswith(s, "//");
14✔
971
        if (!p) {
14✔
972
                p = startswith(s, "\\\\");
6✔
973
                if (!p)
6✔
974
                        return -EINVAL;
975
        }
976

977
        delimiter = s[0];
11✔
978
        e = strchr(p, delimiter);
11✔
979
        if (!e)
11✔
980
                return -EINVAL;
981

982
        h = strndup(p, e - p);
11✔
983
        if (!h)
11✔
984
                return -ENOMEM;
985

986
        if (!hostname_is_valid(h, 0))
11✔
987
                return -EINVAL;
988

989
        e++;
10✔
990

991
        d = strchrnul(e, delimiter);
10✔
992

993
        ss = strndup(e, d - e);
10✔
994
        if (!ss)
10✔
995
                return -ENOMEM;
996

997
        if (!filename_is_valid(ss))
10✔
998
                return -EINVAL;
999

1000
        if (!isempty(d)) {
8✔
1001
                x = strdup(skip_leading_chars(d, CHAR_TO_STR(delimiter)));
6✔
1002
                if (!x)
6✔
1003
                        return -EINVAL;
2✔
1004

1005
                /* Make sure to convert Windows-style "\" → Unix-style / */
1006
                for (char *i = x; *i; i++)
33✔
1007
                        if (*i == delimiter)
27✔
1008
                                *i = '/';
3✔
1009

1010
                if (!path_is_valid(x))
6✔
1011
                        return -EINVAL;
1012

1013
                path_simplify(x);
6✔
1014
                if (!path_is_normalized(x))
6✔
1015
                        return -EINVAL;
1016
        }
1017

1018
        if (ret_host)
6✔
1019
                *ret_host = TAKE_PTR(h);
6✔
1020
        if (ret_service)
6✔
1021
                *ret_service = TAKE_PTR(ss);
6✔
1022
        if (ret_path)
6✔
1023
                *ret_path = TAKE_PTR(x);
6✔
1024

1025
        return 0;
1026
}
1027

1028
int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode) {
245,952✔
1029
        _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
245,952✔
1030
        _cleanup_free_ char *fname = NULL, *parent = NULL;
245,952✔
1031
        int r;
245,952✔
1032

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

1037
        if (flags & ~(O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_EXCL|O_NOATIME|O_NOFOLLOW|O_PATH))
245,952✔
1038
                return -EINVAL;
1039
        if ((flags & O_ACCMODE) != O_RDONLY)
245,952✔
1040
                return -EINVAL;
1041

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

1045
        /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
1046
         * that we can pin it, and operate below it. */
1047
        r = path_extract_directory(path, &parent);
245,952✔
1048
        if (r < 0) {
245,952✔
1049
                if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
6,551✔
1050
                        return r;
1051
        } else {
1052
                r = path_extract_filename(path, &fname);
239,401✔
1053
                if (r < 0)
239,401✔
1054
                        return r;
1055

1056
                parent_fd = openat(dirfd, parent, O_PATH|O_DIRECTORY|O_CLOEXEC);
239,401✔
1057
                if (parent_fd < 0)
239,401✔
1058
                        return -errno;
×
1059

1060
                dirfd = parent_fd;
239,401✔
1061
                path = fname;
239,401✔
1062
        }
1063

1064
        fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode);
245,952✔
1065
        if (IN_SET(fd, -ELOOP, -ENOTDIR))
491,903✔
1066
                return -EEXIST;
1067
        if (fd < 0)
245,951✔
1068
                return fd;
5,079✔
1069

1070
        return TAKE_FD(fd);
1071
}
1072

1073
int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) {
2,570,125✔
1074
        int fd;
2,570,125✔
1075

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

1085
        if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) {
2,570,125✔
1086
                fd = openat(dirfd, pathname, flags, mode);
533,017✔
1087
                if (fd < 0)
533,017✔
1088
                        return -errno;
40,861✔
1089

1090
                if (ret_newly_created)
492,156✔
1091
                        *ret_newly_created = FLAGS_SET(flags, O_CREAT);
492,156✔
1092
                return fd;
492,156✔
1093
        }
1094

1095
        for (unsigned attempts = 7;;) {
1096
                /* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */
1097
                fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode);
2,037,626✔
1098
                if (fd >= 0) {
2,037,626✔
1099
                        if (ret_newly_created)
1,817,536✔
1100
                                *ret_newly_created = false;
1,817,536✔
1101
                        return fd;
1,817,536✔
1102
                }
1103
                if (errno != ENOENT)
220,090✔
1104
                        return -errno;
×
1105

1106
                /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL/O_NOFOLLOW. */
1107
                fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL | O_NOFOLLOW, mode);
220,090✔
1108
                if (fd >= 0) {
220,090✔
1109
                        if (ret_newly_created)
219,570✔
1110
                                *ret_newly_created = true;
219,569✔
1111
                        return fd;
219,570✔
1112
                }
1113
                if (errno != EEXIST)
520✔
1114
                        return -errno;
×
1115

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

1119
                if (--attempts == 0) /* Give up eventually, somebody is playing with us */
520✔
1120
                        return -EEXIST;
1121
        }
1122
}
1123

1124
int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
10,223,910✔
1125
        _cleanup_close_ int fd = -EBADF;
10,223,910✔
1126
        bool made_dir = false, made_file = false;
10,223,910✔
1127
        int r;
10,223,910✔
1128

1129
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
10,223,910✔
1130

1131
        /* An inode cannot be both a directory and a regular file at the same time. */
1132
        assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR)));
10,223,910✔
1133

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

1150
        if (mode == MODE_INVALID)
10,223,910✔
1151
                mode = (open_flags & O_DIRECTORY) ? 0755 : 0644;
50,999✔
1152

1153
        if (isempty(path)) {
10,223,910✔
1154
                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
9,140,915✔
1155

1156
                if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
9,140,915✔
1157
                        r = fd_verify_regular(dir_fd);
95✔
1158
                        if (r < 0)
95✔
1159
                                return r;
1160
                }
1161

1162
                return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
9,140,915✔
1163
        }
1164

1165
        bool call_label_ops_post = false;
1,082,995✔
1166

1167
        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
1,082,995✔
1168
                r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG);
608✔
1169
                if (r < 0)
608✔
1170
                        return r;
1171

1172
                call_label_ops_post = true;
1173
        }
1174

1175
        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
1,082,995✔
1176
                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
280,760✔
1177
                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
×
1178
                else
1179
                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
280,760✔
1180
                if (r == -EEXIST) {
168,212✔
1181
                        if (FLAGS_SET(open_flags, O_EXCL))
168,208✔
1182
                                return -EEXIST;
1183
                } else if (r < 0)
112,552✔
1184
                        return r;
1185
                else
1186
                        made_dir = true;
1187

1188
                open_flags &= ~(O_EXCL|O_CREAT);
275,680✔
1189
        }
1190

1191
        if (FLAGS_SET(xopen_flags, XO_REGULAR)) {
1,077,915✔
1192
                /* Guarantee we return a regular fd only, and don't open the file unless we verified it
1193
                 * first */
1194

1195
                if (FLAGS_SET(open_flags, O_PATH)) {
402,862✔
1196
                        fd = openat(dir_fd, path, open_flags, mode);
1✔
1197
                        if (fd < 0) {
1✔
1198
                                r = -errno;
×
1199
                                goto error;
×
1200
                        }
1201

1202
                        r = fd_verify_regular(fd);
1✔
1203
                        if (r < 0)
1✔
1204
                                goto error;
×
1205

1206
                } else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) {
402,861✔
1207
                        /* In O_EXCL mode we can just create the thing, everything is dealt with for us */
1208
                        fd = openat(dir_fd, path, open_flags, mode);
2✔
1209
                        if (fd < 0) {
2✔
1210
                                r = -errno;
1✔
1211
                                goto error;
1✔
1212
                        }
1213

1214
                        made_file = true;
1✔
1215
                } else {
1216
                        /* Otherwise pin the inode first via O_PATH */
1217
                        _cleanup_close_ int inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|(open_flags & O_NOFOLLOW));
402,859✔
1218
                        if (inode_fd < 0) {
402,859✔
1219
                                if (errno != ENOENT || !FLAGS_SET(open_flags, O_CREAT)) {
21✔
1220
                                        r = -errno;
1✔
1221
                                        goto error;
1✔
1222
                                }
1223

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

1231
                                made_file = true;
20✔
1232
                        } else {
1233
                                /* OK, we pinned it. Now verify it's actually a regular file, and then reopen it */
1234
                                r = fd_verify_regular(inode_fd);
402,838✔
1235
                                if (r < 0)
402,838✔
1236
                                        goto error;
5✔
1237

1238
                                fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT));
402,833✔
1239
                                if (fd < 0) {
402,833✔
1240
                                        r = fd;
×
1241
                                        goto error;
×
1242
                                }
1243
                        }
1244
                }
1245
        } else {
1246
                fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
675,053✔
1247
                if (fd < 0) {
675,053✔
1248
                        r = fd;
39,260✔
1249
                        goto error;
39,260✔
1250
                }
1251
        }
1252

1253
        if (call_label_ops_post) {
1,038,648✔
1254
                call_label_ops_post = false;
608✔
1255

1256
                r = label_ops_post(fd, /* path= */ NULL, made_file || made_dir);
608✔
1257
                if (r < 0)
608✔
1258
                        goto error;
×
1259
        }
1260

1261
        if (FLAGS_SET(xopen_flags, XO_NOCOW)) {
1,038,648✔
1262
                r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
63✔
1263
                if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(r))
63✔
1264
                        goto error;
×
1265
        }
1266

1267
        return TAKE_FD(fd);
1268

1269
error:
39,267✔
1270
        if (call_label_ops_post)
39,267✔
1271
                (void) label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : path, made_dir || made_file);
×
1272

1273
        if (made_dir || made_file)
39,267✔
1274
                (void) unlinkat(dir_fd, path, made_dir ? AT_REMOVEDIR : 0);
×
1275

1276
        return r;
1277
}
1278

1279
int xopenat_lock_full(
251,184✔
1280
                int dir_fd,
1281
                const char *path,
1282
                int open_flags,
1283
                XOpenFlags xopen_flags,
1284
                mode_t mode,
1285
                LockType locktype,
1286
                int operation) {
1287

1288
        _cleanup_close_ int fd = -EBADF;
251,184✔
1289
        int r;
251,184✔
1290

1291
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
251,184✔
1292
        assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
251,184✔
1293

1294
        /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
1295
         * the same error here). */
1296
        if (FLAGS_SET(open_flags, O_DIRECTORY) && !IN_SET(locktype, LOCK_BSD, LOCK_NONE))
251,184✔
1297
                return -EBADF;
1298

1299
        for (;;) {
290,211✔
1300
                struct stat st;
270,697✔
1301

1302
                fd = xopenat_full(dir_fd, path, open_flags, xopen_flags, mode);
270,697✔
1303
                if (fd < 0)
270,697✔
1304
                        return fd;
8✔
1305

1306
                r = lock_generic(fd, locktype, operation);
270,697✔
1307
                if (r < 0)
270,697✔
1308
                        return r;
1309

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

1314
                if (fstat(fd, &st) < 0)
270,689✔
1315
                        return -errno;
×
1316
                if (st.st_nlink > 0)
270,689✔
1317
                        break;
1318

1319
                fd = safe_close(fd);
19,514✔
1320
        }
1321

1322
        return TAKE_FD(fd);
251,175✔
1323
}
1324

1325
int link_fd(int fd, int newdirfd, const char *newpath) {
3,583✔
1326
        int r, k;
3,583✔
1327

1328
        assert(fd >= 0);
3,583✔
1329
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
3,583✔
1330
        assert(newpath);
3,583✔
1331

1332
        /* Try linking via /proc/self/fd/ first. */
1333
        r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
3,583✔
1334
        if (r != -ENOENT)
903✔
1335
                return r;
3,583✔
1336

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

1345
        return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
×
1346
}
1347

1348
int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
1,835✔
1349
        _cleanup_close_ int old_fd = -EBADF;
1,835✔
1350
        int r;
1,835✔
1351

1352
        assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
1,835✔
1353
        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
1,835✔
1354
        assert(!isempty(newpath)); /* source path is optional, but the target path is not */
1,835✔
1355

1356
        /* Like linkat() but replaces the target if needed. Is a NOP if source and target already share the
1357
         * same inode. */
1358

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

1362
        if (path_implies_directory(oldpath)) /* Refuse these definite directories early */
1,835✔
1363
                return -EISDIR;
1364

1365
        if (path_implies_directory(newpath))
1,835✔
1366
                return -EISDIR;
1367

1368
        /* First, try to link this directly */
1369
        if (oldpath)
1,835✔
1370
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
31✔
1371
        else
1372
                r = link_fd(olddirfd, newdirfd, newpath);
1,804✔
1373
        if (r >= 0)
1,810✔
1374
                return 0;
929✔
1375
        if (r != -EEXIST)
906✔
1376
                return r;
1377

1378
        old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC);
906✔
1379
        if (old_fd < 0)
906✔
1380
                return old_fd;
1381

1382
        struct stat old_st;
906✔
1383
        if (fstat(old_fd, &old_st) < 0)
906✔
1384
                return -errno;
×
1385

1386
        if (S_ISDIR(old_st.st_mode)) /* Don't bother if we are operating on a directory */
906✔
1387
                return -EISDIR;
1388

1389
        struct stat new_st;
906✔
1390
        if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0)
906✔
1391
                return -errno;
×
1392

1393
        if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */
906✔
1394
                return -EEXIST;
1395

1396
        if (stat_inode_same(&old_st, &new_st)) /* Already the same inode? Then shortcut this */
906✔
1397
                return 0;
1398

1399
        _cleanup_free_ char *tmp_path = NULL;
905✔
1400
        r = tempfn_random(newpath, /* extra= */ NULL, &tmp_path);
905✔
1401
        if (r < 0)
905✔
1402
                return r;
1403

1404
        r = link_fd(old_fd, newdirfd, tmp_path);
905✔
1405
        if (r < 0) {
905✔
1406
                if (!ERRNO_IS_PRIVILEGE(r))
×
1407
                        return r;
1408

1409
                /* If that didn't work due to permissions then go via the path of the dentry */
1410
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, tmp_path, 0));
×
1411
                if (r < 0)
×
1412
                        return r;
1413
        }
1414

1415
        r = RET_NERRNO(renameat(newdirfd, tmp_path, newdirfd, newpath));
905✔
1416
        if (r < 0) {
×
1417
                (void) unlinkat(newdirfd, tmp_path, /* flags= */ 0);
×
1418
                return r;
×
1419
        }
1420

1421
        return 0;
1422
}
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