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

systemd / systemd / 16062852561

03 Jul 2025 10:04PM UTC coverage: 72.193% (+0.1%) from 72.096%
16062852561

push

github

bluca
pcrlock: process components outside of location window properly

So far, when we tried to match a component to eent log entries we
skipped those components if they were outside of our location window.
That however is too aggressive, since it means any components that are
already in the logs, but outside of the location window will be
considered unrecognized in the logs, and thus removed from the PCR
policy.

Change things around: always try to match up all components, regardless
if inside the location window or outside, but then make it non-fatal we
can't find a component outside of the location window.

Fixes: #36079

7 of 9 new or added lines in 1 file covered. (77.78%)

4116 existing lines in 75 files now uncovered.

301219 of 417241 relevant lines covered (72.19%)

730820.5 hits per line

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

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

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

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

21
bool unsafe_transition(const struct stat *a, const struct stat *b) {
22
        /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
23
         * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files
24
         * making us believe we read something safe even though it isn't safe in the specific context we open it in. */
25

26
        if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */
37,956✔
27
                return false;
28

29
        return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
21✔
30
}
31

32
static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) {
8✔
33
        _cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL;
8✔
34
        struct stat st;
8✔
35

36
        if (!FLAGS_SET(flags, CHASE_WARN))
8✔
37
                return -ENOLINK;
38

39
        (void) fd_get_path(a, &n1);
5✔
40
        (void) fd_get_path(b, &n2);
5✔
41

42
        if (fstat(a, &st) == 0)
5✔
43
                user_a = uid_to_name(st.st_uid);
5✔
44
        if (fstat(b, &st) == 0)
5✔
45
                user_b = uid_to_name(st.st_uid);
5✔
46

47
        return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK),
5✔
48
                                 "Detected unsafe path transition %s (owned by %s) %s %s (owned by %s) during canonicalization of %s.",
49
                                 strna(n1), strna(user_a), glyph(GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path);
50
}
51

52
static int log_autofs_mount_point(int fd, const char *path, ChaseFlags flags) {
×
UNCOV
53
        _cleanup_free_ char *n1 = NULL;
×
54

UNCOV
55
        if (!FLAGS_SET(flags, CHASE_WARN))
×
56
                return -EREMOTE;
57

UNCOV
58
        (void) fd_get_path(fd, &n1);
×
59

UNCOV
60
        return log_warning_errno(SYNTHETIC_ERRNO(EREMOTE),
×
61
                                 "Detected autofs mount point %s during canonicalization of %s.",
62
                                 strna(n1), path);
63
}
64

65
static int log_prohibited_symlink(int fd, ChaseFlags flags) {
6✔
66
        _cleanup_free_ char *n1 = NULL;
6✔
67

68
        assert(fd >= 0);
6✔
69

70
        if (!FLAGS_SET(flags, CHASE_WARN))
6✔
71
                return -EREMCHG;
72

73
        (void) fd_get_path(fd, &n1);
3✔
74

75
        return log_warning_errno(SYNTHETIC_ERRNO(EREMCHG),
3✔
76
                                 "Detected symlink where not symlink is allowed at %s, refusing.",
77
                                 strna(n1));
78
}
79

80
static int chaseat_needs_absolute(int dir_fd, const char *path) {
3,456,019✔
81
        if (dir_fd < 0)
3,456,019✔
82
                return path_is_absolute(path);
15,364✔
83

84
        return dir_fd_is_root(dir_fd);
3,448,337✔
85
}
86

87
int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
88
        _cleanup_free_ char *buffer = NULL, *done = NULL;
3,456,894✔
89
        _cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
6,913,788✔
90
        unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
3,456,894✔
91
        bool exists = true, append_trail_slash = false;
3,456,894✔
92
        struct stat st; /* stat obtained from fd */
3,456,894✔
93
        const char *todo;
3,456,894✔
94
        int r;
3,456,894✔
95

96
        assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
3,456,894✔
97
        assert(!FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR));
3,456,894✔
98
        assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
3,456,894✔
99
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
3,456,894✔
100

101
        /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
102
        if (FLAGS_SET(flags, CHASE_NONEXISTENT))
3,456,894✔
103
                assert(!ret_fd);
199,730✔
104

105
        if (FLAGS_SET(flags, CHASE_STEP))
3,456,894✔
106
                assert(!ret_fd);
27,698✔
107

108
        if (isempty(path))
3,458,079✔
109
                path = ".";
110

111
        /* This function resolves symlinks of the path relative to the given directory file descriptor. If
112
         * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
113
         * are resolved relative to the given directory file descriptor. Otherwise, they are resolved
114
         * relative to the root directory of the host.
115
         *
116
         * Note that when a positive directory file descriptor is provided and CHASE_AT_RESOLVE_IN_ROOT is
117
         * specified and we find an absolute symlink, it is resolved relative to given directory file
118
         * descriptor and not the root of the host. Also, when following relative symlinks, this functions
119
         * ensures they cannot be used to "escape" the given directory file descriptor. If a positive
120
         * directory file descriptor is provided, the "path" parameter is always interpreted relative to the
121
         * given directory file descriptor, even if it is absolute. If the given directory file descriptor is
122
         * AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host.
123
         *
124
         * When "dir_fd" points to a non-root directory and CHASE_AT_RESOLVE_IN_ROOT is set, this function
125
         * always returns a relative path in "ret_path", even if "path" is an absolute path, because openat()
126
         * like functions generally ignore the directory fd if they are provided with an absolute path. When
127
         * CHASE_AT_RESOLVE_IN_ROOT is not set, then this returns relative path to the specified file
128
         * descriptor if all resolved symlinks are relative, otherwise absolute path will be returned. When
129
         * "dir_fd" is AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path"
130
         * because otherwise, if the caller passes the returned relative path to another openat() like
131
         * function, it would be resolved relative to the current working directory instead of to "/".
132
         *
133
         * Summary about the result path:
134
         * - "dir_fd" points to the root directory
135
         *    → result will be absolute
136
         * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is set
137
         *    → relative
138
         * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is not set
139
         *    → relative when all resolved symlinks are relative, otherwise absolute
140
         * - "dir_fd" is AT_FDCWD, and "path" is absolute
141
         *    → absolute
142
         * - "dir_fd" is AT_FDCWD, and "path" is relative
143
         *    → relative when all resolved symlinks are relative, otherwise absolute
144
         *
145
         * Algorithmically this operates on two path buffers: "done" are the components of the path we
146
         * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we
147
         * still need to process. On each iteration, we move one component from "todo" to "done", processing
148
         * its special meaning each time. We always keep an O_PATH fd to the component we are currently
149
         * processing, thus keeping lookup races to a minimum.
150
         *
151
         * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute
152
         * path you got as-is: fully qualified and relative to your host's root. Optionally, specify the
153
         * "dir_fd" parameter to tell this function what to do when encountering a symlink with an absolute
154
         * path as directory: resolve it relative to the given directory file descriptor.
155
         *
156
         * There are five ways to invoke this function:
157
         *
158
         * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is
159
         *    returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0
160
         *    is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is
161
         *    returned if the destination was found, -ENOENT if it wasn't.
162
         *
163
         * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file
164
         *    descriptor is returned as return value. This is useful to open files relative to some root
165
         *    directory. Note that the returned O_PATH file descriptors must be converted into a regular one
166
         *    (using fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be
167
         *    combined with CHASE_NONEXISTENT.
168
         *
169
         * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only
170
         *    the first symlink or ".." component of the path is resolved, and the resulting path is
171
         *    returned. This is useful if a caller wants to trace the path through the file system verbosely.
172
         *    Returns < 0 on error, > 0 if the path is fully normalized, and == 0 for each normalization
173
         *    step. This may be combined with CHASE_NONEXISTENT, in which case 1 is returned when a component
174
         *    is not found.
175
         *
176
         * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions
177
         *    from unprivileged to privileged files or directories. In such cases the return value is
178
         *    -ENOLINK. If CHASE_WARN is also set, a warning describing the unsafe transition is emitted.
179
         *    CHASE_WARN cannot be used in PID 1.
180
         *
181
         * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization
182
         *    is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of
183
         *    the mount point is emitted. CHASE_WARN cannot be used in PID 1.
184
         */
185

186
        if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
3,456,894✔
187
                /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
188
                 * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
189

190
                r = dir_fd_is_root_or_cwd(dir_fd);
900,156✔
191
                if (r < 0)
892,571✔
192
                        return r;
193
                if (r > 0)
900,156✔
194
                        flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
814,153✔
195
        }
196

197
        if (!(flags &
3,456,894✔
198
              (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP|
199
               CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_PARENT)) &&
200
            !ret_path && ret_fd) {
2,129,134✔
201

202
                /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
203
                 * set and doesn't care about any of the other special features we provide either. */
204
                r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
875✔
205
                if (r < 0)
875✔
206
                        return -errno;
19✔
207

208
                *ret_fd = r;
856✔
209
                return 0;
856✔
210
        }
211

212
        buffer = strdup(path);
3,456,019✔
213
        if (!buffer)
3,456,019✔
214
                return -ENOMEM;
215

216
        /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
217
         * a relative path would be interpreted relative to the current working directory. Also, let's make
218
         * the result absolute when the file descriptor of the root directory is specified. */
219
        r = chaseat_needs_absolute(dir_fd, path);
3,456,019✔
220
        if (r < 0)
3,456,019✔
221
                return r;
222

223
        bool need_absolute = r;
3,456,019✔
224
        if (need_absolute) {
3,456,019✔
225
                done = strdup("/");
3,369,111✔
226
                if (!done)
3,369,111✔
227
                        return -ENOMEM;
228
        }
229

230
        /* If a positive directory file descriptor is provided, always resolve the given path relative to it,
231
         * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat()
232
         * semantics, if the path is relative, resolve against the current working directory. Otherwise,
233
         * resolve against root. */
234
        fd = openat(dir_fd, done ?: ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
3,542,927✔
235
        if (fd < 0)
3,456,019✔
UNCOV
236
                return -errno;
×
237

238
        if (fstat(fd, &st) < 0)
3,456,019✔
UNCOV
239
                return -errno;
×
240

241
        /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
242
         * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
243
         * whether to resolve symlinks in it or not. */
244
        if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
3,456,019✔
245
                root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
86,003✔
246
        else
247
                root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
3,370,016✔
248
        if (root_fd < 0)
3,456,019✔
UNCOV
249
                return -errno;
×
250

251
        if (ENDSWITH_SET(buffer, "/", "/.")) {
3,456,019✔
252
                flags |= CHASE_MUST_BE_DIRECTORY;
6,511✔
253
                if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
6,511✔
254
                        append_trail_slash = true;
10✔
255
        } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/.."))
3,449,508✔
256
                flags |= CHASE_MUST_BE_DIRECTORY;
1,194✔
257

258
        if (FLAGS_SET(flags, CHASE_PARENT))
3,456,019✔
259
                flags |= CHASE_MUST_BE_DIRECTORY;
38,580✔
260

261
        for (todo = buffer;;) {
3,456,019✔
262
                _cleanup_free_ char *first = NULL;
21,312,801✔
263
                _cleanup_close_ int child = -EBADF;
23,444,694✔
264
                struct stat st_child;
23,444,694✔
265
                const char *e;
23,444,694✔
266

267
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
23,444,694✔
268
                if (r < 0)
23,444,694✔
269
                        return r;
270
                if (r == 0) /* We reached the end. */
23,444,694✔
271
                        break;
272

273
                first = strndup(e, r);
21,366,302✔
274
                if (!first)
21,366,302✔
275
                        return -ENOMEM;
276

277
                /* Two dots? Then chop off the last bit of what we already found out. */
278
                if (streq(first, "..")) {
21,366,302✔
279
                        _cleanup_free_ char *parent = NULL;
1,102,247✔
280
                        _cleanup_close_ int fd_parent = -EBADF;
1,102,247✔
281
                        struct stat st_parent;
1,102,247✔
282

283
                        /* If we already are at the top, then going up will not change anything. This is
284
                         * in-line with how the kernel handles this. */
285
                        if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
1,102,247✔
286
                                if (FLAGS_SET(flags, CHASE_STEP))
121✔
UNCOV
287
                                        goto chased_one;
×
288
                                continue;
121✔
289
                        }
290

291
                        fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
1,102,126✔
292
                        if (fd_parent < 0)
1,102,126✔
UNCOV
293
                                return -errno;
×
294

295
                        if (fstat(fd_parent, &st_parent) < 0)
1,102,126✔
UNCOV
296
                                return -errno;
×
297

298
                        /* If we opened the same directory, that _may_ indicate that we're at the host root
299
                         * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
300
                         * going up won't change anything. */
301
                        if (stat_inode_same(&st_parent, &st)) {
1,102,126✔
302
                                r = dir_fd_is_root(fd);
42✔
303
                                if (r < 0)
42✔
304
                                        return r;
305
                                if (r > 0) {
42✔
306
                                        if (FLAGS_SET(flags, CHASE_STEP))
42✔
UNCOV
307
                                                goto chased_one;
×
308
                                        continue;
42✔
309
                                }
310
                        }
311

312
                        r = path_extract_directory(done, &parent);
1,102,084✔
313
                        if (r >= 0) {
1,102,084✔
314
                                assert(!need_absolute || path_is_absolute(parent));
1,100,855✔
315
                                free_and_replace(done, parent);
1,100,855✔
316
                        } else if (r == -EDESTADDRREQ) {
1,229✔
317
                                /* 'done' contains filename only (i.e. no slash). */
318
                                assert(!need_absolute);
1,229✔
319
                                done = mfree(done);
1,229✔
UNCOV
320
                        } else if (r == -EADDRNOTAVAIL) {
×
321
                                /* 'done' is "/". This branch should be already handled in the above. */
322
                                assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT));
×
323
                                assert_not_reached();
×
UNCOV
324
                        } else if (r == -EINVAL) {
×
325
                                /* 'done' is an empty string, ends with '..', or an invalid path. */
326
                                assert(!need_absolute);
×
UNCOV
327
                                assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT));
×
328

UNCOV
329
                                if (!path_is_valid(done))
×
330
                                        return -EINVAL;
331

332
                                /* If we're at the top of "dir_fd", start appending ".." to "done". */
UNCOV
333
                                if (!path_extend(&done, ".."))
×
334
                                        return -ENOMEM;
335
                        } else
336
                                return r;
337

338
                        if (FLAGS_SET(flags, CHASE_STEP))
1,102,084✔
339
                                goto chased_one;
6✔
340

341
                        if (FLAGS_SET(flags, CHASE_SAFE) &&
1,102,078✔
342
                            unsafe_transition(&st, &st_parent))
×
UNCOV
343
                                return log_unsafe_transition(fd, fd_parent, path, flags);
×
344

345
                        /* If the path ends on a "..", and CHASE_PARENT is specified then our current 'fd' is
346
                         * the child of the returned normalized path, not the parent as requested. To correct
347
                         * this we have to go *two* levels up. */
348
                        if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
1,102,078✔
349
                                _cleanup_close_ int fd_grandparent = -EBADF;
2✔
350
                                struct stat st_grandparent;
2✔
351

352
                                fd_grandparent = openat(fd_parent, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
2✔
353
                                if (fd_grandparent < 0)
2✔
UNCOV
354
                                        return -errno;
×
355

356
                                if (fstat(fd_grandparent, &st_grandparent) < 0)
2✔
UNCOV
357
                                        return -errno;
×
358

359
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
2✔
360
                                    unsafe_transition(&st_parent, &st_grandparent))
×
UNCOV
361
                                        return log_unsafe_transition(fd_parent, fd_grandparent, path, flags);
×
362

363
                                st = st_grandparent;
2✔
364
                                close_and_replace(fd, fd_grandparent);
2✔
365
                                break;
2✔
366
                        }
367

368
                        /* update fd and stat */
369
                        st = st_parent;
1,102,076✔
370
                        close_and_replace(fd, fd_parent);
1,102,076✔
371
                        continue;
1,102,076✔
372
                }
373

374
                /* Otherwise let's pin it by file descriptor, via O_PATH. Sounds pretty obvious to do this,
375
                 * right? You just do open() with O_PATH, and there you go. But uh, it's not that
376
                 * easy. open() via O_PATH does not trigger automounts, but we usually want that (except if
377
                 * CHASE_NO_AUTOFS is used). But thankfully there's a way out: the newer open_tree() call,
378
                 * when specified without OPEN_TREE_CLONE actually is fully equivalent to open() with O_PATH
379
                 * – except for one thing: it triggers automounts.
380
                 *
381
                 * As it turns out some sandboxes prohibit open_tree(), and return EPERM or ENOSYS if we call
382
                 * it. But since autofs does not work inside of mount namespace anyway, let's simply handle
383
                 * this as gracefully as we can, and fall back to classic openat() if we see EPERM/ENOSYS. */
384
                if (FLAGS_SET(flags, CHASE_NO_AUTOFS))
20,264,055✔
385
                        r = -EPERM;
386
                else
387
                        child = r = RET_NERRNO(open_tree(fd, first, AT_SYMLINK_NOFOLLOW|OPEN_TREE_CLOEXEC));
12,060,219✔
388
                if (r == -EPERM || ERRNO_IS_NEG_NOT_SUPPORTED(r))
12,060,219✔
389
                        child = r = RET_NERRNO(openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH));
14,316,363✔
390
                if (r < 0) {
6,615,768✔
391
                        if (r != -ENOENT)
1,345,977✔
392
                                return r;
393

394
                        if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */
1,775,878✔
395
                                return r;
396

397
                        if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) {
1,346,637✔
398
                                child = xopenat_full(fd,
198✔
399
                                                     first,
400
                                                     O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC,
401
                                                     /* xopen_flags = */ 0,
402
                                                     0755);
403
                                if (child < 0)
198✔
404
                                        return child;
405
                        } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
1,345,775✔
406
                                if (!path_extend(&done, first))
5,938✔
407
                                        return -ENOMEM;
408

409
                                break;
410
                        } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
1,339,837✔
411
                                if (!path_extend(&done, first, todo))
15,731✔
412
                                        return -ENOMEM;
413

414
                                exists = false;
415
                                break;
416
                        } else
417
                                return r;
418
                }
419

420
                if (fstat(child, &st_child) < 0)
18,918,276✔
UNCOV
421
                        return -errno;
×
422

423
                if (FLAGS_SET(flags, CHASE_SAFE) &&
18,956,227✔
424
                    unsafe_transition(&st, &st_child))
37,951✔
425
                        return log_unsafe_transition(fd, child, path, flags);
5✔
426

427
                if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
18,918,271✔
428
                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
7,917,388✔
UNCOV
429
                        return log_autofs_mount_point(child, path, flags);
×
430

431
                if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
18,918,271✔
432
                        _cleanup_free_ char *destination = NULL;
11✔
433

434
                        if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
499,389✔
435
                                return log_prohibited_symlink(child, flags);
6✔
436

437
                        /* This is a symlink, in this case read the destination. But let's make sure we
438
                         * don't follow symlinks without bounds. */
439
                        if (--max_follow <= 0)
499,383✔
440
                                return -ELOOP;
441

442
                        r = readlinkat_malloc(fd, first, &destination);
499,382✔
443
                        if (r < 0)
499,382✔
444
                                return r;
445
                        if (isempty(destination))
499,392✔
446
                                return -EINVAL;
447

448
                        if (path_is_absolute(destination)) {
499,381✔
449

450
                                /* An absolute destination. Start the loop from the beginning, but use the
451
                                 * root file descriptor as base. */
452

453
                                safe_close(fd);
7,716✔
454
                                fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
7,716✔
455
                                if (fd < 0)
7,716✔
456
                                        return fd;
457

458
                                if (fstat(fd, &st) < 0)
7,716✔
UNCOV
459
                                        return -errno;
×
460

461
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
7,721✔
462
                                    unsafe_transition(&st_child, &st))
5✔
463
                                        return log_unsafe_transition(child, fd, path, flags);
3✔
464

465
                                /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be
466
                                 * outside of the specified dir_fd. Let's make the result absolute. */
467
                                if (!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
7,713✔
468
                                        need_absolute = true;
469

470
                                r = free_and_strdup(&done, need_absolute ? "/" : NULL);
8,012✔
471
                                if (r < 0)
7,713✔
472
                                        return r;
473
                        }
474

475
                        /* Prefix what's left to do with what we just read, and start the loop again, but
476
                         * remain in the current directory. */
477
                        if (!path_extend(&destination, todo))
499,378✔
478
                                return -ENOMEM;
479

480
                        free_and_replace(buffer, destination);
499,378✔
481
                        todo = buffer;
499,378✔
482

483
                        if (FLAGS_SET(flags, CHASE_STEP))
499,378✔
484
                                goto chased_one;
8✔
485

486
                        continue;
499,370✔
487
                }
488

489
                /* If this is not a symlink, then let's just add the name we read to what we already verified. */
490
                if (!path_extend(&done, first))
18,418,882✔
491
                        return -ENOMEM;
492

493
                if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
20,550,761✔
494
                        break;
495

496
                /* And iterate again, but go one directory further down. */
497
                st = st_child;
18,387,066✔
498
                close_and_replace(fd, child);
18,387,066✔
499
        }
500

501
        if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) {
2,131,879✔
502
                r = stat_verify_directory(&st);
44,631✔
503
                if (r < 0)
44,631✔
504
                        return r;
505
        }
506

507
        if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) {
2,131,879✔
508
                r = stat_verify_regular(&st);
46✔
509
                if (r < 0)
46✔
510
                        return r;
511
        }
512

513
        if (ret_path) {
2,131,879✔
514
                if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
2,057,797✔
UNCOV
515
                        _cleanup_free_ char *f = NULL;
×
516

517
                        r = path_extract_filename(done, &f);
34,762✔
518
                        if (r < 0 && r != -EADDRNOTAVAIL)
34,762✔
UNCOV
519
                                return r;
×
520

521
                        /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
522
                        free_and_replace(done, f);
34,762✔
523
                }
524

525
                if (!done) {
2,057,797✔
526
                        assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME));
252✔
527
                        done = strdup(".");
252✔
528
                        if (!done)
252✔
529
                                return -ENOMEM;
530
                }
531

532
                if (append_trail_slash)
2,057,797✔
533
                        if (!strextend(&done, "/"))
10✔
534
                                return -ENOMEM;
535

536
                *ret_path = TAKE_PTR(done);
2,057,797✔
537
        }
538

539
        if (ret_fd) {
2,131,879✔
540
                /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
541
                 * proper fd by opening /proc/self/fd/xyz. */
542

543
                assert(fd >= 0);
1,876,149✔
544
                *ret_fd = TAKE_FD(fd);
1,876,149✔
545
        }
546

547
        if (FLAGS_SET(flags, CHASE_STEP))
2,131,879✔
548
                return 1;
549

550
        return exists;
2,104,195✔
551

552
chased_one:
14✔
553
        if (ret_path) {
14✔
554
                const char *e;
14✔
555

556
                if (!done) {
14✔
557
                        assert(!need_absolute);
1✔
558
                        done = strdup(append_trail_slash ? "./" : ".");
1✔
559
                        if (!done)
1✔
UNCOV
560
                                return -ENOMEM;
×
561
                }
562

563
                /* todo may contain slashes at the beginning. */
564
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
14✔
565
                if (r < 0)
14✔
566
                        return r;
567
                if (r == 0)
14✔
UNCOV
568
                        *ret_path = TAKE_PTR(done);
×
569
                else {
570
                        char *c;
14✔
571

572
                        c = path_join(done, e);
14✔
573
                        if (!c)
14✔
574
                                return -ENOMEM;
575

576
                        *ret_path = c;
14✔
577
                }
578
        }
579

580
        return 0;
581
}
582

583
int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
584
        _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
2,636,085✔
585
        _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
5,272,170✔
586
        int r;
2,636,085✔
587

588
        assert(path);
2,636,085✔
589

590
        if (isempty(path))
5,272,169✔
591
                return -EINVAL;
592

593
        r = empty_or_root_harder_to_null(&root);
2,636,084✔
594
        if (r < 0)
2,636,084✔
595
                return r;
596

597
        /* A root directory of "/" or "" is identical to "/". */
598
        if (empty_or_root(root)) {
2,636,081✔
599
                root = "/";
2,555,914✔
600

601
                /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(),
602
                 * hence below is not necessary, but let's shortcut. */
603
                flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
2,555,914✔
604

605
        } else {
606
                r = path_make_absolute_cwd(root, &root_abs);
80,167✔
607
                if (r < 0)
80,167✔
608
                        return r;
609

610
                /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
611
                 * end. While we won't resolve the root path we still simplify it. */
612
                root = path_simplify(root_abs);
80,167✔
613

614
                assert(path_is_absolute(root));
80,167✔
615
                assert(!empty_or_root(root));
80,167✔
616

617
                if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
80,167✔
618
                        absolute = path_join(root, path);
12,974✔
619
                        if (!absolute)
12,974✔
620
                                return -ENOMEM;
621
                }
622

623
                flags |= CHASE_AT_RESOLVE_IN_ROOT;
80,167✔
624
        }
625

626
        if (!absolute) {
2,636,081✔
627
                r = path_make_absolute_cwd(path, &absolute);
2,623,107✔
628
                if (r < 0)
2,623,107✔
629
                        return r;
630
        }
631

632
        path = path_startswith(absolute, root);
2,636,081✔
633
        if (!path)
2,636,081✔
UNCOV
634
                return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG,
×
635
                                      SYNTHETIC_ERRNO(ECHRNG),
636
                                      "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
637
                                      absolute, root);
638

639
        fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
2,636,081✔
640
        if (fd < 0)
2,636,081✔
641
                return -errno;
1✔
642

643
        r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
3,058,569✔
644
        if (r < 0)
2,636,080✔
645
                return r;
646

647
        if (ret_path) {
1,964,965✔
648
                if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
1,898,392✔
649

650
                        /* When "root" points to the root directory, the result of chaseat() is always
651
                         * absolute, hence it is not necessary to prefix with the root. When "root" points to
652
                         * a non-root directory, the result path is always normalized and relative, hence
653
                         * we can simply call path_join() and not necessary to call path_simplify().
654
                         * As a special case, chaseat() may return "." or "./", which are normalized too,
655
                         * but we need to drop "." before merging with root. */
656

657
                        if (empty_or_root(root))
1,865,233✔
658
                                assert(path_is_absolute(p));
1,826,620✔
659
                        else {
660
                                char *q;
38,613✔
661

662
                                assert(!path_is_absolute(p));
38,613✔
663

664
                                q = path_join(root, p + STR_IN_SET(p, ".", "./"));
38,613✔
665
                                if (!q)
38,613✔
UNCOV
666
                                        return -ENOMEM;
×
667

668
                                free_and_replace(p, q);
38,613✔
669
                        }
670
                }
671

672
                *ret_path = TAKE_PTR(p);
1,898,392✔
673
        }
674

675
        if (ret_fd)
1,964,965✔
676
                *ret_fd = TAKE_FD(pfd);
1,823,601✔
677

678
        return r;
679
}
680

681
int chaseat_prefix_root(const char *path, const char *root, char **ret) {
682
        char *q;
110,258✔
683
        int r;
110,258✔
684

685
        assert(path);
110,258✔
686
        assert(ret);
110,258✔
687

688
        /* This is mostly for prefixing the result of chaseat(). */
689

690
        if (!path_is_absolute(path)) {
110,258✔
691
                _cleanup_free_ char *root_abs = NULL;
2,799✔
692

693
                r = empty_or_root_harder_to_null(&root);
2,799✔
694
                if (r < 0 && r != -ENOENT)
2,799✔
695
                        return r;
696

697
                /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
698
                if (empty_or_root(root))
2,799✔
699
                        return -EINVAL;
700

701
                r = path_make_absolute_cwd(root, &root_abs);
2,799✔
702
                if (r < 0)
2,799✔
703
                        return r;
704

705
                root = path_simplify(root_abs);
2,799✔
706

707
                q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
2,799✔
708
        } else
709
                q = strdup(path);
107,459✔
710
        if (!q)
110,258✔
711
                return -ENOMEM;
712

713
        *ret = q;
110,258✔
714
        return 0;
110,258✔
715
}
716

717
int chase_extract_filename(const char *path, const char *root, char **ret) {
718
        int r;
2,208✔
719

720
        /* This is similar to path_extract_filename(), but takes root directory.
721
         * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */
722

723
        assert(path);
2,208✔
724
        assert(ret);
2,208✔
725

726
        if (isempty(path))
2,208✔
727
                return -EINVAL;
728

729
        if (!path_is_absolute(path))
4,416✔
730
                return -EINVAL;
731

732
        r = empty_or_root_harder_to_null(&root);
2,208✔
733
        if (r < 0 && r != -ENOENT)
2,208✔
734
                return r;
735

736
        if (!empty_or_root(root)) {
2,208✔
737
                _cleanup_free_ char *root_abs = NULL;
1,130✔
738

739
                r = path_make_absolute_cwd(root, &root_abs);
1,130✔
740
                if (r < 0)
1,130✔
741
                        return r;
742

743
                path = path_startswith(path, root_abs);
1,130✔
744
                if (!path)
1,130✔
745
                        return -EINVAL;
746
        }
747

748
        if (!isempty(path)) {
2,208✔
749
                r = path_extract_filename(path, ret);
2,064✔
750
                if (r != -EADDRNOTAVAIL)
2,064✔
751
                        return r;
752
        }
753

754
        return strdup_to(ret, ".");
151✔
755
}
756

757
int chase_and_open(
758
                const char *path,
759
                const char *root,
760
                ChaseFlags chase_flags,
761
                int open_flags,
762
                char **ret_path) {
763

764
        _cleanup_close_ int path_fd = -EBADF;
53,962✔
765
        _cleanup_free_ char *p = NULL, *fname = NULL;
53,962✔
766
        int r;
53,962✔
767

768
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
53,962✔
769

770
        if (empty_or_root(root) && !ret_path &&
53,962✔
771
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
51,830✔
772
                /* Shortcut this call if none of the special features of this call are requested */
773
                return xopenat_full(AT_FDCWD, path,
51,348✔
774
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
51,348✔
775
                                    /* xopen_flags = */ 0,
776
                                    MODE_INVALID);
777

778
        r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
2,614✔
779
        if (r < 0)
2,614✔
780
                return r;
781
        assert(path_fd >= 0);
2,203✔
782

783
        if (!FLAGS_SET(chase_flags, CHASE_PARENT) &&
2,203✔
784
            !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) {
2,202✔
785
                r = chase_extract_filename(p, root, &fname);
2,202✔
786
                if (r < 0)
2,202✔
787
                        return r;
788
        }
789

790
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, MODE_INVALID);
2,204✔
791
        if (r < 0)
2,203✔
792
                return r;
793

794
        if (ret_path)
1,837✔
795
                *ret_path = TAKE_PTR(p);
729✔
796

797
        return r;
798
}
799

800
int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
801
        _cleanup_close_ int path_fd = -EBADF;
36,684✔
802
        _cleanup_free_ char *p = NULL;
36,684✔
803
        DIR *d;
36,684✔
804
        int r;
36,684✔
805

806
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
36,684✔
807
        assert(ret_dir);
36,684✔
808

809
        if (empty_or_root(root) && !ret_path &&
36,684✔
810
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
44✔
811
                /* Shortcut this call if none of the special features of this call are requested */
812
                d = opendir(path);
×
UNCOV
813
                if (!d)
×
UNCOV
814
                        return -errno;
×
815

UNCOV
816
                *ret_dir = d;
×
UNCOV
817
                return 0;
×
818
        }
819

820
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
36,728✔
821
        if (r < 0)
36,684✔
822
                return r;
823
        assert(path_fd >= 0);
8,023✔
824

825
        d = xopendirat(path_fd, ".", O_NOFOLLOW);
8,023✔
826
        if (!d)
8,023✔
UNCOV
827
                return -errno;
×
828

829
        if (ret_path)
8,023✔
830
                *ret_path = TAKE_PTR(p);
7,979✔
831

832
        *ret_dir = d;
8,023✔
833
        return 0;
8,023✔
834
}
835

836
int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
837
        _cleanup_close_ int path_fd = -EBADF;
616,184✔
838
        _cleanup_free_ char *p = NULL;
616,184✔
839
        int r;
616,184✔
840

841
        assert(path);
616,184✔
842
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
616,184✔
843
        assert(ret_stat);
616,184✔
844

845
        if (empty_or_root(root) && !ret_path &&
616,184✔
846
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
301,908✔
847
                /* Shortcut this call if none of the special features of this call are requested */
848
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
301,965✔
849
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
301,908✔
850

851
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
314,495✔
852
        if (r < 0)
314,276✔
853
                return r;
854
        assert(path_fd >= 0);
183,754✔
855

856
        if (fstat(path_fd, ret_stat) < 0)
183,754✔
UNCOV
857
                return -errno;
×
858

859
        if (ret_path)
183,754✔
860
                *ret_path = TAKE_PTR(p);
183,686✔
861

862
        return 0;
863
}
864

865
int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
866
        _cleanup_close_ int path_fd = -EBADF;
486✔
867
        _cleanup_free_ char *p = NULL;
486✔
868
        int r;
486✔
869

870
        assert(path);
486✔
871
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
486✔
872

873
        if (empty_or_root(root) && !ret_path &&
486✔
874
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
444✔
875
                /* Shortcut this call if none of the special features of this call are requested */
876
                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
888✔
877
                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
444✔
878

879
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
74✔
880
        if (r < 0)
42✔
881
                return r;
882
        assert(path_fd >= 0);
34✔
883

884
        r = access_fd(path_fd, access_mode);
34✔
885
        if (r < 0)
34✔
886
                return r;
887

888
        if (ret_path)
34✔
889
                *ret_path = TAKE_PTR(p);
10✔
890

891
        return 0;
892
}
893

894
int chase_and_fopen_unlocked(
895
                const char *path,
896
                const char *root,
897
                ChaseFlags chase_flags,
898
                const char *open_flags,
899
                char **ret_path,
900
                FILE **ret_file) {
901

902
        _cleanup_free_ char *final_path = NULL;
49,938✔
903
        _cleanup_close_ int fd = -EBADF;
49,938✔
904
        int mode_flags, r;
49,938✔
905

906
        assert(path);
49,938✔
907
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
49,938✔
908
        assert(open_flags);
49,938✔
909
        assert(ret_file);
49,938✔
910

911
        mode_flags = fopen_mode_to_flags(open_flags);
49,938✔
912
        if (mode_flags < 0)
49,938✔
913
                return mode_flags;
914

915
        fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
99,117✔
916
        if (fd < 0)
49,938✔
917
                return fd;
918

919
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
49,099✔
920
        if (r < 0)
49,099✔
921
                return r;
922

923
        if (ret_path)
49,099✔
924
                *ret_path = TAKE_PTR(final_path);
604✔
925

926
        return 0;
927
}
928

929
int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
930
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
931
        _cleanup_close_ int fd = -EBADF;
1✔
932
        int r;
1✔
933

934
        assert(path);
1✔
935
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
936

937
        fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
938
        if (fd < 0)
1✔
939
                return fd;
940

941
        r = path_extract_filename(p, &fname);
1✔
942
        if (r < 0)
1✔
943
                return r;
944

945
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
UNCOV
946
                return -errno;
×
947

948
        if (ret_path)
1✔
949
                *ret_path = TAKE_PTR(p);
1✔
950

951
        return 0;
952
}
953

954
int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
955
        int pfd, r;
28,288✔
956

957
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
28,288✔
958

959
        r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
28,288✔
960
        if (r < 0)
28,288✔
961
                return r;
28,288✔
962

963
        return pfd;
28,079✔
964
}
965

966
int chase_and_openat(
967
                int dir_fd,
968
                const char *path,
969
                ChaseFlags chase_flags,
970
                int open_flags,
971
                char **ret_path) {
972

973
        _cleanup_close_ int path_fd = -EBADF;
992✔
974
        _cleanup_free_ char *p = NULL, *fname = NULL;
992✔
975
        int r;
992✔
976

977
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
992✔
978

979
        if (dir_fd == AT_FDCWD && !ret_path &&
992✔
UNCOV
980
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
981
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
982
                return xopenat_full(dir_fd, path,
×
UNCOV
983
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
×
984
                                    /* xopen_flags = */ 0,
985
                                    MODE_INVALID);
986

987
        r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
992✔
988
        if (r < 0)
992✔
989
                return r;
990

991
        if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
988✔
992
                r = path_extract_filename(p, &fname);
180✔
993
                if (r < 0 && r != -EADDRNOTAVAIL)
180✔
994
                        return r;
995
        }
996

997
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags= */ 0, MODE_INVALID);
1,796✔
998
        if (r < 0)
988✔
999
                return r;
1000

1001
        if (ret_path)
988✔
1002
                *ret_path = TAKE_PTR(p);
809✔
1003

1004
        return r;
1005
}
1006

1007
int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
1008
        _cleanup_close_ int path_fd = -EBADF;
691,771✔
1009
        _cleanup_free_ char *p = NULL;
691,771✔
1010
        DIR *d;
691,771✔
1011
        int r;
691,771✔
1012

1013
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
691,771✔
1014
        assert(ret_dir);
691,771✔
1015

1016
        if (dir_fd == AT_FDCWD && !ret_path &&
691,771✔
UNCOV
1017
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
×
1018
                /* Shortcut this call if none of the special features of this call are requested */
1019
                d = opendir(path);
×
UNCOV
1020
                if (!d)
×
UNCOV
1021
                        return -errno;
×
1022

UNCOV
1023
                *ret_dir = d;
×
UNCOV
1024
                return 0;
×
1025
        }
1026

1027
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
691,771✔
1028
        if (r < 0)
691,771✔
1029
                return r;
1030
        assert(path_fd >= 0);
41,089✔
1031

1032
        d = xopendirat(path_fd, ".", O_NOFOLLOW);
41,089✔
1033
        if (!d)
41,089✔
UNCOV
1034
                return -errno;
×
1035

1036
        if (ret_path)
41,089✔
1037
                *ret_path = TAKE_PTR(p);
41,089✔
1038

1039
        *ret_dir = d;
41,089✔
1040
        return 0;
41,089✔
1041
}
1042

1043
int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
1044
        _cleanup_close_ int path_fd = -EBADF;
326✔
1045
        _cleanup_free_ char *p = NULL;
326✔
1046
        int r;
326✔
1047

1048
        assert(path);
326✔
1049
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
326✔
1050
        assert(ret_stat);
326✔
1051

1052
        if (dir_fd == AT_FDCWD && !ret_path &&
326✔
UNCOV
1053
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
1054
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
1055
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
×
UNCOV
1056
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
1057

1058
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
326✔
1059
        if (r < 0)
326✔
1060
                return r;
1061
        assert(path_fd >= 0);
294✔
1062

1063
        if (fstat(path_fd, ret_stat) < 0)
294✔
UNCOV
1064
                return -errno;
×
1065

1066
        if (ret_path)
294✔
1067
                *ret_path = TAKE_PTR(p);
294✔
1068

1069
        return 0;
1070
}
1071

1072
int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
1073
        _cleanup_close_ int path_fd = -EBADF;
1✔
1074
        _cleanup_free_ char *p = NULL;
1✔
1075
        int r;
1✔
1076

1077
        assert(path);
1✔
1078
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1✔
1079

1080
        if (dir_fd == AT_FDCWD && !ret_path &&
1✔
UNCOV
1081
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
1082
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
1083
                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
×
UNCOV
1084
                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
1085

1086
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
1✔
1087
        if (r < 0)
1✔
1088
                return r;
1089
        assert(path_fd >= 0);
1✔
1090

1091
        r = access_fd(path_fd, access_mode);
1✔
1092
        if (r < 0)
1✔
1093
                return r;
1094

1095
        if (ret_path)
1✔
1096
                *ret_path = TAKE_PTR(p);
1✔
1097

1098
        return 0;
1099
}
1100

1101
int chase_and_fopenat_unlocked(
1102
                int dir_fd,
1103
                const char *path,
1104
                ChaseFlags chase_flags,
1105
                const char *open_flags,
1106
                char **ret_path,
1107
                FILE **ret_file) {
1108

1109
        _cleanup_free_ char *final_path = NULL;
181✔
1110
        _cleanup_close_ int fd = -EBADF;
181✔
1111
        int mode_flags, r;
181✔
1112

1113
        assert(path);
181✔
1114
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
181✔
1115
        assert(open_flags);
181✔
1116
        assert(ret_file);
181✔
1117

1118
        mode_flags = fopen_mode_to_flags(open_flags);
181✔
1119
        if (mode_flags < 0)
181✔
1120
                return mode_flags;
1121

1122
        fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
361✔
1123
        if (fd < 0)
181✔
1124
                return fd;
1125

1126
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
177✔
1127
        if (r < 0)
177✔
1128
                return r;
1129

1130
        if (ret_path)
177✔
1131
                *ret_path = TAKE_PTR(final_path);
1✔
1132

1133
        return 0;
1134
}
1135

1136
int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1137
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
1138
        _cleanup_close_ int fd = -EBADF;
1✔
1139
        int r;
1✔
1140

1141
        assert(path);
1✔
1142
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
1143

1144
        fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
1145
        if (fd < 0)
1✔
1146
                return fd;
1147

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

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

1155
        if (ret_path)
1✔
1156
                *ret_path = TAKE_PTR(p);
1✔
1157

1158
        return 0;
1159
}
1160

1161
int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
1162
        int pfd, r;
983✔
1163

1164
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
983✔
1165

1166
        r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
983✔
1167
        if (r < 0)
983✔
1168
                return r;
983✔
1169

1170
        return pfd;
983✔
1171
}
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