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

systemd / systemd / 12940458095

23 Jan 2025 10:34PM UTC coverage: 71.463% (+0.02%) from 71.444%
12940458095

push

github

web-flow
nspawn: support unpriv directory-tree containers (#35685)

So far nspawn supported unpriv containers only if backed by a DDI. This
adds dir-based unpriv containers too.

To make this work this introduces a new UID concept to systemd: the
"foreign UID range". This is a high UID range of size 64K. The idea is
that disk images that are "foreign" to the local system can use that,
and when a container or similar is invoked from it, a transiently
allocated dynamic UID range is mapped from that foreign UID range via id
mapped mounts.

This means the fully dynamic, transient UID ranges never hit the disk,
which should vastly simplify management, and does not require that uid
"subranges" are persistently delegated to any users.

The mountfsd daemon gained a new method call for acquiring an idmapped
mount fd for an mount tree owned by the foreign UID range. Access is
permitted to unpriv clients – as long as the referenced inode is located
within a dir owned by client's own uid range.

166 of 349 new or added lines in 6 files covered. (47.56%)

2432 existing lines in 65 files now uncovered.

291664 of 408131 relevant lines covered (71.46%)

709262.54 hits per line

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

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

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

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

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

45
        assert(path);
78,447✔
46
        assert(stop);
78,447✔
47

48
        if (!path_is_safe(path))
78,447✔
49
                return -EINVAL;
50

51
        if (!path_is_safe(stop))
78,446✔
52
                return -EINVAL;
53

54
        p = strdupa_safe(path);
78,445✔
55

56
        for (;;) {
3,331✔
57
                char *slash = NULL;
81,776✔
58

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

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

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

72
                if (rmdir(p) < 0 && errno != ENOENT)
81,640✔
73
                        return -errno;
78,309✔
74
        }
75
}
76

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

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

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

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

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

100
                return 0;
×
101
        }
102

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

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

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

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

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

120
        assert(fd >= 0 || fd == AT_FDCWD);
3,838,911✔
121

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

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

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

134
                n = readlinkat(fd, strempty(p), c, l);
3,838,913✔
135
                if (n < 0)
3,838,911✔
136
                        return -errno;
569,699✔
137

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

141
                        if (ret)
3,269,212✔
142
                                *ret = TAKE_PTR(c);
3,269,212✔
143

144
                        return 0;
3,269,212✔
145
                }
146

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

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

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

160
        assert(p);
764,030✔
161
        assert(ret);
764,030✔
162

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

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

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

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

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

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

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

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

194
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
26,553✔
195

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

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

211
        return fchmod_and_chown(dir_fd, mode, uid, gid);
26,553✔
212
}
213

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

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

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

234
        do_chown =
232,882✔
235
                (uid != UID_INVALID && st.st_uid != uid) ||
116,441✔
236
                (gid != GID_INVALID && st.st_gid != gid);
15,243✔
237

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

313
                return proc_fd_enoent_errno();
×
314
        }
315

316
        return 0;
×
317
}
318

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

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

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

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

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

336
                return proc_fd_enoent_errno();
×
337
        }
338

339
        return 0;
×
340
}
341

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

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

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

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

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

359
        return 0;
360
}
361

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

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

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

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

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

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

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

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

390
        assert(path);
73,134✔
391

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

397
        if (parents)
73,134✔
398
                (void) mkdir_parents(path, 0755);
32,987✔
399

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

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

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

420
        return RET_GATHER(ret, touch_fd(fd, stamp));
73,130✔
421
}
422

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

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

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

435
                from = relpath;
175✔
436
        }
437

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

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

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

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

454
        return 0;
455
}
456

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

461
        assert(from);
154,955✔
462
        assert(to);
154,955✔
463

464
        if (make_relative) {
154,955✔
465
                r = path_make_relative_parent(to, from, &relpath);
150,905✔
466
                if (r < 0)
150,905✔
467
                        return r;
468

469
                from = relpath;
150,905✔
470
        }
471

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

476
        if (symlinkat(from, atfd, t) < 0)
154,955✔
UNCOV
477
                return -errno;
×
478

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

485
        return 0;
486
}
487

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

492
        assert(path);
×
493

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

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

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

507
        return 0;
508
}
509

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

514
        assert(path);
1✔
515

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

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

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

530
        return 0;
531
}
532

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

538
        assert(path);
323✔
539

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

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

548
        FOREACH_DIRENT_ALL(de, d, return -errno) {
1,359✔
549
                if (!dirent_is_file(de))
1,038✔
550
                        continue;
699✔
551

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

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

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

566
        if (list)
321✔
567
                *list = TAKE_PTR(l);
318✔
568

569
        return n;
321✔
570
}
571

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

575
        assert(ret_path);
2,313✔
576

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

671
        return 0;
672
}
673

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

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

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

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

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

691
                return proc_fd_enoent_errno();
×
692
        }
693

694
        return 0;
×
695
}
696

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

805
        return 0;
806
}
807

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

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

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

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

830
        if (FLAGS_SET(flags, O_PATH))
1,703,414✔
831
                flags |= O_DIRECTORY;
×
832
        else if (!FLAGS_SET(flags, O_TMPFILE))
1,703,414✔
833
                flags |= O_DIRECTORY|O_RDONLY;
1,700,653✔
834

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

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

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

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

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

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

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

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

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

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

872
        if (old_stat.st_mode != new_stat.st_mode ||
32,046✔
873
            old_stat.st_size != new_stat.st_size ||
32,046✔
874
            old_stat.st_uid != new_stat.st_uid ||
22,102✔
875
            old_stat.st_gid != new_stat.st_gid)
22,102✔
876
                goto do_rename;
9,944✔
877

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

883
                l1 = read(old_fd, buf1, sizeof(buf1));
22,105✔
884
                if (l1 < 0)
22,105✔
885
                        goto do_rename;
275✔
886

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

890
                        l2 = read(new_fd, buf2, l1);
4✔
891
                else {
892
                        assert((size_t) l1 < sizeof(buf1));
22,101✔
893

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

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

904
                if (memcmp(buf1, buf2, l1) != 0)
22,105✔
905
                        goto do_rename;
275✔
906

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

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

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

919
        return 0;
920

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

925
        return 1;
926
}
927

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

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

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

945
        return -EINTR;
946
}
947

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

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

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

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

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

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

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

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

984
        e++;
10✔
985

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

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

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

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

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

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

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

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

1020
        return 0;
1021
}
1022

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

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

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

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

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

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

1055
                dirfd = parent_fd;
249,005✔
1056
                path = fname;
249,005✔
1057
        }
1058

1059
        fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode);
255,494✔
1060
        if (IN_SET(fd, -ELOOP, -ENOTDIR))
510,987✔
1061
                return -EEXIST;
1062
        if (fd < 0)
255,493✔
1063
                return fd;
5,034✔
1064

1065
        return TAKE_FD(fd);
1066
}
1067

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

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

1080
        if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) {
2,474,563✔
1081
                fd = openat(dirfd, pathname, flags, mode);
531,750✔
1082
                if (fd < 0)
531,750✔
1083
                        return -errno;
40,299✔
1084

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

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

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

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

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

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

1124
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
10,214,242✔
1125

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

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

1145
        if (mode == MODE_INVALID)
10,214,242✔
1146
                mode = (open_flags & O_DIRECTORY) ? 0755 : 0644;
50,868✔
1147

1148
        if (isempty(path)) {
10,214,242✔
1149
                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
9,124,855✔
1150

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

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

1160
        bool call_label_ops_post = false;
1,089,387✔
1161

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

1167
                call_label_ops_post = true;
1168
        }
1169

1170
        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
1,089,387✔
1171
                if (FLAGS_SET(xopen_flags, XO_SUBVOLUME))
289,354✔
1172
                        r = btrfs_subvol_make_fallback(dir_fd, path, mode);
×
1173
                else
1174
                        r = RET_NERRNO(mkdirat(dir_fd, path, mode));
289,354✔
1175
                if (r == -EEXIST) {
170,851✔
1176
                        if (FLAGS_SET(open_flags, O_EXCL))
170,847✔
1177
                                return -EEXIST;
1178
                } else if (r < 0)
118,507✔
1179
                        return r;
1180
                else
1181
                        made_dir = true;
1182

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

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

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

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

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

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

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

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

1233
                                fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT));
394,969✔
1234
                                if (fd < 0) {
394,969✔
1235
                                        r = fd;
×
1236
                                        goto error;
×
1237
                                }
1238
                        }
1239
                }
1240
        } else {
1241
                fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
689,354✔
1242
                if (fd < 0) {
689,354✔
1243
                        r = fd;
38,692✔
1244
                        goto error;
38,692✔
1245
                }
1246
        }
1247

1248
        if (call_label_ops_post) {
1,045,653✔
1249
                call_label_ops_post = false;
487✔
1250

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

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

1262
        return TAKE_FD(fd);
1263

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

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

1271
        return r;
1272
}
1273

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

1283
        _cleanup_close_ int fd = -EBADF;
260,007✔
1284
        int r;
260,007✔
1285

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

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

1294
        for (;;) {
300,736✔
1295
                struct stat st;
280,371✔
1296

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

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

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

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

1314
                fd = safe_close(fd);
20,365✔
1315
        }
1316

1317
        return TAKE_FD(fd);
259,998✔
1318
}
1319

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

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

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

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

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

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

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

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

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

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

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

1363
        /* First, try to link this directly */
1364
        if (oldpath)
1,804✔
1365
                r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0));
6✔
1366
        else
1367
                r = link_fd(olddirfd, newdirfd, newpath);
1,798✔
1368
        if (r >= 0)
1,803✔
1369
                return 0;
900✔
1370
        if (r != -EEXIST)
904✔
1371
                return r;
1372

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

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

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

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

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

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

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

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

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

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

1416
        return 0;
1417
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc