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

systemd / systemd / 25770446501

12 May 2026 11:15PM UTC coverage: 72.513% (-0.1%) from 72.65%
25770446501

push

github

web-flow
dhcp: random trivial cleanups (#42061)

8 of 25 new or added lines in 4 files covered. (32.0%)

4141 existing lines in 87 files now uncovered.

327776 of 452023 relevant lines covered (72.51%)

1437215.93 hits per line

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

92.41
/src/basic/chase.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <linux/magic.h>
4
#include <unistd.h>
5

6
#include "alloc-util.h"
7
#include "chase.h"
8
#include "errno-util.h"
9
#include "fd-util.h"
10
#include "fileio.h"
11
#include "fs-util.h"
12
#include "glyph-util.h"
13
#include "log.h"
14
#include "path-util.h"
15
#include "stat-util.h"
16
#include "string-util.h"
17
#include "strv.h"
18
#include "user-util.h"
19

20
/* Flags that prevent us from taking any of the early shortcuts: either they change the path resolution
21
 * semantics (e.g. CHASE_NONEXISTENT, CHASE_PARENT, CHASE_STEP) or ask for per-component validation that a
22
 * single open() cannot provide (e.g. CHASE_SAFE, CHASE_NO_AUTOFS, CHASE_PROHIBIT_SYMLINKS).
23
 *
24
 * Notably, the following are *not* listed here:
25
 *   - CHASE_TRIGGER_AUTOFS: plain open() already triggers automounts, and O_PATH shortcuts can use
26
 *     XO_TRIGGER_AUTOMOUNT to tell xopenat_full() to use open_tree() instead.
27
 *   - CHASE_MUST_BE_{DIRECTORY,REGULAR,SOCKET}: xopenat_full() can enforce these via O_DIRECTORY,
28
 *     XO_REGULAR and XO_SOCKET. Shortcut callers that don't go through xopenat_full() (stat/access
29
 *     paths) must include CHASE_MUST_BE_ANY in their local mask to still bail on these. */
30
#define CHASE_NO_SHORTCUT_MASK                          \
31
        (CHASE_NONEXISTENT |                            \
32
         CHASE_NO_AUTOFS |                              \
33
         CHASE_SAFE |                                   \
34
         CHASE_STEP |                                   \
35
         CHASE_PROHIBIT_SYMLINKS |                      \
36
         CHASE_PARENT |                                 \
37
         CHASE_MKDIR_0755)
38

39
#define CHASE_MUST_BE_ANY \
40
        (CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_SOCKET)
41

42
static int chase_statx(int fd, struct statx *ret) {
32,542,999✔
43
        return xstatx_full(fd,
32,542,999✔
44
                        /* path= */ NULL,
45
                        /* statx_flags= */ 0,
46
                        XSTATX_MNT_ID_BEST,
47
                        STATX_TYPE|STATX_UID|STATX_INO,
48
                        /* optional_mask= */ 0,
49
                        /* mandatory_attributes= */ 0,
50
                        ret);
51
}
52

53
static int chase_openat2(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags) {
89,594✔
54
        /* Open the target of a chase operation via openat2(), translating the relevant ChaseFlags into
55
         * RESOLVE_* and O_* flags and verifying MUST_BE_REGULAR/SOCKET via fstat after the open. Returns
56
         * -EOPNOTSUPP when openat2() is unavailable (older kernels) or blocked by a seccomp filter
57
         * (notably systemd's own filter, which returns ENOSYS to force programs onto the openat()
58
         * fallback path) — the verdict is cached so subsequent calls in the same process skip the syscall
59
         * entirely. */
60

61
        static bool can_openat2 = true;
89,594✔
62
        int r;
89,594✔
63

64
        assert(path);
89,594✔
65
        assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
89,594✔
66

67
        if (!can_openat2)
89,594✔
68
                return -EOPNOTSUPP;
89,594✔
69

70
        /* openat2() can handle everything the regular shortcut handles, plus a real root boundary (via
71
         * RESOLVE_IN_ROOT) and CHASE_PROHIBIT_SYMLINKS (via RESOLVE_NO_SYMLINKS). It cannot model the other
72
         * CHASE_NO_SHORTCUT flags, cannot trigger automounts on O_PATH fds, and RESOLVE_IN_ROOT requires
73
         * the dirfd to be the root. Bail out so the caller falls back to the regular chase loop. */
74
        if ((chase_flags & (CHASE_NO_SHORTCUT_MASK & ~CHASE_PROHIBIT_SYMLINKS)) != 0)
85,788✔
75
                return -EOPNOTSUPP;
76
        if (FLAGS_SET(chase_flags, CHASE_TRIGGER_AUTOFS))
18,702✔
77
                return -EOPNOTSUPP;
78
        if (root_fd != XAT_FDROOT && root_fd != dir_fd)
18,684✔
79
                return -EOPNOTSUPP;
80

81
        _cleanup_close_ int dir_fd_local = -EBADF;
89,594✔
82
        if (dir_fd == XAT_FDROOT) {
18,683✔
83
                if (path_is_absolute(path))
10,311✔
84
                        dir_fd = AT_FDCWD;
85
                else {
86
                        dir_fd_local = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
118✔
87
                        if (dir_fd_local < 0)
118✔
UNCOV
88
                                return -errno;
×
89
                        dir_fd = dir_fd_local;
90
                }
91
        }
92

93
        struct open_how how = {
18,683✔
94
                .flags = O_PATH|O_CLOEXEC,
95
        };
96
        if (FLAGS_SET(chase_flags, CHASE_NOFOLLOW))
18,683✔
97
                how.flags |= O_NOFOLLOW;
5,859✔
98
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY))
18,683✔
99
                how.flags |= O_DIRECTORY;
1,017✔
100
        if (root_fd != XAT_FDROOT)
18,683✔
101
                how.resolve |= RESOLVE_IN_ROOT;
8,372✔
102
        if (FLAGS_SET(chase_flags, CHASE_PROHIBIT_SYMLINKS))
18,683✔
103
                how.resolve |= RESOLVE_NO_SYMLINKS;
227✔
104

105
        _cleanup_close_ int fd = openat2(dir_fd, path, &how, sizeof(how));
37,366✔
106
        if (fd < 0) {
18,683✔
107
                /* ENOSYS: kernel too old or seccomp filter (systemd's filter returns ENOSYS).
108
                 * EPERM: Some seccomp profiles of container runtimes use EPERM rather than ENOSYS.
109
                 * But EPERM might also be returned because we can't access some component of the path. So
110
                 * we can't cache the result and skip using openat2() if it is blocked with EPERM. Instead
111
                 * we fall back to userspace chase() if we get EPERM.
112
                 * EAGAIN: with RESOLVE_IN_ROOT the kernel returns this when a ".." component
113
                 * (typically from following a symlink like /etc/os-release → ../usr/lib/os-release)
114
                 * is processed and the global mount_lock or rename_lock seqcount changed during
115
                 * the walk. Any mount activity anywhere in the system bumps mount_lock, so this
116
                 * fires reliably while we're still setting up a mount tree. Fall back to the
117
                 * regular chase loop, which handles root boundaries without openat2(). Don't
118
                 * cache this — the condition is per-call, not a kernel/sandbox capability. */
119
                if (errno == ENOSYS)
7,922✔
120
                        can_openat2 = false;
5,029✔
121
                if (IN_SET(errno, ENOSYS, EPERM, EAGAIN))
7,922✔
122
                        return -EOPNOTSUPP;
123
                return -errno;
2,877✔
124
        }
125

126
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) {
10,761✔
127
                r = fd_verify_regular(fd);
14✔
128
                if (r < 0)
14✔
129
                        return r;
130
        }
131
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_SOCKET)) {
10,761✔
UNCOV
132
                r = fd_verify_socket(fd);
×
UNCOV
133
                if (r < 0)
×
UNCOV
134
                        return r;
×
135
        }
136

137
        return TAKE_FD(fd);
138
}
139

140
static int chase_xopenat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, XOpenFlags xopen_flags) {
52,982✔
141
        /* Wrapper around xopenat_full() that translates CHASE_NOFOLLOW, CHASE_MUST_BE_* and
142
         * CHASE_TRIGGER_AUTOFS into their xopenat_full() counterparts. Used by shortcuts that want to open
143
         * the final target of a chase operation: they all want O_NOFOLLOW honoured, MUST_BE_* verified on
144
         * the opened inode, and automounts triggered if requested. */
145

146
        if (FLAGS_SET(chase_flags, CHASE_NOFOLLOW))
52,982✔
147
                open_flags |= O_NOFOLLOW;
283✔
148
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY))
52,982✔
149
                open_flags |= O_DIRECTORY;
248✔
150
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR))
52,982✔
151
                xopen_flags |= XO_REGULAR;
41,399✔
152
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_SOCKET))
52,982✔
UNCOV
153
                xopen_flags |= XO_SOCKET;
×
154
        /* Only needed for O_PATH since plain open() already triggers automounts */
155
        if (FLAGS_SET(chase_flags, CHASE_TRIGGER_AUTOFS) && FLAGS_SET(open_flags, O_PATH))
52,982✔
156
                xopen_flags |= XO_TRIGGER_AUTOMOUNT;
41✔
157

158
        return xopenat_full(dir_fd, path, open_flags, xopen_flags, MODE_INVALID);
52,982✔
159
}
160

161
static bool uid_unsafe_transition(uid_t a, uid_t b) {
37,955✔
162
        /* Returns true if the transition from a to b is safe, i.e. that we never transition from
163
         * unprivileged to privileged files or directories. Why bother? So that unprivileged code can't
164
         * symlink to privileged files making us believe we read something safe even though it isn't safe in
165
         * the specific context we open it in. */
166

167
        if (a == 0) /* Transitioning from privileged to unprivileged is always fine */
37,955✔
168
                return false;
169

170
        return a != b; /* Otherwise we need to stay within the same UID */
645✔
171
}
172

173
int statx_unsafe_transition(const struct statx *a, const struct statx *b) {
37,955✔
174
        assert(a);
37,955✔
175
        assert(b);
37,955✔
176

177
        if (!FLAGS_SET(a->stx_mask, STATX_UID) || !FLAGS_SET(b->stx_mask, STATX_UID))
37,955✔
178
                return -ENODATA;
179

180
        return uid_unsafe_transition(a->stx_uid, b->stx_uid);
37,955✔
181
}
182

UNCOV
183
bool stat_unsafe_transition(const struct stat *a, const struct stat *b) {
×
UNCOV
184
        assert(a);
×
UNCOV
185
        assert(b);
×
186

UNCOV
187
        return uid_unsafe_transition(a->st_uid, b->st_uid);
×
188
}
189

190
static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) {
10✔
191
        _cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL;
10✔
192
        struct stat st;
10✔
193

194
        if (!FLAGS_SET(flags, CHASE_WARN))
10✔
195
                return -ENOLINK;
196

197
        (void) fd_get_path(a, &n1);
5✔
198
        (void) fd_get_path(b, &n2);
5✔
199

200
        if (fstat(a, &st) == 0)
5✔
201
                user_a = uid_to_name(st.st_uid);
5✔
202
        if (fstat(b, &st) == 0)
5✔
203
                user_b = uid_to_name(st.st_uid);
5✔
204

205
        return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK),
5✔
206
                                 "Detected unsafe path transition %s (owned by %s) %s %s (owned by %s) during canonicalization of %s.",
207
                                 strna(n1), strna(user_a), glyph(GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path);
208
}
209

UNCOV
210
static int log_autofs_mount_point(int fd, const char *path, ChaseFlags flags) {
×
UNCOV
211
        _cleanup_free_ char *n1 = NULL;
×
212

UNCOV
213
        if (!FLAGS_SET(flags, CHASE_WARN))
×
214
                return -EREMOTE;
215

UNCOV
216
        (void) fd_get_path(fd, &n1);
×
217

UNCOV
218
        return log_warning_errno(SYNTHETIC_ERRNO(EREMOTE),
×
219
                                 "Detected autofs mount point '%s' during canonicalization of '%s'.",
220
                                 strna(n1), path);
221
}
222

223
static int log_prohibited_symlink(int fd, ChaseFlags flags) {
7✔
224
        _cleanup_free_ char *n1 = NULL;
7✔
225

226
        assert(fd >= 0);
7✔
227

228
        if (!FLAGS_SET(flags, CHASE_WARN))
7✔
229
                return -ELOOP;
230

231
        (void) fd_get_path(fd, &n1);
3✔
232

233
        return log_warning_errno(SYNTHETIC_ERRNO(ELOOP),
3✔
234
                                 "Detected symlink where no symlink is allowed at '%s', refusing.",
235
                                 strna(n1));
236
}
237

238
int chaseat(int root_fd, int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
5,807,832✔
239
        int r;
5,807,832✔
240

241
        assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
5,807,832✔
242
        assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
5,807,832✔
243
        assert(!FLAGS_SET(flags, CHASE_NO_AUTOFS|CHASE_TRIGGER_AUTOFS));
5,807,832✔
244
        assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
5,807,832✔
245
        assert(root_fd >= 0 || IN_SET(root_fd, AT_FDCWD, XAT_FDROOT));
5,807,832✔
246
        /* AT_FDCWD for dir_fd is only allowed when there is no chroot boundary: otherwise the current
247
         * working directory might live outside root_fd's subtree. */
248
        assert(dir_fd != AT_FDCWD || IN_SET(root_fd, AT_FDCWD, XAT_FDROOT));
5,807,832✔
249

250
        if (FLAGS_SET(flags, CHASE_STEP))
5,807,832✔
251
                assert(!ret_fd);
23,624✔
252

253
        /* This function resolves symlinks of the path relative to the given directory file descriptor.
254
         * The root directory file descriptor sets the chroot boundary: symlinks may not escape it, and
255
         * absolute symlinks encountered during resolution are resolved relative to it. When the root fd is
256
         * XAT_FDROOT, symlinks are resolved relative to the host's root directory with no containment.
257
         *
258
         * The given path is always resolved starting at dir_fd, regardless of whether it is absolute or
259
         * relative. The leading slashes of an absolute path are ignored. The only exceptions are
260
         * dir_fd == XAT_FDROOT (which starts resolution at root_fd) and dir_fd == AT_FDCWD with an absolute
261
         * path (which starts resolution at "/" rather than the current working directory).
262
         *
263
         * Note that we do not verify that dir_fd actually points to a descendant of root_fd. If dir_fd
264
         * lies outside the root_fd subtree, ".." traversal and absolute symlinks may still be clamped to
265
         * root_fd, leading to surprising results. Callers must ensure the relationship themselves.
266
         *
267
         * Absolute paths returned by this function are relative to the given root file descriptor. Relative
268
         * paths returned by this function are relative to the given directory file descriptor. The result is
269
         * absolute when root_fd is XAT_FDROOT (i.e. there is no chroot boundary, so openat()-like callers
270
         * need an absolute path to reach the host inode), or when an absolute symlink made us jump to a
271
         * different subtree than the one dir_fd points into. Otherwise the result is relative.
272
         *
273
         * Algorithmically this operates on two path buffers: "done" are the components of the path we
274
         * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we
275
         * still need to process. On each iteration, we move one component from "todo" to "done", processing
276
         * its special meaning each time. We always keep an O_PATH fd to the component we are currently
277
         * processing, thus keeping lookup races to a minimum.
278
         *
279
         * There are five ways to invoke this function:
280
         *
281
         * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is
282
         *    returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0
283
         *    is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is
284
         *    returned if the destination was found, -ENOENT if it wasn't.
285
         *
286
         * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file
287
         *    descriptor is returned as return value. This is useful to open files relative to some root
288
         *    directory. Note that the returned O_PATH file descriptors must be converted into a regular one
289
         *    (using fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be
290
         *    combined with CHASE_NONEXISTENT.
291
         *
292
         * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only
293
         *    the first symlink or ".." component of the path is resolved, and the resulting path is
294
         *    returned. This is useful if a caller wants to trace the path through the file system verbosely.
295
         *    Returns < 0 on error, > 0 if the path is fully normalized, and == 0 for each normalization
296
         *    step. This may be combined with CHASE_NONEXISTENT, in which case 1 is returned when a component
297
         *    is not found.
298
         *
299
         * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions
300
         *    from unprivileged to privileged files or directories. In such cases the return value is
301
         *    -ENOLINK. If CHASE_WARN is also set, a warning describing the unsafe transition is emitted.
302
         *    CHASE_WARN cannot be used in PID 1.
303
         *
304
         * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization
305
         *    is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of
306
         *    the mount point is emitted. CHASE_WARN cannot be used in PID 1.
307
         */
308

309
        /* We treat AT_FDCWD as XAT_FDROOT for a more seamless migration for all callers of chaseat() before
310
         * it was reworked to support separate root_fd and dir_fd arguments. */
311
        if (root_fd == AT_FDCWD)
5,807,832✔
312
                root_fd = XAT_FDROOT;
313
        else {
314
                r = dir_fd_is_root(root_fd);
5,781,425✔
315
                if (r < 0)
107,036✔
316
                        return r;
5,807,832✔
317
                if (r > 0)
5,781,425✔
318
                        root_fd = XAT_FDROOT;
5,683,985✔
319
        }
320

321
        /* If dir_fd points to the host's root directory and there is no chroot boundary, normalize it
322
         * to XAT_FDROOT so the shortcut path can kick in. */
323
        r = dir_fd_is_root(dir_fd);
5,807,832✔
324
        if (r < 0)
134,297✔
325
                return r;
326
        if (r > 0 && root_fd == XAT_FDROOT)
5,807,832✔
327
                dir_fd = XAT_FDROOT;
328

329
        /* dir_fd == XAT_FDROOT means "start at root_fd". An absolute path is always resolved relative to
330
         * root_fd, regardless of what dir_fd points to. */
331
        if (dir_fd == XAT_FDROOT || path_is_absolute(path))
196,166✔
332
                dir_fd = root_fd;
333

334
        if (isempty(path))
5,809,367✔
335
                path = ".";
336

337
        bool append_trail_slash = false;
5,807,832✔
338
        if (ENDSWITH_SET(path, "/", "/.")) {
5,807,832✔
339
                flags |= CHASE_MUST_BE_DIRECTORY;
6,976✔
340
                if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
6,976✔
341
                        append_trail_slash = true;
8✔
342
        } else if (dot_or_dot_dot(path) || endswith(path, "/.."))
5,800,856✔
343
                flags |= CHASE_MUST_BE_DIRECTORY;
1,545✔
344

345
        if (FLAGS_SET(flags, CHASE_PARENT))
5,807,832✔
346
                flags |= CHASE_MUST_BE_DIRECTORY;
53,755✔
347

348
        /* If multiple flags are set now, fail immediately */
349
        if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY) + FLAGS_SET(flags, CHASE_MUST_BE_REGULAR) + FLAGS_SET(flags, CHASE_MUST_BE_SOCKET) > 1)
5,807,832✔
350
                return -EBADSLT;
351

352
        if (!ret_path) {
5,807,832✔
353
                r = chase_openat2(root_fd, dir_fd, path, flags);
89,594✔
354
                if (r >= 0) {
89,594✔
355
                        if (ret_fd)
10,761✔
356
                                *ret_fd = r;
6,262✔
357
                        else
358
                                safe_close(r);
4,499✔
359

360
                        return 1;
361
                }
362
                if (r != -EOPNOTSUPP)
78,833✔
363
                        return r;
364
        }
365

366
        if (root_fd == XAT_FDROOT && !ret_path && (flags & CHASE_NO_SHORTCUT_MASK) == 0) {
5,794,194✔
367
                /* Shortcut the common case where we don't have a real root boundary and no fancy features
368
                 * are requested: open the target directly via xopenat_full() which applies any MUST_BE_*
369
                 * verification and automount triggering for us. */
370

371
                r = chase_xopenat(dir_fd, path, flags, O_PATH|O_CLOEXEC, /* xopen_flags= */ 0);
4,927✔
372
                if (r < 0)
4,927✔
373
                        return r;
374

375
                if (ret_fd)
4,921✔
376
                        *ret_fd = r;
249✔
377
                else
378
                        safe_close(r);
4,672✔
379

380
                return 1;
381
        }
382

383
        /* Decide whether to return an absolute or relative path.
384
         *
385
         * We return an absolute path only when there is no chroot boundary (root_fd == XAT_FDROOT)
386
         * and resolution starts from root — i.e. either dir_fd was XAT_FDROOT or path is absolute,
387
         * both of which caused dir_fd = root_fd above. In every other case we return a relative
388
         * path so the result keeps working when fed to an openat()-style call against dir_fd,
389
         * which would ignore dir_fd if handed an absolute path.
390
         *
391
         * When root_fd != XAT_FDROOT and an absolute symlink later causes resolution to escape
392
         * dir_fd, the loop below rebases onto root_fd and switches to an absolute result at that
393
         * point — it is not handled here.
394
         */
395
        bool need_absolute = (root_fd == XAT_FDROOT || dir_fd != root_fd) && (dir_fd == XAT_FDROOT || path_is_absolute(path));
5,789,347✔
396

397
        _cleanup_free_ char *done = NULL;
5,789,267✔
398
        if (need_absolute) {
5,789,267✔
399
                done = strdup("/");
5,699,830✔
400
                if (!done)
5,699,830✔
401
                        return -ENOMEM;
402
        }
403

404
        _cleanup_close_ int fd = xopenat(dir_fd, NULL, O_CLOEXEC|O_DIRECTORY|O_PATH);
11,578,534✔
405
        if (fd < 0)
5,789,267✔
406
                return fd;
407

408
        struct statx stx;
5,789,267✔
409
        r = chase_statx(fd, &stx);
5,789,267✔
410
        if (r < 0)
5,789,267✔
411
                return r;
412

413
        /* Remember stat data of the root, so that we can recognize it later during .. handling. Only
414
         * needed when there is an actual chroot boundary — with root_fd == XAT_FDROOT the boundary
415
         * check in the .. loop below is skipped and root_stx is never consulted. */
416
        struct statx root_stx;
5,789,267✔
417
        if (root_fd != XAT_FDROOT) {
5,789,267✔
418
                if (root_fd == dir_fd)
89,363✔
419
                        root_stx = stx;
89,357✔
420
                else {
421
                        r = chase_statx(root_fd, &root_stx);
6✔
422
                        if (r < 0)
6✔
423
                                return r;
424
                }
425
        }
426

427
        _cleanup_free_ char *buffer = strdup(path);
5,789,267✔
428
        if (!buffer)
5,789,267✔
429
                return -ENOMEM;
430

431
        const char *todo = buffer;
5,789,267✔
432
        bool exists = true;
5,789,267✔
433
        for (unsigned n_steps = 0;; n_steps++) {
32,482,350✔
434
                _cleanup_free_ char *first = NULL;
30,183,004✔
435
                _cleanup_close_ int child = -EBADF;
32,482,350✔
436
                struct statx stx_child;
32,482,350✔
437
                const char *e;
32,482,350✔
438

439
                /* If people change our tree behind our back, they might send us in circles. Put a limit on
440
                 * things */
441
                if (n_steps > CHASE_MAX)
32,482,350✔
442
                        return -ELOOP;
443

444
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
32,482,349✔
445
                if (r < 0)
32,482,349✔
446
                        return r;
447
                if (r == 0) /* We reached the end. */
32,482,349✔
448
                        break;
449

450
                first = strndup(e, r);
30,250,465✔
451
                if (!first)
30,250,465✔
452
                        return -ENOMEM;
453

454
                /* Two dots? Then chop off the last bit of what we already found out. */
455
                if (streq(first, "..")) {
30,250,465✔
456
                        _cleanup_free_ char *parent = NULL;
930,535✔
457
                        _cleanup_close_ int fd_parent = -EBADF;
930,535✔
458
                        struct statx stx_parent;
930,535✔
459

460
                        /* If we already are at the top, then going up will not change anything. This is
461
                         * in-line with how the kernel handles this. We check this both by path and by
462
                         * inode/mount identity check. The latter is load-bearing if concurrent access of the
463
                         * root tree we operate in is allowed, where an inode is moved up the tree while we
464
                         * look at it, and thus get the current path wrong and think we are deeper down than
465
                         * we actually are.
466
                         *
467
                         * The path-based fast path is only valid when the caller started at the root fd:
468
                         * otherwise 'done' being empty just means we haven't descended past the starting
469
                         * dir_fd, not that we're at the chroot boundary. */
470
                        if (root_fd != XAT_FDROOT) {
930,535✔
471
                                bool is_root = root_fd == dir_fd && empty_or_root(done);
1,605✔
472
                                if (!is_root && statx_inode_same(&stx, &root_stx)) {
1,480✔
473
                                        r = statx_mount_same(&stx, &root_stx);
6✔
474
                                        if (r < 0)
6✔
475
                                                return r;
476

477
                                        is_root = r > 0;
6✔
478
                                }
479
                                if (is_root) {
1,480✔
480
                                        if (FLAGS_SET(flags, CHASE_STEP))
131✔
UNCOV
481
                                                goto chased_one;
×
482
                                        continue;
131✔
483
                                }
484
                        }
485

486
                        fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
930,404✔
487
                        if (fd_parent < 0)
930,404✔
UNCOV
488
                                return -errno;
×
489

490
                        r = chase_statx(fd_parent, &stx_parent);
930,404✔
491
                        if (r < 0)
930,404✔
492
                                return r;
493

494
                        /* If we opened the same directory, that _may_ indicate that we're at the host root
495
                         * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
496
                         * going up won't change anything. */
497
                        if (statx_inode_same(&stx_parent, &stx)) {
930,404✔
498
                                r = dir_fd_is_root(fd);
42✔
499
                                if (r < 0)
42✔
500
                                        return r;
501
                                if (r > 0) {
42✔
502
                                        if (FLAGS_SET(flags, CHASE_STEP))
42✔
UNCOV
503
                                                goto chased_one;
×
504
                                        continue;
42✔
505
                                }
506
                        }
507

508
                        r = path_extract_directory(done, &parent);
930,362✔
509
                        if (r >= 0) {
930,362✔
510
                                assert(!need_absolute || path_is_absolute(parent));
928,954✔
511
                                free_and_replace(done, parent);
928,954✔
512
                        } else if (r == -EDESTADDRREQ) {
1,408✔
513
                                /* 'done' contains filename only (i.e. no slash). */
514
                                assert(!need_absolute);
1,405✔
515
                                done = mfree(done);
1,405✔
516
                        } else if (r == -EADDRNOTAVAIL) {
3✔
517
                                /* 'done' is "/". This branch should already be handled above via the
518
                                 * is_root check. */
UNCOV
519
                                assert_not_reached();
×
520
                        } else if (r == -EINVAL) {
3✔
521
                                /* 'done' is empty (we haven't descended past the starting dir_fd yet), or
522
                                 * ends with '..'. In both cases we're traversing above the starting point
523
                                 * (valid when root_fd is XAT_FDROOT, or when dir_fd was below root_fd to
524
                                 * start with), so record another '..' in 'done'. */
525
                                assert(!need_absolute);
3✔
526

527
                                if (!isempty(done) && !path_is_valid(done))
3✔
528
                                        return -EINVAL;
529

530
                                if (!path_extend(&done, ".."))
3✔
531
                                        return -ENOMEM;
532
                        } else
533
                                return r;
534

535
                        if (FLAGS_SET(flags, CHASE_STEP))
930,362✔
536
                                goto chased_one;
6✔
537

538
                        if (FLAGS_SET(flags, CHASE_SAFE)) {
930,356✔
539
                                r = statx_unsafe_transition(&stx, &stx_parent);
2✔
540
                                if (r < 0)
2✔
541
                                        return r;
542
                                if (r > 0)
2✔
543
                                        return log_unsafe_transition(fd, fd_parent, path, flags);
2✔
544
                        }
545

546
                        /* If the path ends on a "..", and CHASE_PARENT is specified then our current 'fd' is
547
                         * the child of the returned normalized path, not the parent as requested. To correct
548
                         * this we have to go *two* levels up. */
549
                        if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
930,354✔
550
                                _cleanup_close_ int fd_grandparent = -EBADF;
2✔
551
                                struct statx stx_grandparent;
2✔
552

553
                                fd_grandparent = openat(fd_parent, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
2✔
554
                                if (fd_grandparent < 0)
2✔
UNCOV
555
                                        return -errno;
×
556

557
                                r = chase_statx(fd_grandparent, &stx_grandparent);
2✔
558
                                if (r < 0)
2✔
559
                                        return r;
560

561
                                if (FLAGS_SET(flags, CHASE_SAFE)) {
2✔
UNCOV
562
                                        r = statx_unsafe_transition(&stx_parent, &stx_grandparent);
×
UNCOV
563
                                        if (r < 0)
×
564
                                                return r;
UNCOV
565
                                        if (r > 0)
×
UNCOV
566
                                                return log_unsafe_transition(fd_parent, fd_grandparent, path, flags);
×
567
                                }
568

569
                                stx = stx_grandparent;
2✔
570
                                close_and_replace(fd, fd_grandparent);
2✔
571
                                break;
2✔
572
                        }
573

574
                        /* update fd and stat */
575
                        stx = stx_parent;
930,352✔
576
                        close_and_replace(fd, fd_parent);
930,352✔
577
                        continue;
930,352✔
578
                }
579

580
                /* Otherwise let's pin it by file descriptor, via O_PATH. */
581
                child = r = xopenat_full(fd, first,
58,639,860✔
582
                                         O_PATH|O_NOFOLLOW|O_CLOEXEC,
583
                                         FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS) ? XO_TRIGGER_AUTOMOUNT : 0,
29,319,930✔
584
                                         MODE_INVALID);
585
                if (r < 0) {
29,319,930✔
586
                        if (r != -ENOENT)
3,509,904✔
587
                                return r;
588

589
                        if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */
4,900,469✔
590
                                return r;
591

592
                        if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) {
3,510,760✔
593
                                child = xopenat(fd,
427✔
594
                                                first,
595
                                                O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC);
596
                                if (child < 0)
427✔
597
                                        return child;
598
                        } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
3,509,473✔
599
                                if (!path_extend(&done, first))
3,318✔
600
                                        return -ENOMEM;
601

602
                                break;
603
                        } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
3,506,155✔
604
                                if (!path_extend(&done, first, todo))
16,256✔
605
                                        return -ENOMEM;
606

607
                                exists = false;
608
                                break;
609
                        } else
610
                                return r;
611
                }
612

613
                r = chase_statx(child, &stx_child);
25,810,453✔
614
                if (r < 0)
25,810,453✔
615
                        return r;
616

617
                if (FLAGS_SET(flags, CHASE_SAFE)) {
25,810,453✔
618
                        r = statx_unsafe_transition(&stx, &stx_child);
37,948✔
619
                        if (r < 0)
37,948✔
620
                                return r;
621
                        if (r > 0)
37,948✔
622
                                return log_unsafe_transition(fd, child, path, flags);
5✔
623
                }
624

625
                if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
25,810,448✔
626
                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
7,552,245✔
UNCOV
627
                        return log_autofs_mount_point(child, path, flags);
×
628

629
                if (S_ISLNK(stx_child.stx_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
25,810,448✔
630
                        _cleanup_free_ char *destination = NULL;
10✔
631

632
                        if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
438,579✔
633
                                return log_prohibited_symlink(child, flags);
7✔
634

635
                        r = readlinkat_malloc(fd, first, &destination);
438,572✔
636
                        if (r < 0)
438,572✔
637
                                return r;
638
                        if (isempty(destination))
438,582✔
639
                                return -EINVAL;
640

641
                        if (path_is_absolute(destination)) {
438,572✔
642

643
                                /* An absolute destination. Start the loop from the beginning, but use the
644
                                 * root file descriptor as base. */
645

646
                                safe_close(fd);
12,867✔
647
                                fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
12,867✔
648
                                if (fd < 0)
12,867✔
649
                                        return fd;
650

651
                                r = chase_statx(fd, &stx);
12,867✔
652
                                if (r < 0)
12,867✔
653
                                        return r;
654

655
                                if (FLAGS_SET(flags, CHASE_SAFE)) {
12,867✔
656
                                        r = statx_unsafe_transition(&stx_child, &stx);
5✔
657
                                        if (r < 0)
5✔
658
                                                return r;
659
                                        if (r > 0)
5✔
660
                                                return log_unsafe_transition(child, fd, path, flags);
3✔
661
                                }
662

663
                                if (dir_fd != root_fd)
12,864✔
664
                                        need_absolute = true;
665

666
                                r = free_and_strdup(&done, need_absolute ? "/" : NULL);
13,183✔
667
                                if (r < 0)
12,864✔
668
                                        return r;
669
                        }
670

671
                        /* Prefix what's left to do with what we just read, and start the loop again, but
672
                         * remain in the current directory. */
673
                        if (!path_extend(&destination, todo))
438,569✔
674
                                return -ENOMEM;
675

676
                        free_and_replace(buffer, destination);
438,569✔
677
                        todo = buffer;
438,569✔
678

679
                        if (FLAGS_SET(flags, CHASE_STEP))
438,569✔
680
                                goto chased_one;
8✔
681

682
                        continue;
438,561✔
683
                }
684

685
                /* If this is not a symlink, then let's just add the name we read to what we already verified. */
686
                if (!path_extend(&done, first))
25,371,869✔
687
                        return -ENOMEM;
688

689
                if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
27,671,201✔
690
                        break;
691

692
                /* And iterate again, but go one directory further down. */
693
                stx = stx_child;
25,323,997✔
694
                close_and_replace(fd, child);
25,323,997✔
695
        }
696

697
        if (exists) {
2,299,332✔
698
                if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) {
2,283,076✔
699
                        r = statx_verify_directory(&stx);
60,124✔
700
                        if (r < 0)
60,124✔
701
                                return r;
702
                }
703

704
                if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) {
2,283,076✔
705
                        r = statx_verify_regular(&stx);
43✔
706
                        if (r < 0)
43✔
707
                                return r;
708
                }
709

710
                if (FLAGS_SET(flags, CHASE_MUST_BE_SOCKET)) {
2,283,076✔
711
                        r = statx_verify_socket(&stx);
78✔
712
                        if (r < 0)
78✔
713
                                return r;
714
                }
715
        }
716

717
        if (ret_path) {
2,299,332✔
718
                if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
2,229,134✔
UNCOV
719
                        _cleanup_free_ char *f = NULL;
×
720

721
                        r = path_extract_filename(done, &f);
7,324✔
722
                        if (r < 0 && r != -EADDRNOTAVAIL)
7,324✔
UNCOV
723
                                return r;
×
724

725
                        /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
726
                        free_and_replace(done, f);
7,324✔
727
                }
728

729
                if (!done) {
2,229,134✔
730
                        assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME));
250✔
731
                        done = strdup(".");
250✔
732
                        if (!done)
250✔
733
                                return -ENOMEM;
734
                }
735

736
                if (append_trail_slash)
2,229,134✔
737
                        if (!strextend(&done, "/"))
8✔
738
                                return -ENOMEM;
739

740
                *ret_path = TAKE_PTR(done);
2,229,134✔
741
        }
742

743
        if (ret_fd) {
2,299,332✔
744
                if (exists) {
2,100,826✔
745
                        /* Return the O_PATH fd we currently are looking to the caller. It can translate it
746
                         * to a proper fd by opening /proc/self/fd/xyz. */
747
                        assert(fd >= 0);
2,100,007✔
748
                        *ret_fd = TAKE_FD(fd);
2,100,007✔
749
                } else
750
                        *ret_fd = -EBADF;
819✔
751
        }
752

753
        if (FLAGS_SET(flags, CHASE_STEP))
2,299,332✔
754
                return 1;
755

756
        return exists;
2,275,722✔
757

758
chased_one:
14✔
759
        if (ret_path) {
14✔
760
                const char *e;
14✔
761

762
                if (!done) {
14✔
763
                        assert(!need_absolute);
1✔
764
                        done = strdup(append_trail_slash ? "./" : ".");
1✔
765
                        if (!done)
1✔
UNCOV
766
                                return -ENOMEM;
×
767
                }
768

769
                /* todo may contain slashes at the beginning. */
770
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
14✔
771
                if (r < 0)
14✔
772
                        return r;
773
                if (r == 0)
14✔
UNCOV
774
                        *ret_path = TAKE_PTR(done);
×
775
                else {
776
                        char *c;
14✔
777

778
                        c = path_join(done, e);
14✔
779
                        if (!c)
14✔
780
                                return -ENOMEM;
781

782
                        *ret_path = c;
14✔
783
                }
784
        }
785

786
        return 0;
787
}
788

789
int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
4,833,129✔
790
        _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
4,833,129✔
791
        _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
9,666,258✔
792
        int r;
4,833,129✔
793

794
        assert(path);
4,833,129✔
795

796
        if (isempty(path))
9,666,257✔
797
                return -EINVAL;
798

799
        r = empty_or_root_harder_to_null(&root);
4,833,128✔
800
        if (r < 0)
4,833,128✔
801
                return r;
802

803
        /* A root directory of "/" or "" is identical to "/". */
804
        if (empty_or_root(root))
4,833,125✔
805
                root = "/";
4,783,469✔
806
        else {
807
                r = path_make_absolute_cwd(root, &root_abs);
49,656✔
808
                if (r < 0)
49,656✔
809
                        return r;
810

811
                /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
812
                 * end. While we won't resolve the root path we still simplify it. */
813
                root = path_simplify(root_abs);
49,656✔
814

815
                assert(path_is_absolute(root));
49,656✔
816
                assert(!empty_or_root(root));
49,656✔
817

818
                if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
49,656✔
819
                        absolute = path_join(root, path);
16,305✔
820
                        if (!absolute)
16,305✔
821
                                return -ENOMEM;
822
                }
823
        }
824

825
        if (!absolute) {
4,833,125✔
826
                r = path_make_absolute_cwd(path, &absolute);
4,816,820✔
827
                if (r < 0)
4,816,820✔
828
                        return r;
829
        }
830

831
        path = path_startswith(absolute, root);
4,833,125✔
832
        if (!path)
4,833,125✔
UNCOV
833
                return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG,
×
834
                                      SYNTHETIC_ERRNO(ECHRNG),
835
                                      "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
836
                                      absolute, root);
837

838
        if (empty_or_root(root))
4,833,125✔
839
                fd = XAT_FDROOT;
840
        else {
841
                fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
49,656✔
842
                if (fd < 0)
49,656✔
843
                        return -errno;
1✔
844
        }
845

846
        r = chaseat(fd, fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
7,571,129✔
847
        if (r < 0)
4,833,124✔
848
                return r;
849

850
        if (ret_path) {
1,973,863✔
851
                if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
1,899,666✔
852

853
                        /* When "root" points to the root directory, the result of chaseat() is always
854
                         * absolute, hence it is not necessary to prefix with the root. When "root" points to
855
                         * a non-root directory, the result path is always normalized and relative, hence
856
                         * we can simply call path_join() and not necessary to call path_simplify().
857
                         * As a special case, chaseat() may return "." or "./", which are normalized too,
858
                         * but we need to drop "." before merging with root. */
859

860
                        if (empty_or_root(root))
1,893,996✔
861
                                assert(path_is_absolute(p));
1,861,877✔
862
                        else {
863
                                char *q;
32,119✔
864

865
                                assert(!path_is_absolute(p));
32,119✔
866

867
                                q = path_join(root, p + STR_IN_SET(p, ".", "./"));
32,119✔
868
                                if (!q)
32,119✔
UNCOV
869
                                        return -ENOMEM;
×
870

871
                                free_and_replace(p, q);
32,119✔
872
                        }
873
                }
874

875
                *ret_path = TAKE_PTR(p);
1,899,666✔
876
        }
877

878
        if (ret_fd)
1,973,863✔
879
                *ret_fd = TAKE_FD(pfd);
1,776,853✔
880

881
        return r;
882
}
883

884
int chaseat_prefix_root(const char *path, const char *root, char **ret) {
202,219✔
885
        char *q;
202,219✔
886
        int r;
202,219✔
887

888
        assert(path);
202,219✔
889
        assert(ret);
202,219✔
890

891
        /* This is mostly for prefixing the result of chaseat(). */
892

893
        if (!path_is_absolute(path)) {
202,219✔
894
                _cleanup_free_ char *root_abs = NULL;
6,663✔
895

896
                r = empty_or_root_harder_to_null(&root);
6,663✔
897
                if (r < 0 && r != -ENOENT)
6,663✔
898
                        return r;
899

900
                /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
901
                if (empty_or_root(root))
6,663✔
902
                        return -EINVAL;
903

904
                r = path_make_absolute_cwd(root, &root_abs);
6,663✔
905
                if (r < 0)
6,663✔
906
                        return r;
907

908
                root = path_simplify(root_abs);
6,663✔
909

910
                q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
6,663✔
911
        } else
912
                q = strdup(path);
195,556✔
913
        if (!q)
202,219✔
914
                return -ENOMEM;
915

916
        *ret = q;
202,219✔
917
        return 0;
202,219✔
918
}
919

920
int chase_extract_filename(const char *path, const char *root, char **ret) {
2,258✔
921
        int r;
2,258✔
922

923
        /* This is similar to path_extract_filename(), but takes root directory.
924
         * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */
925

926
        assert(path);
2,258✔
927
        assert(ret);
2,258✔
928

929
        if (isempty(path))
2,258✔
930
                return -EINVAL;
931

932
        if (!path_is_absolute(path))
4,516✔
933
                return -EINVAL;
934

935
        r = empty_or_root_harder_to_null(&root);
2,258✔
936
        if (r < 0 && r != -ENOENT)
2,258✔
937
                return r;
938

939
        if (!empty_or_root(root)) {
2,258✔
940
                _cleanup_free_ char *root_abs = NULL;
1,285✔
941

942
                r = path_make_absolute_cwd(root, &root_abs);
1,285✔
943
                if (r < 0)
1,285✔
944
                        return r;
945

946
                path = path_startswith(path, root_abs);
1,285✔
947
                if (!path)
1,285✔
948
                        return -EINVAL;
949
        }
950

951
        if (!isempty(path)) {
2,258✔
952
                r = path_extract_filename(path, ret);
2,075✔
953
                if (r != -EADDRNOTAVAIL)
2,075✔
954
                        return r;
955
        }
956

957
        return strdup_to(ret, ".");
190✔
958
}
959

960
int chase_and_open(
6,174✔
961
                const char *path,
962
                const char *root,
963
                ChaseFlags chase_flags,
964
                int open_flags,
965
                char **ret_path) {
966

967
        _cleanup_close_ int path_fd = -EBADF;
6,174✔
968
        _cleanup_free_ char *p = NULL, *fname = NULL;
6,174✔
969
        const char *open_name = NULL;
6,174✔
970
        int r;
6,174✔
971

972
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
6,174✔
973

974
        if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
6,174✔
975
                /* Shortcut this call if none of the special features of this call are requested */
976
                return chase_xopenat(AT_FDCWD, path, chase_flags, open_flags, /* xopen_flags= */ 0);
3,333✔
977

978
        r = chase(path, root, (CHASE_PARENT|chase_flags)&~CHASE_MUST_BE_REGULAR, &p, &path_fd);
2,841✔
979
        if (r < 0)
2,841✔
980
                return r;
981
        assert(path_fd >= 0);
2,260✔
982

983
        if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
2,260✔
984
                if (FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME))
2,254✔
985
                        /* chase() with CHASE_EXTRACT_FILENAME already returns just the filename in
986
                         * p — use it directly without redundant extraction. */
987
                        open_name = p;
2✔
988
                else {
989
                        r = chase_extract_filename(p, root, &fname);
2,252✔
990
                        if (r < 0)
2,252✔
991
                                return r;
992
                        open_name = fname;
2,252✔
993
                }
994
        }
995

996
        r = chase_xopenat(path_fd, strempty(open_name), chase_flags, open_flags|O_NOFOLLOW, /* xopen_flags= */ 0);
2,260✔
997
        if (r < 0)
2,260✔
998
                return r;
999

1000
        if (ret_path)
1,839✔
1001
                *ret_path = TAKE_PTR(p);
294✔
1002

1003
        return r;
1004
}
1005

1006
int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
105✔
1007
        _cleanup_close_ int path_fd = -EBADF;
105✔
1008
        _cleanup_free_ char *p = NULL;
105✔
1009
        DIR *d;
105✔
1010
        int r;
105✔
1011

1012
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_SOCKET)));
105✔
1013
        assert(ret_dir);
105✔
1014

1015
        if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) {
105✔
1016
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
1017
                d = opendir(path);
×
UNCOV
1018
                if (!d)
×
UNCOV
1019
                        return -errno;
×
1020

UNCOV
1021
                *ret_dir = d;
×
UNCOV
1022
                return 0;
×
1023
        }
1024

1025
        r = chase(path, root, chase_flags|CHASE_MUST_BE_DIRECTORY, ret_path ? &p : NULL, &path_fd);
105✔
1026
        if (r < 0)
105✔
1027
                return r;
1028
        assert(path_fd >= 0);
89✔
1029

1030
        d = xopendirat(path_fd, /* path= */ NULL, /* flags= */ 0);
89✔
1031
        if (!d)
89✔
UNCOV
1032
                return -errno;
×
1033

1034
        if (ret_path)
89✔
1035
                *ret_path = TAKE_PTR(p);
89✔
1036

1037
        *ret_dir = d;
89✔
1038
        return 0;
89✔
1039
}
1040

1041
int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
814,686✔
1042
        _cleanup_close_ int path_fd = -EBADF;
814,686✔
1043
        _cleanup_free_ char *p = NULL;
814,686✔
1044
        int r;
814,686✔
1045

1046
        assert(path);
814,686✔
1047
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
814,686✔
1048
        assert(ret_stat);
814,686✔
1049

1050
        if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0)
814,686✔
1051
                /* Shortcut this call if none of the special features of this call are requested. We can't
1052
                 * take the shortcut if CHASE_MUST_BE_* is set because fstatat() alone does not verify the
1053
                 * inode type. */
1054
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
814,755✔
1055
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
572,364✔
1056

1057
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
242,614✔
1058
        if (r < 0)
242,322✔
1059
                return r;
1060
        assert(path_fd >= 0);
155,503✔
1061

1062
        if (fstat(path_fd, ret_stat) < 0)
155,503✔
UNCOV
1063
                return -errno;
×
1064

1065
        if (ret_path)
155,503✔
1066
                *ret_path = TAKE_PTR(p);
155,434✔
1067

1068
        return 0;
1069
}
1070

1071
int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
9,349✔
1072
        _cleanup_close_ int path_fd = -EBADF;
9,349✔
1073
        _cleanup_free_ char *p = NULL;
9,349✔
1074
        int r;
9,349✔
1075

1076
        assert(path);
9,349✔
1077
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
9,349✔
1078

1079
        if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0)
9,349✔
1080
                /* Shortcut this call if none of the special features of this call are requested. */
1081
                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
18,460✔
1082
                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
9,114✔
1083

1084
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
460✔
1085
        if (r < 0)
235✔
1086
                return r;
1087
        assert(path_fd >= 0);
42✔
1088

1089
        r = access_fd(path_fd, access_mode);
42✔
1090
        if (r < 0)
42✔
1091
                return r;
1092

1093
        if (ret_path)
42✔
1094
                *ret_path = TAKE_PTR(p);
10✔
1095

1096
        return 0;
1097
}
1098

1099
int chase_and_fopen_unlocked(
414✔
1100
                const char *path,
1101
                const char *root,
1102
                ChaseFlags chase_flags,
1103
                const char *open_flags,
1104
                char **ret_path,
1105
                FILE **ret_file) {
1106

1107
        _cleanup_free_ char *final_path = NULL;
414✔
1108
        _cleanup_close_ int fd = -EBADF;
414✔
1109
        int mode_flags, r;
414✔
1110

1111
        assert(path);
414✔
1112
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT|CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_SOCKET)));
414✔
1113
        assert(open_flags);
414✔
1114
        assert(ret_file);
414✔
1115

1116
        mode_flags = fopen_mode_to_flags(open_flags);
414✔
1117
        if (mode_flags < 0)
414✔
1118
                return mode_flags;
1119

1120
        fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
555✔
1121
        if (fd < 0)
414✔
1122
                return fd;
1123

1124
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
254✔
1125
        if (r < 0)
254✔
1126
                return r;
1127

1128
        if (ret_path)
254✔
1129
                *ret_path = TAKE_PTR(final_path);
119✔
1130

1131
        return 0;
1132
}
1133

1134
int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1✔
1135
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
1136
        _cleanup_close_ int fd = -EBADF;
1✔
1137
        int r;
1✔
1138

1139
        assert(path);
1✔
1140
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT|CHASE_MUST_BE_SOCKET|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_DIRECTORY|CHASE_EXTRACT_FILENAME|CHASE_MKDIR_0755)));
1✔
1141

1142
        fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
1143
        if (fd < 0)
1✔
1144
                return fd;
1145

1146
        r = path_extract_filename(p, &fname);
1✔
1147
        if (r < 0)
1✔
1148
                return r;
1149

1150
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
UNCOV
1151
                return -errno;
×
1152

1153
        if (ret_path)
1✔
1154
                *ret_path = TAKE_PTR(p);
1✔
1155

1156
        return 0;
1157
}
1158

1159
int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
179✔
1160
        int pfd, r;
179✔
1161

1162
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
179✔
1163

1164
        r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
179✔
1165
        if (r < 0)
179✔
1166
                return r;
179✔
1167

1168
        return pfd;
178✔
1169
}
1170

1171
int chase_and_openat(
44,250✔
1172
                int root_fd,
1173
                int dir_fd,
1174
                const char *path,
1175
                ChaseFlags chase_flags,
1176
                int open_flags,
1177
                char **ret_path) {
1178

1179
        _cleanup_close_ int path_fd = -EBADF;
44,250✔
1180
        _cleanup_free_ char *p = NULL, *fname = NULL;
44,250✔
1181
        const char *open_name = NULL;
44,250✔
1182
        int r;
44,250✔
1183

1184
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_SOCKET)));
44,250✔
1185

1186
        if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
44,250✔
1187
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
1188
                return chase_xopenat(dir_fd, path, chase_flags, open_flags, /* xopen_flags= */ 0);
×
1189

1190
        r = chaseat(root_fd, dir_fd, path, (chase_flags|CHASE_PARENT)&~CHASE_MUST_BE_REGULAR, &p, &path_fd);
44,250✔
1191
        if (r < 0)
44,250✔
1192
                return r;
1193

1194
        if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
42,462✔
1195
                if (FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME))
41,405✔
1196
                        /* chaseat() with CHASE_EXTRACT_FILENAME already returns just the filename in
1197
                         * p — use it directly without redundant extraction. */
1198
                        open_name = p;
1✔
1199
                else {
1200
                        r = path_extract_filename(p, &fname);
41,404✔
1201
                        if (r < 0 && r != -EADDRNOTAVAIL)
41,404✔
1202
                                return r;
1203
                        open_name = fname;
41,404✔
1204
                }
1205
        }
1206

1207
        r = chase_xopenat(path_fd, strempty(open_name), chase_flags, open_flags|O_NOFOLLOW, /* xopen_flags= */ 0);
42,462✔
1208
        if (r < 0)
42,462✔
1209
                return r;
1210

1211
        if (ret_path)
41,228✔
1212
                *ret_path = TAKE_PTR(p);
1,188✔
1213

1214
        return r;
1215
}
1216

1217
int chase_and_opendirat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
705,861✔
1218
        _cleanup_close_ int path_fd = -EBADF;
705,861✔
1219
        _cleanup_free_ char *p = NULL;
705,861✔
1220
        DIR *d;
705,861✔
1221
        int r;
705,861✔
1222

1223
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_SOCKET)));
705,861✔
1224
        assert(ret_dir);
705,861✔
1225

1226
        if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) {
705,861✔
1227
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
1228
                d = opendir(path);
×
UNCOV
1229
                if (!d)
×
UNCOV
1230
                        return -errno;
×
1231

UNCOV
1232
                *ret_dir = d;
×
UNCOV
1233
                return 0;
×
1234
        }
1235

1236
        r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
706,244✔
1237
        if (r < 0)
705,861✔
1238
                return r;
1239
        assert(path_fd >= 0);
77,811✔
1240

1241
        d = xopendirat(path_fd, /* path= */ NULL, /* flags= */ 0);
77,811✔
1242
        if (!d)
77,811✔
UNCOV
1243
                return -errno;
×
1244

1245
        if (ret_path)
77,811✔
1246
                *ret_path = TAKE_PTR(p);
77,676✔
1247

1248
        *ret_dir = d;
77,811✔
1249
        return 0;
77,811✔
1250
}
1251

1252
int chase_and_statat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
1✔
1253
        _cleanup_close_ int path_fd = -EBADF;
1✔
1254
        _cleanup_free_ char *p = NULL;
1✔
1255
        int r;
1✔
1256

1257
        assert(path);
1✔
1258
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1✔
1259
        assert(ret_stat);
1✔
1260

1261
        if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0)
1✔
1262
                /* Shortcut this call if none of the special features of this call are requested. We can't
1263
                 * take the shortcut if CHASE_MUST_BE_* is set because fstatat() alone does not verify the
1264
                 * inode type. */
1265
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
1✔
UNCOV
1266
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
1267

1268
        r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
1✔
1269
        if (r < 0)
1✔
1270
                return r;
1271
        assert(path_fd >= 0);
1✔
1272

1273
        if (fstat(path_fd, ret_stat) < 0)
1✔
UNCOV
1274
                return -errno;
×
1275

1276
        if (ret_path)
1✔
1277
                *ret_path = TAKE_PTR(p);
1✔
1278

1279
        return 0;
1280
}
1281

1282
int chase_and_accessat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
16✔
1283
        _cleanup_close_ int path_fd = -EBADF;
16✔
1284
        _cleanup_free_ char *p = NULL;
16✔
1285
        int r;
16✔
1286

1287
        assert(path);
16✔
1288
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
16✔
1289

1290
        if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0)
16✔
1291
                /* Shortcut this call if none of the special features of this call are requested. */
1292
                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
16✔
UNCOV
1293
                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
1294

1295
        r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
30✔
1296
        if (r < 0)
16✔
1297
                return r;
1298
        assert(path_fd >= 0);
16✔
1299

1300
        r = access_fd(path_fd, access_mode);
16✔
1301
        if (r < 0)
16✔
1302
                return r;
1303

1304
        if (ret_path)
16✔
1305
                *ret_path = TAKE_PTR(p);
2✔
1306

1307
        return 0;
1308
}
1309

1310
int chase_and_fopenat_unlocked(
42,670✔
1311
                int root_fd,
1312
                int dir_fd,
1313
                const char *path,
1314
                ChaseFlags chase_flags,
1315
                const char *open_flags,
1316
                char **ret_path,
1317
                FILE **ret_file) {
1318

1319
        _cleanup_free_ char *final_path = NULL;
42,670✔
1320
        _cleanup_close_ int fd = -EBADF;
42,670✔
1321
        int mode_flags, r;
42,670✔
1322

1323
        assert(path);
42,670✔
1324
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
42,670✔
1325
        assert(open_flags);
42,670✔
1326
        assert(ret_file);
42,670✔
1327

1328
        mode_flags = fopen_mode_to_flags(open_flags);
42,670✔
1329
        if (mode_flags < 0)
42,670✔
1330
                return mode_flags;
1331

1332
        fd = chase_and_openat(root_fd, dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
85,339✔
1333
        if (fd < 0)
42,670✔
1334
                return fd;
1335

1336
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
39,890✔
1337
        if (r < 0)
39,890✔
1338
                return r;
1339

1340
        if (ret_path)
39,890✔
1341
                *ret_path = TAKE_PTR(final_path);
1✔
1342

1343
        return 0;
1344
}
1345

1346
int chase_and_unlinkat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
295✔
1347
        _cleanup_free_ char *p = NULL, *fname = NULL;
295✔
1348
        _cleanup_close_ int fd = -EBADF;
295✔
1349
        int r;
295✔
1350

1351
        assert(path);
295✔
1352
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT|CHASE_MUST_BE_SOCKET|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_DIRECTORY|CHASE_EXTRACT_FILENAME|CHASE_MKDIR_0755)));
295✔
1353

1354
        fd = chase_and_openat(root_fd, dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
295✔
1355
        if (fd < 0)
295✔
1356
                return fd;
1357

1358
        r = path_extract_filename(p, &fname);
249✔
1359
        if (r < 0)
249✔
1360
                return r;
1361

1362
        if (unlinkat(fd, fname, unlink_flags) < 0)
249✔
1363
                return -errno;
109✔
1364

1365
        if (ret_path)
140✔
1366
                *ret_path = TAKE_PTR(p);
21✔
1367

1368
        return 0;
1369
}
1370

1371
int chase_and_open_parent_at(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
990✔
1372
        int pfd, r;
990✔
1373

1374
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
990✔
1375

1376
        r = chaseat(root_fd, dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
990✔
1377
        if (r < 0)
990✔
1378
                return r;
990✔
1379

1380
        return pfd;
990✔
1381
}
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