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

systemd / systemd / 17872489626

19 Sep 2025 10:01PM UTC coverage: 72.323% (-0.003%) from 72.326%
17872489626

push

github

YHNdnzj
core/manager: honor show_status_overridden in manager_watch_jobs_next_time()

Prompted by #39029

1 of 1 new or added line in 1 file covered. (100.0%)

8275 existing lines in 112 files now uncovered.

302883 of 418794 relevant lines covered (72.32%)

1056411.46 hits per line

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

89.58
/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
#define CHASE_NO_SHORTCUT_MASK                          \
22
        (CHASE_NONEXISTENT |                            \
23
         CHASE_NO_AUTOFS |                              \
24
         CHASE_TRIGGER_AUTOFS |                         \
25
         CHASE_SAFE |                                   \
26
         CHASE_STEP |                                   \
27
         CHASE_PROHIBIT_SYMLINKS |                      \
28
         CHASE_PARENT |                                 \
29
         CHASE_MKDIR_0755)
30

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

36
        if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */
35,221✔
37
                return false;
38

39
        return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
21✔
40
}
41

42
static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) {
8✔
43
        _cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL;
8✔
44
        struct stat st;
8✔
45

46
        if (!FLAGS_SET(flags, CHASE_WARN))
8✔
47
                return -ENOLINK;
48

49
        (void) fd_get_path(a, &n1);
5✔
50
        (void) fd_get_path(b, &n2);
5✔
51

52
        if (fstat(a, &st) == 0)
5✔
53
                user_a = uid_to_name(st.st_uid);
5✔
54
        if (fstat(b, &st) == 0)
5✔
55
                user_b = uid_to_name(st.st_uid);
5✔
56

57
        return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK),
5✔
58
                                 "Detected unsafe path transition %s (owned by %s) %s %s (owned by %s) during canonicalization of %s.",
59
                                 strna(n1), strna(user_a), glyph(GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path);
60
}
61

62
static int log_autofs_mount_point(int fd, const char *path, ChaseFlags flags) {
×
63
        _cleanup_free_ char *n1 = NULL;
×
64

65
        if (!FLAGS_SET(flags, CHASE_WARN))
×
66
                return -EREMOTE;
67

68
        (void) fd_get_path(fd, &n1);
×
69

70
        return log_warning_errno(SYNTHETIC_ERRNO(EREMOTE),
×
71
                                 "Detected autofs mount point %s during canonicalization of %s.",
72
                                 strna(n1), path);
73
}
74

75
static int log_prohibited_symlink(int fd, ChaseFlags flags) {
6✔
76
        _cleanup_free_ char *n1 = NULL;
6✔
77

78
        assert(fd >= 0);
6✔
79

80
        if (!FLAGS_SET(flags, CHASE_WARN))
6✔
81
                return -EREMCHG;
82

83
        (void) fd_get_path(fd, &n1);
3✔
84

85
        return log_warning_errno(SYNTHETIC_ERRNO(EREMCHG),
3✔
86
                                 "Detected symlink where not symlink is allowed at %s, refusing.",
87
                                 strna(n1));
88
}
89

90
static int openat_opath_with_automount(int dir_fd, const char *path, bool automount) {
16,067,829✔
91
        static bool can_open_tree = true;
16,067,829✔
92
        int r;
16,067,829✔
93

94
        /* Pin an inode via O_PATH semantics. Sounds pretty obvious to do this, right? You just do open()
95
         * with O_PATH, and there you go. But uh, it's not that easy. open() via O_PATH does not trigger
96
         * automounts, but we may want that when CHASE_TRIGGER_AUTOFS is set. But thankfully there's
97
         * a way out: the newer open_tree() call, when specified without OPEN_TREE_CLONE actually is fully
98
         * equivalent to open() with O_PATH – except for one thing: it triggers automounts.
99
         *
100
         * As it turns out some sandboxes prohibit open_tree(), and return EPERM or ENOSYS if we call it.
101
         * But since autofs does not work inside of mount namespace anyway, let's simply handle this
102
         * as gracefully as we can, and fall back to classic openat() if we see EPERM/ENOSYS. */
103

104
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
16,067,829✔
105
        assert(path);
16,067,829✔
106

107
        if (automount && can_open_tree) {
16,067,829✔
108
                r = RET_NERRNO(open_tree(dir_fd, path, AT_SYMLINK_NOFOLLOW|OPEN_TREE_CLOEXEC));
80,819✔
109
                if (r >= 0 || (r != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(r)))
2,921✔
110
                        return r;
80,819✔
111

112
                can_open_tree = false;
×
113
        }
114

115
        return RET_NERRNO(openat(dir_fd, path, O_PATH|O_NOFOLLOW|O_CLOEXEC));
15,987,010✔
116
}
117

118
static int chaseat_needs_absolute(int dir_fd, const char *path) {
2,881,580✔
119
        if (dir_fd < 0)
2,881,580✔
120
                return path_is_absolute(path);
15,346✔
121

122
        return dir_fd_is_root(dir_fd);
2,873,907✔
123
}
124

125
int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
2,882,469✔
126
        _cleanup_free_ char *buffer = NULL, *done = NULL;
2,882,469✔
127
        _cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
5,764,938✔
128
        unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
2,882,469✔
129
        bool exists = true, append_trail_slash = false;
2,882,469✔
130
        struct stat st; /* stat obtained from fd */
2,882,469✔
131
        const char *todo;
2,882,469✔
132
        int r;
2,882,469✔
133

134
        assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
2,882,469✔
135
        assert(!FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR));
2,882,469✔
136
        assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
2,882,469✔
137
        assert(!FLAGS_SET(flags, CHASE_NO_AUTOFS|CHASE_TRIGGER_AUTOFS));
2,882,469✔
138
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
2,882,469✔
139

140
        if (FLAGS_SET(flags, CHASE_STEP))
2,882,469✔
141
                assert(!ret_fd);
24,441✔
142

143
        if (isempty(path))
2,883,591✔
144
                path = ".";
145

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

221
        if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
2,882,469✔
222
                /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
223
                 * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
224

225
                r = dir_fd_is_root_or_cwd(dir_fd);
773,910✔
226
                if (r < 0)
766,301✔
227
                        return r;
228
                if (r > 0)
773,910✔
229
                        flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
692,302✔
230
        }
231

232
        if (!ret_path && ret_fd && (flags & (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NO_SHORTCUT_MASK)) == 0) {
2,882,469✔
233
                /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
234
                 * set and doesn't care about any of the other special features we provide either. */
235
                r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
889✔
236
                if (r < 0)
889✔
237
                        return -errno;
19✔
238

239
                *ret_fd = r;
870✔
240
                return 0;
870✔
241
        }
242

243
        buffer = strdup(path);
2,881,580✔
244
        if (!buffer)
2,881,580✔
245
                return -ENOMEM;
246

247
        /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
248
         * a relative path would be interpreted relative to the current working directory. Also, let's make
249
         * the result absolute when the file descriptor of the root directory is specified. */
250
        r = chaseat_needs_absolute(dir_fd, path);
2,881,580✔
251
        if (r < 0)
2,881,580✔
252
                return r;
253

254
        bool need_absolute = r;
2,881,580✔
255
        if (need_absolute) {
2,881,580✔
256
                done = strdup("/");
2,799,067✔
257
                if (!done)
2,799,067✔
258
                        return -ENOMEM;
259
        }
260

261
        /* If a positive directory file descriptor is provided, always resolve the given path relative to it,
262
         * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat()
263
         * semantics, if the path is relative, resolve against the current working directory. Otherwise,
264
         * resolve against root. */
265
        fd = openat(dir_fd, done ?: ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
2,964,093✔
266
        if (fd < 0)
2,881,580✔
267
                return -errno;
×
268

269
        if (fstat(fd, &st) < 0)
2,881,580✔
270
                return -errno;
×
271

272
        /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
273
         * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
274
         * whether to resolve symlinks in it or not. */
275
        if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
2,881,580✔
276
                root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
81,608✔
277
        else
278
                root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
2,799,972✔
279
        if (root_fd < 0)
2,881,580✔
280
                return -errno;
×
281

282
        if (ENDSWITH_SET(buffer, "/", "/.")) {
2,881,580✔
283
                flags |= CHASE_MUST_BE_DIRECTORY;
6,138✔
284
                if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
6,138✔
285
                        append_trail_slash = true;
10✔
286
        } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/.."))
2,875,442✔
287
                flags |= CHASE_MUST_BE_DIRECTORY;
1,131✔
288

289
        if (FLAGS_SET(flags, CHASE_PARENT))
2,881,580✔
290
                flags |= CHASE_MUST_BE_DIRECTORY;
36,996✔
291

292
        for (todo = buffer;;) {
2,881,580✔
293
                _cleanup_free_ char *first = NULL;
16,644,505✔
294
                _cleanup_close_ int child = -EBADF;
18,394,726✔
295
                struct stat st_child;
18,394,726✔
296
                const char *e;
18,394,726✔
297

298
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
18,394,726✔
299
                if (r < 0)
18,394,726✔
300
                        return r;
301
                if (r == 0) /* We reached the end. */
18,394,726✔
302
                        break;
303

304
                first = strndup(e, r);
16,694,855✔
305
                if (!first)
16,694,855✔
306
                        return -ENOMEM;
307

308
                /* Two dots? Then chop off the last bit of what we already found out. */
309
                if (streq(first, "..")) {
16,694,855✔
310
                        _cleanup_free_ char *parent = NULL;
627,026✔
311
                        _cleanup_close_ int fd_parent = -EBADF;
627,026✔
312
                        struct stat st_parent;
627,026✔
313

314
                        /* If we already are at the top, then going up will not change anything. This is
315
                         * in-line with how the kernel handles this. */
316
                        if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
627,026✔
317
                                if (FLAGS_SET(flags, CHASE_STEP))
121✔
318
                                        goto chased_one;
×
319
                                continue;
121✔
320
                        }
321

322
                        fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
626,905✔
323
                        if (fd_parent < 0)
626,905✔
324
                                return -errno;
×
325

326
                        if (fstat(fd_parent, &st_parent) < 0)
626,905✔
327
                                return -errno;
×
328

329
                        /* If we opened the same directory, that _may_ indicate that we're at the host root
330
                         * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
331
                         * going up won't change anything. */
332
                        if (stat_inode_same(&st_parent, &st)) {
626,905✔
333
                                r = dir_fd_is_root(fd);
42✔
334
                                if (r < 0)
42✔
335
                                        return r;
336
                                if (r > 0) {
42✔
337
                                        if (FLAGS_SET(flags, CHASE_STEP))
42✔
338
                                                goto chased_one;
×
339
                                        continue;
42✔
340
                                }
341
                        }
342

343
                        r = path_extract_directory(done, &parent);
626,863✔
344
                        if (r >= 0) {
626,863✔
345
                                assert(!need_absolute || path_is_absolute(parent));
625,628✔
346
                                free_and_replace(done, parent);
625,628✔
347
                        } else if (r == -EDESTADDRREQ) {
1,235✔
348
                                /* 'done' contains filename only (i.e. no slash). */
349
                                assert(!need_absolute);
1,235✔
350
                                done = mfree(done);
1,235✔
351
                        } else if (r == -EADDRNOTAVAIL) {
×
352
                                /* 'done' is "/". This branch should be already handled in the above. */
353
                                assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT));
×
354
                                assert_not_reached();
×
355
                        } else if (r == -EINVAL) {
×
356
                                /* 'done' is an empty string, ends with '..', or an invalid path. */
357
                                assert(!need_absolute);
×
358
                                assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT));
×
359

360
                                if (!path_is_valid(done))
×
361
                                        return -EINVAL;
362

363
                                /* If we're at the top of "dir_fd", start appending ".." to "done". */
364
                                if (!path_extend(&done, ".."))
×
365
                                        return -ENOMEM;
366
                        } else
367
                                return r;
368

369
                        if (FLAGS_SET(flags, CHASE_STEP))
626,863✔
370
                                goto chased_one;
6✔
371

372
                        if (FLAGS_SET(flags, CHASE_SAFE) &&
626,857✔
373
                            unsafe_transition(&st, &st_parent))
×
374
                                return log_unsafe_transition(fd, fd_parent, path, flags);
×
375

376
                        /* If the path ends on a "..", and CHASE_PARENT is specified then our current 'fd' is
377
                         * the child of the returned normalized path, not the parent as requested. To correct
378
                         * this we have to go *two* levels up. */
379
                        if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
626,857✔
380
                                _cleanup_close_ int fd_grandparent = -EBADF;
2✔
381
                                struct stat st_grandparent;
2✔
382

383
                                fd_grandparent = openat(fd_parent, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
2✔
384
                                if (fd_grandparent < 0)
2✔
385
                                        return -errno;
×
386

387
                                if (fstat(fd_grandparent, &st_grandparent) < 0)
2✔
388
                                        return -errno;
×
389

390
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
2✔
391
                                    unsafe_transition(&st_parent, &st_grandparent))
×
392
                                        return log_unsafe_transition(fd_parent, fd_grandparent, path, flags);
×
393

394
                                st = st_grandparent;
2✔
395
                                close_and_replace(fd, fd_grandparent);
2✔
396
                                break;
2✔
397
                        }
398

399
                        /* update fd and stat */
400
                        st = st_parent;
626,855✔
401
                        close_and_replace(fd, fd_parent);
626,855✔
402
                        continue;
626,855✔
403
                }
404

405
                /* Otherwise let's pin it by file descriptor, via O_PATH. */
406
                child = r = openat_opath_with_automount(fd, first, /* automount = */ FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS));
16,067,829✔
407
                if (r < 0) {
16,067,829✔
408
                        if (r != -ENOENT)
1,151,216✔
409
                                return r;
410

411
                        if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */
1,520,485✔
412
                                return r;
413

414
                        if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) {
1,151,876✔
415
                                child = xopenat_full(fd,
198✔
416
                                                     first,
417
                                                     O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC,
418
                                                     /* xopen_flags = */ 0,
419
                                                     0755);
420
                                if (child < 0)
198✔
421
                                        return child;
422
                        } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
1,151,014✔
423
                                if (!path_extend(&done, first))
5,617✔
424
                                        return -ENOMEM;
425

426
                                break;
427
                        } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
1,145,397✔
428
                                if (!path_extend(&done, first, todo))
14,059✔
429
                                        return -ENOMEM;
430

431
                                exists = false;
432
                                break;
433
                        } else
434
                                return r;
435
                }
436

437
                /* ... and then check what it actually is. */
438
                if (fstat(child, &st_child) < 0)
14,916,811✔
439
                        return -errno;
×
440

441
                if (FLAGS_SET(flags, CHASE_SAFE) &&
14,952,027✔
442
                    unsafe_transition(&st, &st_child))
35,216✔
443
                        return log_unsafe_transition(fd, child, path, flags);
5✔
444

445
                if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
14,916,806✔
446
                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
6,413,101✔
447
                        return log_autofs_mount_point(child, path, flags);
×
448

449
                if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
14,916,806✔
450
                        _cleanup_free_ char *destination = NULL;
12✔
451

452
                        if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
297,700✔
453
                                return log_prohibited_symlink(child, flags);
6✔
454

455
                        /* This is a symlink, in this case read the destination. But let's make sure we
456
                         * don't follow symlinks without bounds. */
457
                        if (--max_follow <= 0)
297,694✔
458
                                return -ELOOP;
459

460
                        r = readlinkat_malloc(fd, first, &destination);
297,693✔
461
                        if (r < 0)
297,693✔
462
                                return r;
463
                        if (isempty(destination))
297,703✔
464
                                return -EINVAL;
465

466
                        if (path_is_absolute(destination)) {
297,691✔
467

468
                                /* An absolute destination. Start the loop from the beginning, but use the
469
                                 * root file descriptor as base. */
470

471
                                safe_close(fd);
7,016✔
472
                                fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
7,016✔
473
                                if (fd < 0)
7,016✔
474
                                        return fd;
475

476
                                if (fstat(fd, &st) < 0)
7,016✔
477
                                        return -errno;
×
478

479
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
7,021✔
480
                                    unsafe_transition(&st_child, &st))
5✔
481
                                        return log_unsafe_transition(child, fd, path, flags);
3✔
482

483
                                /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be
484
                                 * outside of the specified dir_fd. Let's make the result absolute. */
485
                                if (!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
7,013✔
486
                                        need_absolute = true;
487

488
                                r = free_and_strdup(&done, need_absolute ? "/" : NULL);
7,312✔
489
                                if (r < 0)
7,013✔
490
                                        return r;
491
                        }
492

493
                        /* Prefix what's left to do with what we just read, and start the loop again, but
494
                         * remain in the current directory. */
495
                        if (!path_extend(&destination, todo))
297,688✔
496
                                return -ENOMEM;
497

498
                        free_and_replace(buffer, destination);
297,688✔
499
                        todo = buffer;
297,688✔
500

501
                        if (FLAGS_SET(flags, CHASE_STEP))
297,688✔
502
                                goto chased_one;
8✔
503

504
                        continue;
297,680✔
505
                }
506

507
                /* If this is not a symlink, then let's just add the name we read to what we already verified. */
508
                if (!path_extend(&done, first))
14,619,106✔
509
                        return -ENOMEM;
510

511
                if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
16,369,313✔
512
                        break;
513

514
                /* And iterate again, but go one directory further down. */
515
                st = st_child;
14,588,448✔
516
                close_and_replace(fd, child);
14,588,448✔
517
        }
518

519
        if (exists) {
1,750,207✔
520
                if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) {
1,736,148✔
521
                        r = stat_verify_directory(&st);
51,807✔
522
                        if (r < 0)
51,807✔
523
                                return r;
524
                }
525

526
                if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) {
1,736,148✔
527
                        r = stat_verify_regular(&st);
624✔
528
                        if (r < 0)
624✔
529
                                return r;
530
                }
531
        }
532

533
        if (ret_path) {
1,750,200✔
534
                if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
1,680,804✔
535
                        _cleanup_free_ char *f = NULL;
×
536

537
                        r = path_extract_filename(done, &f);
33,268✔
538
                        if (r < 0 && r != -EADDRNOTAVAIL)
33,268✔
539
                                return r;
×
540

541
                        /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
542
                        free_and_replace(done, f);
33,268✔
543
                }
544

545
                if (!done) {
1,680,804✔
546
                        assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME));
250✔
547
                        done = strdup(".");
250✔
548
                        if (!done)
250✔
549
                                return -ENOMEM;
550
                }
551

552
                if (append_trail_slash)
1,680,804✔
553
                        if (!strextend(&done, "/"))
10✔
554
                                return -ENOMEM;
555

556
                *ret_path = TAKE_PTR(done);
1,680,804✔
557
        }
558

559
        if (ret_fd) {
1,750,200✔
560
                if (exists) {
1,610,448✔
561
                        /* Return the O_PATH fd we currently are looking to the caller. It can translate it
562
                         * to a proper fd by opening /proc/self/fd/xyz. */
563
                        assert(fd >= 0);
1,610,312✔
564
                        *ret_fd = TAKE_FD(fd);
1,610,312✔
565
                } else
566
                        *ret_fd = -EBADF;
136✔
567
        }
568

569
        if (FLAGS_SET(flags, CHASE_STEP))
1,750,200✔
570
                return 1;
571

572
        return exists;
1,725,773✔
573

574
chased_one:
14✔
575
        if (ret_path) {
14✔
576
                const char *e;
14✔
577

578
                if (!done) {
14✔
579
                        assert(!need_absolute);
1✔
580
                        done = strdup(append_trail_slash ? "./" : ".");
1✔
581
                        if (!done)
1✔
582
                                return -ENOMEM;
×
583
                }
584

585
                /* todo may contain slashes at the beginning. */
586
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
14✔
587
                if (r < 0)
14✔
588
                        return r;
589
                if (r == 0)
14✔
590
                        *ret_path = TAKE_PTR(done);
×
591
                else {
592
                        char *c;
14✔
593

594
                        c = path_join(done, e);
14✔
595
                        if (!c)
14✔
596
                                return -ENOMEM;
597

598
                        *ret_path = c;
14✔
599
                }
600
        }
601

602
        return 0;
603
}
604

605
int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
2,183,516✔
606
        _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
2,183,516✔
607
        _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
4,367,032✔
608
        int r;
2,183,516✔
609

610
        assert(path);
2,183,516✔
611

612
        if (isempty(path))
4,367,031✔
613
                return -EINVAL;
614

615
        r = empty_or_root_harder_to_null(&root);
2,183,515✔
616
        if (r < 0)
2,183,515✔
617
                return r;
618

619
        /* A root directory of "/" or "" is identical to "/". */
620
        if (empty_or_root(root)) {
2,183,512✔
621
                root = "/";
2,107,744✔
622

623
                /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(),
624
                 * hence below is not necessary, but let's shortcut. */
625
                flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
2,107,744✔
626

627
        } else {
628
                r = path_make_absolute_cwd(root, &root_abs);
75,768✔
629
                if (r < 0)
75,768✔
630
                        return r;
631

632
                /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
633
                 * end. While we won't resolve the root path we still simplify it. */
634
                root = path_simplify(root_abs);
75,768✔
635

636
                assert(path_is_absolute(root));
75,768✔
637
                assert(!empty_or_root(root));
75,768✔
638

639
                if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
75,768✔
640
                        absolute = path_join(root, path);
13,554✔
641
                        if (!absolute)
13,554✔
642
                                return -ENOMEM;
643
                }
644

645
                flags |= CHASE_AT_RESOLVE_IN_ROOT;
75,768✔
646
        }
647

648
        if (!absolute) {
2,183,512✔
649
                r = path_make_absolute_cwd(path, &absolute);
2,169,958✔
650
                if (r < 0)
2,169,958✔
651
                        return r;
652
        }
653

654
        path = path_startswith(absolute, root);
2,183,512✔
655
        if (!path)
2,183,512✔
656
                return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG,
×
657
                                      SYNTHETIC_ERRNO(ECHRNG),
658
                                      "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
659
                                      absolute, root);
660

661
        fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
2,183,512✔
662
        if (fd < 0)
2,183,512✔
663
                return -errno;
1✔
664

665
        r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
2,566,265✔
666
        if (r < 0)
2,183,511✔
667
                return r;
668

669
        if (ret_path) {
1,586,542✔
670
                if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
1,524,643✔
671

672
                        /* When "root" points to the root directory, the result of chaseat() is always
673
                         * absolute, hence it is not necessary to prefix with the root. When "root" points to
674
                         * a non-root directory, the result path is always normalized and relative, hence
675
                         * we can simply call path_join() and not necessary to call path_simplify().
676
                         * As a special case, chaseat() may return "." or "./", which are normalized too,
677
                         * but we need to drop "." before merging with root. */
678

679
                        if (empty_or_root(root))
1,492,969✔
680
                                assert(path_is_absolute(p));
1,457,244✔
681
                        else {
682
                                char *q;
35,725✔
683

684
                                assert(!path_is_absolute(p));
35,725✔
685

686
                                q = path_join(root, p + STR_IN_SET(p, ".", "./"));
35,725✔
687
                                if (!q)
35,725✔
688
                                        return -ENOMEM;
×
689

690
                                free_and_replace(p, q);
35,725✔
691
                        }
692
                }
693

694
                *ret_path = TAKE_PTR(p);
1,524,643✔
695
        }
696

697
        if (ret_fd)
1,586,542✔
698
                *ret_fd = TAKE_FD(pfd);
1,455,498✔
699

700
        return r;
701
}
702

703
int chaseat_prefix_root(const char *path, const char *root, char **ret) {
119,453✔
704
        char *q;
119,453✔
705
        int r;
119,453✔
706

707
        assert(path);
119,453✔
708
        assert(ret);
119,453✔
709

710
        /* This is mostly for prefixing the result of chaseat(). */
711

712
        if (!path_is_absolute(path)) {
119,453✔
713
                _cleanup_free_ char *root_abs = NULL;
2,953✔
714

715
                r = empty_or_root_harder_to_null(&root);
2,953✔
716
                if (r < 0 && r != -ENOENT)
2,953✔
717
                        return r;
718

719
                /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
720
                if (empty_or_root(root))
2,953✔
721
                        return -EINVAL;
722

723
                r = path_make_absolute_cwd(root, &root_abs);
2,953✔
724
                if (r < 0)
2,953✔
725
                        return r;
726

727
                root = path_simplify(root_abs);
2,953✔
728

729
                q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
2,953✔
730
        } else
731
                q = strdup(path);
116,500✔
732
        if (!q)
119,453✔
733
                return -ENOMEM;
734

735
        *ret = q;
119,453✔
736
        return 0;
119,453✔
737
}
738

739
int chase_extract_filename(const char *path, const char *root, char **ret) {
2,191✔
740
        int r;
2,191✔
741

742
        /* This is similar to path_extract_filename(), but takes root directory.
743
         * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */
744

745
        assert(path);
2,191✔
746
        assert(ret);
2,191✔
747

748
        if (isempty(path))
2,191✔
749
                return -EINVAL;
750

751
        if (!path_is_absolute(path))
4,382✔
752
                return -EINVAL;
753

754
        r = empty_or_root_harder_to_null(&root);
2,191✔
755
        if (r < 0 && r != -ENOENT)
2,191✔
756
                return r;
757

758
        if (!empty_or_root(root)) {
2,191✔
759
                _cleanup_free_ char *root_abs = NULL;
1,145✔
760

761
                r = path_make_absolute_cwd(root, &root_abs);
1,145✔
762
                if (r < 0)
1,145✔
763
                        return r;
764

765
                path = path_startswith(path, root_abs);
1,145✔
766
                if (!path)
1,145✔
767
                        return -EINVAL;
768
        }
769

770
        if (!isempty(path)) {
2,191✔
771
                r = path_extract_filename(path, ret);
2,049✔
772
                if (r != -EADDRNOTAVAIL)
2,049✔
773
                        return r;
774
        }
775

776
        return strdup_to(ret, ".");
149✔
777
}
778

779
int chase_and_open(
42,315✔
780
                const char *path,
781
                const char *root,
782
                ChaseFlags chase_flags,
783
                int open_flags,
784
                char **ret_path) {
785

786
        _cleanup_close_ int path_fd = -EBADF;
42,315✔
787
        _cleanup_free_ char *p = NULL, *fname = NULL;
42,315✔
788
        int r;
42,315✔
789

790
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
42,315✔
791

792
        XOpenFlags xopen_flags = 0;
42,315✔
793
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY))
42,315✔
794
                open_flags |= O_DIRECTORY;
14✔
795
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR))
42,315✔
796
                xopen_flags |= XO_REGULAR;
467✔
797

798
        if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
42,315✔
799
                /* Shortcut this call if none of the special features of this call are requested */
800
                return xopenat_full(AT_FDCWD, path,
39,732✔
801
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
39,732✔
802
                                    xopen_flags,
803
                                    MODE_INVALID);
804

805
        r = chase(path, root, (CHASE_PARENT|chase_flags)&~CHASE_MUST_BE_REGULAR, &p, &path_fd);
2,583✔
806
        if (r < 0)
2,583✔
807
                return r;
808
        assert(path_fd >= 0);
2,186✔
809

810
        if (!FLAGS_SET(chase_flags, CHASE_PARENT) &&
2,186✔
811
            !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) {
2,185✔
812
                r = chase_extract_filename(p, root, &fname);
2,185✔
813
                if (r < 0)
2,185✔
814
                        return r;
815
        }
816

817
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, xopen_flags, MODE_INVALID);
2,187✔
818
        if (r < 0)
2,186✔
819
                return r;
820

821
        if (ret_path)
1,820✔
822
                *ret_path = TAKE_PTR(p);
760✔
823

824
        return r;
825
}
826

827
int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
36,632✔
828
        _cleanup_close_ int path_fd = -EBADF;
36,632✔
829
        _cleanup_free_ char *p = NULL;
36,632✔
830
        DIR *d;
36,632✔
831
        int r;
36,632✔
832

833
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_REGULAR)));
36,632✔
834
        assert(ret_dir);
36,632✔
835

836
        if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) {
36,632✔
837
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
838
                d = opendir(path);
×
UNCOV
839
                if (!d)
×
UNCOV
840
                        return -errno;
×
841

UNCOV
842
                *ret_dir = d;
×
UNCOV
843
                return 0;
×
844
        }
845

846
        r = chase(path, root, chase_flags|CHASE_MUST_BE_DIRECTORY, ret_path ? &p : NULL, &path_fd);
36,679✔
847
        if (r < 0)
36,632✔
848
                return r;
849
        assert(path_fd >= 0);
7,984✔
850

851
        d = xopendirat(path_fd, /* path= */ NULL, /* flags= */ 0);
7,984✔
852
        if (!d)
7,984✔
UNCOV
853
                return -errno;
×
854

855
        if (ret_path)
7,984✔
856
                *ret_path = TAKE_PTR(p);
7,937✔
857

858
        *ret_dir = d;
7,984✔
859
        return 0;
7,984✔
860
}
861

862
int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
478,569✔
863
        _cleanup_close_ int path_fd = -EBADF;
478,569✔
864
        _cleanup_free_ char *p = NULL;
478,569✔
865
        int r;
478,569✔
866

867
        assert(path);
478,569✔
868
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
478,569✔
869
        assert(ret_stat);
478,569✔
870

871
        if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
478,569✔
872
                /* Shortcut this call if none of the special features of this call are requested */
873
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
286,183✔
874
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
286,125✔
875

876
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
192,663✔
877
        if (r < 0)
192,444✔
878
                return r;
879
        assert(path_fd >= 0);
95,394✔
880

881
        if (fstat(path_fd, ret_stat) < 0)
95,394✔
UNCOV
882
                return -errno;
×
883

884
        if (ret_path)
95,394✔
885
                *ret_path = TAKE_PTR(p);
95,326✔
886

887
        return 0;
888
}
889

890
int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
286✔
891
        _cleanup_close_ int path_fd = -EBADF;
286✔
892
        _cleanup_free_ char *p = NULL;
286✔
893
        int r;
286✔
894

895
        assert(path);
286✔
896
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
286✔
897

898
        if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
286✔
899
                /* Shortcut this call if none of the special features of this call are requested */
900
                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
470✔
901
                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
235✔
902

903
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
92✔
904
        if (r < 0)
51✔
905
                return r;
906
        assert(path_fd >= 0);
43✔
907

908
        r = access_fd(path_fd, access_mode);
43✔
909
        if (r < 0)
43✔
910
                return r;
911

912
        if (ret_path)
43✔
913
                *ret_path = TAKE_PTR(p);
10✔
914

915
        return 0;
916
}
917

918
int chase_and_fopen_unlocked(
38,356✔
919
                const char *path,
920
                const char *root,
921
                ChaseFlags chase_flags,
922
                const char *open_flags,
923
                char **ret_path,
924
                FILE **ret_file) {
925

926
        _cleanup_free_ char *final_path = NULL;
38,356✔
927
        _cleanup_close_ int fd = -EBADF;
38,356✔
928
        int mode_flags, r;
38,356✔
929

930
        assert(path);
38,356✔
931
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT|CHASE_MUST_BE_DIRECTORY)));
38,356✔
932
        assert(open_flags);
38,356✔
933
        assert(ret_file);
38,356✔
934

935
        mode_flags = fopen_mode_to_flags(open_flags);
38,356✔
936
        if (mode_flags < 0)
38,356✔
937
                return mode_flags;
938

939
        fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
75,925✔
940
        if (fd < 0)
38,356✔
941
                return fd;
942

943
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
36,199✔
944
        if (r < 0)
36,199✔
945
                return r;
946

947
        if (ret_path)
36,199✔
948
                *ret_path = TAKE_PTR(final_path);
633✔
949

950
        return 0;
951
}
952

953
int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1✔
954
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
955
        _cleanup_close_ int fd = -EBADF;
1✔
956
        int r;
1✔
957

958
        assert(path);
1✔
959
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
960

961
        fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
962
        if (fd < 0)
1✔
963
                return fd;
964

965
        r = path_extract_filename(p, &fname);
1✔
966
        if (r < 0)
1✔
967
                return r;
968

969
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
UNCOV
970
                return -errno;
×
971

972
        if (ret_path)
1✔
973
                *ret_path = TAKE_PTR(p);
1✔
974

975
        return 0;
976
}
977

978
int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
26,375✔
979
        int pfd, r;
26,375✔
980

981
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
26,375✔
982

983
        r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
26,375✔
984
        if (r < 0)
26,375✔
985
                return r;
26,375✔
986

987
        return pfd;
26,255✔
988
}
989

990
int chase_and_openat(
983✔
991
                int dir_fd,
992
                const char *path,
993
                ChaseFlags chase_flags,
994
                int open_flags,
995
                char **ret_path) {
996

997
        _cleanup_close_ int path_fd = -EBADF;
983✔
998
        _cleanup_free_ char *p = NULL, *fname = NULL;
983✔
999
        int r;
983✔
1000

1001
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
983✔
1002

1003
        XOpenFlags xopen_flags = 0;
983✔
1004
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY))
983✔
UNCOV
1005
                open_flags |= O_DIRECTORY;
×
1006
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR))
983✔
1007
                xopen_flags |= XO_REGULAR;
1✔
1008

1009
        if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
983✔
1010
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
1011
                return xopenat_full(dir_fd, path,
×
UNCOV
1012
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
×
1013
                                    xopen_flags,
1014
                                    MODE_INVALID);
1015

1016
        r = chaseat(dir_fd, path, (chase_flags|CHASE_PARENT)&~CHASE_MUST_BE_REGULAR, &p, &path_fd);
983✔
1017
        if (r < 0)
983✔
1018
                return r;
1019

1020
        if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
979✔
1021
                r = path_extract_filename(p, &fname);
180✔
1022
                if (r < 0 && r != -EADDRNOTAVAIL)
180✔
1023
                        return r;
1024
        }
1025

1026
        r = xopenat_full(
2,937✔
1027
                        path_fd,
1028
                        strempty(fname),
1,778✔
1029
                        open_flags|O_NOFOLLOW,
1030
                        xopen_flags,
1031
                        MODE_INVALID);
1032
        if (r < 0)
979✔
1033
                return r;
1034

1035
        if (ret_path)
979✔
1036
                *ret_path = TAKE_PTR(p);
800✔
1037

1038
        return r;
1039
}
1040

1041
int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
569,523✔
1042
        _cleanup_close_ int path_fd = -EBADF;
569,523✔
1043
        _cleanup_free_ char *p = NULL;
569,523✔
1044
        DIR *d;
569,523✔
1045
        int r;
569,523✔
1046

1047
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_REGULAR)));
569,523✔
1048
        assert(ret_dir);
569,523✔
1049

1050
        if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) {
569,523✔
1051
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
1052
                d = opendir(path);
×
UNCOV
1053
                if (!d)
×
UNCOV
1054
                        return -errno;
×
1055

UNCOV
1056
                *ret_dir = d;
×
UNCOV
1057
                return 0;
×
1058
        }
1059

1060
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
569,523✔
1061
        if (r < 0)
569,523✔
1062
                return r;
1063
        assert(path_fd >= 0);
38,110✔
1064

1065
        d = xopendirat(path_fd, /* path= */ NULL, /* flags= */ 0);
38,110✔
1066
        if (!d)
38,110✔
UNCOV
1067
                return -errno;
×
1068

1069
        if (ret_path)
38,110✔
1070
                *ret_path = TAKE_PTR(p);
38,110✔
1071

1072
        *ret_dir = d;
38,110✔
1073
        return 0;
38,110✔
1074
}
1075

1076
int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
1✔
1077
        _cleanup_close_ int path_fd = -EBADF;
1✔
1078
        _cleanup_free_ char *p = NULL;
1✔
1079
        int r;
1✔
1080

1081
        assert(path);
1✔
1082
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1✔
1083
        assert(ret_stat);
1✔
1084

1085
        if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
1✔
1086
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
1087
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
×
UNCOV
1088
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
1089

1090
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
1✔
1091
        if (r < 0)
1✔
1092
                return r;
1093
        assert(path_fd >= 0);
1✔
1094

1095
        if (fstat(path_fd, ret_stat) < 0)
1✔
UNCOV
1096
                return -errno;
×
1097

1098
        if (ret_path)
1✔
1099
                *ret_path = TAKE_PTR(p);
1✔
1100

1101
        return 0;
1102
}
1103

1104
int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
1✔
1105
        _cleanup_close_ int path_fd = -EBADF;
1✔
1106
        _cleanup_free_ char *p = NULL;
1✔
1107
        int r;
1✔
1108

1109
        assert(path);
1✔
1110
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1✔
1111

1112
        if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
1✔
1113
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
1114
                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
×
UNCOV
1115
                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
1116

1117
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
1✔
1118
        if (r < 0)
1✔
1119
                return r;
1120
        assert(path_fd >= 0);
1✔
1121

1122
        r = access_fd(path_fd, access_mode);
1✔
1123
        if (r < 0)
1✔
1124
                return r;
1125

1126
        if (ret_path)
1✔
1127
                *ret_path = TAKE_PTR(p);
1✔
1128

1129
        return 0;
1130
}
1131

1132
int chase_and_fopenat_unlocked(
181✔
1133
                int dir_fd,
1134
                const char *path,
1135
                ChaseFlags chase_flags,
1136
                const char *open_flags,
1137
                char **ret_path,
1138
                FILE **ret_file) {
1139

1140
        _cleanup_free_ char *final_path = NULL;
181✔
1141
        _cleanup_close_ int fd = -EBADF;
181✔
1142
        int mode_flags, r;
181✔
1143

1144
        assert(path);
181✔
1145
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
181✔
1146
        assert(open_flags);
181✔
1147
        assert(ret_file);
181✔
1148

1149
        mode_flags = fopen_mode_to_flags(open_flags);
181✔
1150
        if (mode_flags < 0)
181✔
1151
                return mode_flags;
1152

1153
        fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
361✔
1154
        if (fd < 0)
181✔
1155
                return fd;
1156

1157
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
177✔
1158
        if (r < 0)
177✔
1159
                return r;
1160

1161
        if (ret_path)
177✔
1162
                *ret_path = TAKE_PTR(final_path);
1✔
1163

1164
        return 0;
1165
}
1166

1167
int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1✔
1168
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
1169
        _cleanup_close_ int fd = -EBADF;
1✔
1170
        int r;
1✔
1171

1172
        assert(path);
1✔
1173
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
1174

1175
        fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
1176
        if (fd < 0)
1✔
1177
                return fd;
1178

1179
        r = path_extract_filename(p, &fname);
1✔
1180
        if (r < 0)
1✔
1181
                return r;
1182

1183
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
UNCOV
1184
                return -errno;
×
1185

1186
        if (ret_path)
1✔
1187
                *ret_path = TAKE_PTR(p);
1✔
1188

1189
        return 0;
1190
}
1191

1192
int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
983✔
1193
        int pfd, r;
983✔
1194

1195
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
983✔
1196

1197
        r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
983✔
1198
        if (r < 0)
983✔
1199
                return r;
983✔
1200

1201
        return pfd;
983✔
1202
}
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