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

systemd / systemd / 15263807472

26 May 2025 08:53PM UTC coverage: 72.046% (-0.002%) from 72.048%
15263807472

push

github

yuwata
src/core/manager.c: log preset activity on first boot

This gives us a little more information about what units were enabled
or disabled on that first boot and will be useful for OS developers
tracking down the source of unit state.

An example with this enabled looks like:

```
NET: Registered PF_VSOCK protocol family
systemd[1]: Applying preset policy.
systemd[1]: Unit /etc/systemd/system/dnsmasq.service is masked, ignoring.
systemd[1]: Unit /etc/systemd/system/systemd-repart.service is masked, ignoring.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket'.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir.mount' → '/etc/systemd/system/var-mnt-workdir.mount'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir\x2dtmp.mount' → '/etc/systemd/system/var-mnt-workdir\x2dtmp.mount'.
systemd[1]: Created symlink '/etc/systemd/system/afterburn-sshkeys.target.requires/afterburn-sshkeys@core.service' → '/usr/lib/systemd/system/afterburn-sshkeys@.service'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket' → '/usr/lib/systemd/system/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket' → '/usr/lib/systemd/system/systemd-resolved-monitor.socket'.
systemd[1]: Populated /etc with preset unit settings.
```

Considering it only happens on first boot and not on every boot I think
the extra information is worth the extra verbosity in the logs just for
that boot.

5 of 6 new or added lines in 1 file covered. (83.33%)

5463 existing lines in 165 files now uncovered.

299151 of 415222 relevant lines covered (72.05%)

702386.45 hits per line

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

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

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

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

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

37
        assert(path);
92,310✔
38
        assert(stop);
92,310✔
39

40
        if (!path_is_safe(path))
92,310✔
41
                return -EINVAL;
42

43
        if (!path_is_safe(stop))
92,309✔
44
                return -EINVAL;
45

46
        p = strdupa_safe(path);
92,308✔
47

48
        for (;;) {
3,581✔
49
                char *slash = NULL;
95,889✔
50

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

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

61
                if (path_startswith_full(stop, p, PATH_STARTSWITH_REFUSE_DOT_DOT))
95,889✔
62
                        return 0;
63

64
                if (rmdir(p) < 0 && errno != ENOENT)
95,753✔
65
                        return -errno;
92,172✔
66
        }
67
}
68

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

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

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

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

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

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

UNCOV
97
                return 0;
×
98
        }
99

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

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

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

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

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

117
        assert(fd >= 0 || fd == AT_FDCWD);
3,516,550✔
118

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

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

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

131
                n = readlinkat(fd, strempty(p), c, l);
3,516,552✔
132
                if (n < 0)
3,516,550✔
133
                        return -errno;
422,282✔
134

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

138
                        if (ret)
3,094,268✔
139
                                *ret = TAKE_PTR(c);
3,094,268✔
140

141
                        return 0;
3,094,268✔
142
                }
143

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

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

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

157
        assert(p);
631,439✔
158
        assert(ret);
631,439✔
159

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

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

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

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

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

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

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

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

191
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
23,874✔
192

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

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

208
        return fchmod_and_chown(dir_fd, mode, uid, gid);
23,874✔
209
}
210

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

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

228
        if (fstat(fd, &st) < 0)
109,091✔
UNCOV
229
                return -errno;
×
230

231
        do_chown =
218,182✔
232
                (uid != UID_INVALID && st.st_uid != uid) ||
109,091✔
233
                (gid != GID_INVALID && st.st_gid != gid);
13,718✔
234

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

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

246
        if (do_chown && do_chmod) {
109,087✔
247
                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
17,606✔
248

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

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

262
        if (do_chown)
109,087✔
263
                if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0)
17,609✔
264
                        return -errno;
1✔
265

266
        if (do_chmod) {
109,086✔
267
                r = fchmod_opath(fd, mode & 07777);
17,785✔
268
                if (r < 0) {
17,785✔
UNCOV
269
                        if (!path || r != -ENOSYS)
×
270
                                return r;
271

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

278
        return do_chown || do_chmod;
109,086✔
279
}
280

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

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

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

299
        assert(fd >= 0);
20,660✔
300

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

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

UNCOV
310
                return proc_fd_enoent_errno();
×
311
        }
312

UNCOV
313
        return 0;
×
314
}
315

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

319
        assert(fd >= 0);
105,049✔
320

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

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

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

333
                return proc_fd_enoent_errno();
×
334
        }
335

UNCOV
336
        return 0;
×
337
}
338

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

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

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

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

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

356
        return 0;
357
}
358

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

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

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

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

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

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

378
        if (stamp == USEC_INFINITY)
70,573✔
379
                return futimens_opath(fd, /* ts= */ NULL);
69,972✔
380

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

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

391
        assert(path);
70,577✔
392

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

398
        if (parents)
70,577✔
399
                (void) mkdir_parents(path, 0755);
31,316✔
400

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

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

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

421
        return RET_GATHER(ret, touch_fd(fd, stamp));
70,573✔
422
}
423

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

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

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

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

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

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

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

459
        return 0;
460
}
461

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

466
        assert(from);
156,328✔
467
        assert(to);
156,328✔
468

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

474
                from = relpath;
153,825✔
475
        }
476

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

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

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

490
        return 0;
491
}
492

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

UNCOV
497
        assert(path);
×
498

UNCOV
499
        r = tempfn_random(path, NULL, &t);
×
UNCOV
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));
×
UNCOV
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✔
UNCOV
527
                return -errno;
×
528

529
        r = RET_NERRNO(renameat(atfd, t, atfd, path));
1✔
UNCOV
530
        if (r < 0) {
×
UNCOV
531
                (void) unlinkat(atfd, t, 0);
×
UNCOV
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,257✔
554
                if (!dirent_is_file(de))
960✔
555
                        continue;
645✔
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,323✔
578
        int r, ret = 0;
2,323✔
579

580
        assert(ret_path);
2,323✔
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,289✔
585
                const char *e;
6,967✔
586

587
                e = secure_getenv(n);
6,967✔
588
                if (!e)
6,967✔
589
                        continue;
6,965✔
590
                if (!path_is_absolute(e)) {
2✔
UNCOV
591
                        r = -ENOTDIR;
×
UNCOV
592
                        goto next;
×
593
                }
594
                if (!path_is_normalized(e)) {
2✔
UNCOV
595
                        r = -EPERM;
×
UNCOV
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✔
UNCOV
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,322✔
617
                return ret;
618

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

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

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

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

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

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

646
int var_tmp_dir(const char **ret) {
2,214✔
647
        assert(ret);
2,214✔
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,214✔
655
}
656

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

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

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

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

669
        if (unlink(filename) < 0 && errno != ENOENT)
175✔
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 */
UNCOV
673
                if (errno != EROFS || access(filename, F_OK) >= 0)
×
UNCOV
674
                        return log_error_errno(errno, "Failed to remove \"%s\": %m", filename);
×
675

676
        return 0;
677
}
678

679
char *rmdir_and_free(char *p) {
245,834✔
680
        PROTECT_ERRNO;
245,834✔
681

682
        if (!p)
245,834✔
683
                return NULL;
684

685
        (void) rmdir(p);
245,834✔
686
        return mfree(p);
245,834✔
687
}
688

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

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

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

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

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

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

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

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

UNCOV
716
                return proc_fd_enoent_errno();
×
717
        }
718

UNCOV
719
        return 0;
×
720
}
721

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

830
        return 0;
831
}
832

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

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

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

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

855
        if (FLAGS_SET(flags, O_PATH))
1,796,028✔
UNCOV
856
                flags |= O_DIRECTORY;
×
857
        else if (!FLAGS_SET(flags, O_TMPFILE))
1,796,028✔
858
                flags |= O_DIRECTORY|O_RDONLY;
1,789,068✔
859

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

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

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

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

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

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

885
        if (fstat(old_fd, &old_stat) < 0)
34,101✔
UNCOV
886
                goto do_rename;
×
887

888
        if (!S_ISREG(old_stat.st_mode))
34,101✔
UNCOV
889
                goto do_rename;
×
890

891
        if (fstat(new_fd, &new_stat) < 0)
34,101✔
UNCOV
892
                goto do_rename;
×
893

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

897
        if (old_stat.st_mode != new_stat.st_mode ||
34,100✔
898
            old_stat.st_size != new_stat.st_size ||
34,100✔
899
            old_stat.st_uid != new_stat.st_uid ||
23,923✔
900
            old_stat.st_gid != new_stat.st_gid)
23,923✔
901
                goto do_rename;
10,177✔
902

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

908
                l1 = read(old_fd, buf1, sizeof(buf1));
23,929✔
909
                if (l1 < 0)
23,929✔
910
                        goto do_rename;
293✔
911

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

915
                        l2 = read(new_fd, buf2, l1);
7✔
916
                else {
917
                        assert((size_t) l1 < sizeof(buf1));
23,922✔
918

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

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

929
                if (memcmp(buf1, buf2, l1) != 0)
23,929✔
930
                        goto do_rename;
293✔
931

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

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

941
        if (unlinkat(olddirfd, oldpath, 0) < 0)
23,631✔
UNCOV
942
                goto do_rename;
×
943

944
        return 0;
945

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

950
        return 1;
951
}
952

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

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

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

970
        return -EINTR;
971
}
972

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

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

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

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

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

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

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

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

1009
        e++;
10✔
1010

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

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

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

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

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

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

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

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

1045
        return 0;
1046
}
1047

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

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

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

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

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

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

1080
                dirfd = parent_fd;
242,139✔
1081
                path = fname;
242,139✔
1082
        }
1083

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

1090
        return TAKE_FD(fd);
1091
}
1092

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

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

1105
        if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) {
2,567,950✔
1106
                fd = openat(dirfd, pathname, flags, mode);
522,787✔
1107
                if (fd < 0)
522,787✔
1108
                        return -errno;
39,603✔
1109

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

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

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

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

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

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

1149
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
1,121,064✔
1150

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

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

1170
        if (mode == MODE_INVALID)
1,121,064✔
1171
                mode = (open_flags & O_DIRECTORY) ? 0755 : 0644;
51,239✔
1172

1173
        if (isempty(path)) {
1,121,064✔
1174
                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
5,531✔
1175

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

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

1185
        bool call_label_ops_post = false;
1,115,533✔
1186

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

1192
                call_label_ops_post = true;
1193
        }
1194

1195
        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
1,115,533✔
1196
                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
282,001✔
UNCOV
1197
                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
×
1198
                else
1199
                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
282,001✔
1200
                if (r == -EEXIST) {
150,488✔
1201
                        if (FLAGS_SET(open_flags, O_EXCL))
150,484✔
1202
                                return -EEXIST;
1203
                } else if (r < 0)
131,517✔
1204
                        return r;
1205
                else
1206
                        made_dir = true;
1207

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

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

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

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

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

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

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

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

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

1273
        if (call_label_ops_post) {
1,073,496✔
1274
                call_label_ops_post = false;
533✔
1275

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

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

1287
        return TAKE_FD(fd);
1288

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

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

1296
        return r;
1297
}
1298

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

1308
        _cleanup_close_ int fd = -EBADF;
277,311✔
1309
        int r;
277,311✔
1310

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

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

1319
        for (;;) {
312,998✔
1320
                struct stat st;
295,154✔
1321

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

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

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

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

1339
                fd = safe_close(fd);
17,844✔
1340
        }
1341

1342
        return TAKE_FD(fd);
277,302✔
1343
}
1344

1345
int link_fd(int fd, int newdirfd, const char *newpath) {
11,218✔
1346
        int r, k;
11,218✔
1347

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

1352
        /* Try linking via /proc/self/fd/ first. */
1353
        r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
11,218✔
1354
        if (r != -ENOENT)
4,348✔
1355
                return r;
11,218✔
1356

1357
        /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a
1358
         * more recent kernel, but does not require /proc/ mounted) */
1359
        k = proc_mounted();
×
UNCOV
1360
        if (k < 0)
×
1361
                return r;
UNCOV
1362
        if (k > 0)
×
1363
                return -EBADF;
1364

UNCOV
1365
        return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
×
1366
}
1367

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

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

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

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

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

1385
        if (path_implies_directory(newpath))
6,019✔
1386
                return -EISDIR;
1387

1388
        /* First, try to link this directly */
1389
        if (oldpath)
6,019✔
1390
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
31✔
1391
        else
1392
                r = link_fd(olddirfd, newdirfd, newpath);
5,988✔
1393
        if (r >= 0)
5,998✔
1394
                return 0;
1,664✔
1395
        if (r != -EEXIST)
4,355✔
1396
                return r;
1397

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

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

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

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

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

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

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

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

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

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

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