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

systemd / systemd / 16104615022

06 Jul 2025 08:06PM UTC coverage: 72.1% (+0.04%) from 72.057%
16104615022

push

github

web-flow
Two follow-ups for recent PRs (#38062)

12 of 13 new or added lines in 2 files covered. (92.31%)

1207 existing lines in 43 files now uncovered.

300846 of 417265 relevant lines covered (72.1%)

718237.88 hits per line

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

88.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
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 */
34,675✔
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) {
×
53
        _cleanup_free_ char *n1 = NULL;
×
54

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

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

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 openat_opath_with_automount(int dir_fd, const char *path, bool automount) {
19,558,395✔
81
        static bool can_open_tree = true;
19,558,395✔
82
        int r;
19,558,395✔
83

84
        /* Pin an inode via O_PATH semantics. Sounds pretty obvious to do this, right? You just do open()
85
         * with O_PATH, and there you go. But uh, it's not that easy. open() via O_PATH does not trigger
86
         * automounts, but we usually want that (except if CHASE_NO_AUTOFS is used). But thankfully there's
87
         * a way out: the newer open_tree() call, when specified without OPEN_TREE_CLONE actually is fully
88
         * equivalent to open() with O_PATH – except for one thing: it triggers automounts.
89
         *
90
         * As it turns out some sandboxes prohibit open_tree(), and return EPERM or ENOSYS if we call it.
91
         * But since autofs does not work inside of mount namespace anyway, let's simply handle this
92
         * as gracefully as we can, and fall back to classic openat() if we see EPERM/ENOSYS. */
93

94
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
19,558,395✔
95
        assert(path);
19,558,395✔
96

97
        if (automount && can_open_tree) {
19,558,395✔
98
                r = RET_NERRNO(open_tree(dir_fd, path, AT_SYMLINK_NOFOLLOW|OPEN_TREE_CLOEXEC));
11,668,306✔
99
                if (r >= 0 || (r != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(r)))
1,014,496✔
100
                        return r;
11,668,306✔
101

NEW
102
                can_open_tree = false;
×
103
        }
104

105
        return RET_NERRNO(openat(dir_fd, path, O_PATH|O_NOFOLLOW|O_CLOEXEC));
7,890,089✔
106
}
107

108
static int chaseat_needs_absolute(int dir_fd, const char *path) {
3,305,814✔
109
        if (dir_fd < 0)
3,305,814✔
110
                return path_is_absolute(path);
15,320✔
111

112
        return dir_fd_is_root(dir_fd);
3,298,154✔
113
}
114

115
int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
116
        _cleanup_free_ char *buffer = NULL, *done = NULL;
3,306,688✔
117
        _cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
6,613,376✔
118
        unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
3,306,688✔
119
        bool exists = true, append_trail_slash = false;
3,306,688✔
120
        struct stat st; /* stat obtained from fd */
3,306,688✔
121
        const char *todo;
3,306,688✔
122
        int r;
3,306,688✔
123

124
        assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
3,306,688✔
125
        assert(!FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR));
3,306,688✔
126
        assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
3,306,688✔
127
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
3,306,688✔
128

129
        /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
130
        if (FLAGS_SET(flags, CHASE_NONEXISTENT))
3,306,688✔
131
                assert(!ret_fd);
191,092✔
132

133
        if (FLAGS_SET(flags, CHASE_STEP))
3,306,688✔
134
                assert(!ret_fd);
26,650✔
135

136
        if (isempty(path))
3,307,792✔
137
                path = ".";
138

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

214
        if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
3,306,688✔
215
                /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
216
                 * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
217

218
                r = dir_fd_is_root_or_cwd(dir_fd);
851,786✔
219
                if (r < 0)
844,201✔
220
                        return r;
221
                if (r > 0)
851,786✔
222
                        flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
767,359✔
223
        }
224

225
        if (!(flags &
3,306,688✔
226
              (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP|
227
               CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_PARENT)) &&
228
            !ret_path && ret_fd) {
2,042,888✔
229

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

236
                *ret_fd = r;
855✔
237
                return 0;
855✔
238
        }
239

240
        buffer = strdup(path);
3,305,814✔
241
        if (!buffer)
3,305,814✔
242
                return -ENOMEM;
243

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

251
        bool need_absolute = r;
3,305,814✔
252
        if (need_absolute) {
3,305,814✔
253
                done = strdup("/");
3,220,482✔
254
                if (!done)
3,220,482✔
255
                        return -ENOMEM;
256
        }
257

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

266
        if (fstat(fd, &st) < 0)
3,305,814✔
267
                return -errno;
×
268

269
        /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
270
         * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
271
         * whether to resolve symlinks in it or not. */
272
        if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
3,305,814✔
273
                root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
84,427✔
274
        else
275
                root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
3,221,387✔
276
        if (root_fd < 0)
3,305,814✔
277
                return -errno;
×
278

279
        if (ENDSWITH_SET(buffer, "/", "/.")) {
3,305,814✔
280
                flags |= CHASE_MUST_BE_DIRECTORY;
6,171✔
281
                if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
6,171✔
282
                        append_trail_slash = true;
10✔
283
        } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/.."))
3,299,643✔
284
                flags |= CHASE_MUST_BE_DIRECTORY;
1,113✔
285

286
        if (FLAGS_SET(flags, CHASE_PARENT))
3,305,814✔
287
                flags |= CHASE_MUST_BE_DIRECTORY;
37,693✔
288

289
        for (todo = buffer;;) {
3,305,814✔
290
                _cleanup_free_ char *first = NULL;
20,591,827✔
291
                _cleanup_close_ int child = -EBADF;
22,627,024✔
292
                struct stat st_child;
22,627,024✔
293
                const char *e;
22,627,024✔
294

295
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
22,627,024✔
296
                if (r < 0)
22,627,024✔
297
                        return r;
298
                if (r == 0) /* We reached the end. */
22,627,024✔
299
                        break;
300

301
                first = strndup(e, r);
20,643,185✔
302
                if (!first)
20,643,185✔
303
                        return -ENOMEM;
304

305
                /* Two dots? Then chop off the last bit of what we already found out. */
306
                if (streq(first, "..")) {
20,643,185✔
307
                        _cleanup_free_ char *parent = NULL;
1,084,790✔
308
                        _cleanup_close_ int fd_parent = -EBADF;
1,084,790✔
309
                        struct stat st_parent;
1,084,790✔
310

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

319
                        fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
1,084,669✔
320
                        if (fd_parent < 0)
1,084,669✔
321
                                return -errno;
×
322

323
                        if (fstat(fd_parent, &st_parent) < 0)
1,084,669✔
324
                                return -errno;
×
325

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

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

357
                                if (!path_is_valid(done))
×
358
                                        return -EINVAL;
359

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

366
                        if (FLAGS_SET(flags, CHASE_STEP))
1,084,627✔
367
                                goto chased_one;
6✔
368

369
                        if (FLAGS_SET(flags, CHASE_SAFE) &&
1,084,621✔
370
                            unsafe_transition(&st, &st_parent))
×
371
                                return log_unsafe_transition(fd, fd_parent, path, flags);
×
372

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

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

384
                                if (fstat(fd_grandparent, &st_grandparent) < 0)
2✔
385
                                        return -errno;
×
386

387
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
2✔
388
                                    unsafe_transition(&st_parent, &st_grandparent))
×
389
                                        return log_unsafe_transition(fd_parent, fd_grandparent, path, flags);
×
390

391
                                st = st_grandparent;
2✔
392
                                close_and_replace(fd, fd_grandparent);
2✔
393
                                break;
2✔
394
                        }
395

396
                        /* update fd and stat */
397
                        st = st_parent;
1,084,619✔
398
                        close_and_replace(fd, fd_parent);
1,084,619✔
399
                        continue;
1,084,619✔
400
                }
401

402
                /* Otherwise let's pin it by file descriptor, via O_PATH. */
403
                child = r = openat_opath_with_automount(fd, first, /* automount = */ !FLAGS_SET(flags, CHASE_NO_AUTOFS));
19,558,395✔
404
                if (r < 0) {
19,558,395✔
405
                        if (r != -ENOENT)
1,291,076✔
406
                                return r;
407

408
                        if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */
1,701,206✔
409
                                return r;
410

411
                        if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) {
1,291,722✔
412
                                child = xopenat_full(fd,
196✔
413
                                                     first,
414
                                                     O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC,
415
                                                     /* xopen_flags = */ 0,
416
                                                     0755);
417
                                if (child < 0)
196✔
418
                                        return child;
419
                        } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
1,290,876✔
420
                                if (!path_extend(&done, first))
5,812✔
421
                                        return -ENOMEM;
422

423
                                break;
424
                        } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
1,285,064✔
425
                                if (!path_extend(&done, first, todo))
14,469✔
426
                                        return -ENOMEM;
427

428
                                exists = false;
429
                                break;
430
                        } else
431
                                return r;
432
                }
433

434
                /* ... and then check what it actually is. */
435
                if (fstat(child, &st_child) < 0)
18,267,515✔
436
                        return -errno;
×
437

438
                if (FLAGS_SET(flags, CHASE_SAFE) &&
18,302,185✔
439
                    unsafe_transition(&st, &st_child))
34,670✔
440
                        return log_unsafe_transition(fd, child, path, flags);
5✔
441

442
                if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
18,267,510✔
443
                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
7,613,509✔
444
                        return log_autofs_mount_point(child, path, flags);
×
445

446
                if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
18,267,510✔
447
                        _cleanup_free_ char *destination = NULL;
13✔
448

449
                        if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
490,735✔
450
                                return log_prohibited_symlink(child, flags);
6✔
451

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

457
                        r = readlinkat_malloc(fd, first, &destination);
490,728✔
458
                        if (r < 0)
490,728✔
459
                                return r;
460
                        if (isempty(destination))
490,738✔
461
                                return -EINVAL;
462

463
                        if (path_is_absolute(destination)) {
490,725✔
464

465
                                /* An absolute destination. Start the loop from the beginning, but use the
466
                                 * root file descriptor as base. */
467

468
                                safe_close(fd);
7,145✔
469
                                fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
7,145✔
470
                                if (fd < 0)
7,145✔
471
                                        return fd;
472

473
                                if (fstat(fd, &st) < 0)
7,145✔
474
                                        return -errno;
×
475

476
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
7,150✔
477
                                    unsafe_transition(&st_child, &st))
5✔
478
                                        return log_unsafe_transition(child, fd, path, flags);
3✔
479

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

485
                                r = free_and_strdup(&done, need_absolute ? "/" : NULL);
7,441✔
486
                                if (r < 0)
7,142✔
487
                                        return r;
488
                        }
489

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

495
                        free_and_replace(buffer, destination);
490,722✔
496
                        todo = buffer;
490,722✔
497

498
                        if (FLAGS_SET(flags, CHASE_STEP))
490,722✔
499
                                goto chased_one;
8✔
500

501
                        continue;
490,714✔
502
                }
503

504
                /* If this is not a symlink, then let's just add the name we read to what we already verified. */
505
                if (!path_extend(&done, first))
17,776,775✔
506
                        return -ENOMEM;
507

508
                if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
19,811,958✔
509
                        break;
510

511
                /* And iterate again, but go one directory further down. */
512
                st = st_child;
17,745,714✔
513
                close_and_replace(fd, child);
17,745,714✔
514
        }
515

516
        if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) {
2,035,183✔
517
                r = stat_verify_directory(&st);
43,339✔
518
                if (r < 0)
43,339✔
519
                        return r;
520
        }
521

522
        if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) {
2,035,183✔
523
                r = stat_verify_regular(&st);
43✔
524
                if (r < 0)
43✔
525
                        return r;
526
        }
527

528
        if (ret_path) {
2,035,183✔
529
                if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
1,967,034✔
530
                        _cleanup_free_ char *f = NULL;
×
531

532
                        r = path_extract_filename(done, &f);
33,903✔
533
                        if (r < 0 && r != -EADDRNOTAVAIL)
33,903✔
534
                                return r;
×
535

536
                        /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
537
                        free_and_replace(done, f);
33,903✔
538
                }
539

540
                if (!done) {
1,967,034✔
541
                        assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME));
248✔
542
                        done = strdup(".");
248✔
543
                        if (!done)
248✔
544
                                return -ENOMEM;
545
                }
546

547
                if (append_trail_slash)
1,967,034✔
548
                        if (!strextend(&done, "/"))
10✔
549
                                return -ENOMEM;
550

551
                *ret_path = TAKE_PTR(done);
1,967,034✔
552
        }
553

554
        if (ret_fd) {
2,035,183✔
555
                /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
556
                 * proper fd by opening /proc/self/fd/xyz. */
557

558
                assert(fd >= 0);
1,789,130✔
559
                *ret_fd = TAKE_FD(fd);
1,789,130✔
560
        }
561

562
        if (FLAGS_SET(flags, CHASE_STEP))
2,035,183✔
563
                return 1;
564

565
        return exists;
2,008,547✔
566

567
chased_one:
14✔
568
        if (ret_path) {
14✔
569
                const char *e;
14✔
570

571
                if (!done) {
14✔
572
                        assert(!need_absolute);
1✔
573
                        done = strdup(append_trail_slash ? "./" : ".");
1✔
574
                        if (!done)
1✔
575
                                return -ENOMEM;
×
576
                }
577

578
                /* todo may contain slashes at the beginning. */
579
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
14✔
580
                if (r < 0)
14✔
581
                        return r;
582
                if (r == 0)
14✔
583
                        *ret_path = TAKE_PTR(done);
×
584
                else {
585
                        char *c;
14✔
586

587
                        c = path_join(done, e);
14✔
588
                        if (!c)
14✔
589
                                return -ENOMEM;
590

591
                        *ret_path = c;
14✔
592
                }
593
        }
594

595
        return 0;
596
}
597

598
int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
599
        _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
2,532,720✔
600
        _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
5,065,440✔
601
        int r;
2,532,720✔
602

603
        assert(path);
2,532,720✔
604

605
        if (isempty(path))
5,065,439✔
606
                return -EINVAL;
607

608
        r = empty_or_root_harder_to_null(&root);
2,532,719✔
609
        if (r < 0)
2,532,719✔
610
                return r;
611

612
        /* A root directory of "/" or "" is identical to "/". */
613
        if (empty_or_root(root)) {
2,532,716✔
614
                root = "/";
2,454,100✔
615

616
                /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(),
617
                 * hence below is not necessary, but let's shortcut. */
618
                flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
2,454,100✔
619

620
        } else {
621
                r = path_make_absolute_cwd(root, &root_abs);
78,616✔
622
                if (r < 0)
78,616✔
623
                        return r;
624

625
                /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
626
                 * end. While we won't resolve the root path we still simplify it. */
627
                root = path_simplify(root_abs);
78,616✔
628

629
                assert(path_is_absolute(root));
78,616✔
630
                assert(!empty_or_root(root));
78,616✔
631

632
                if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
78,616✔
633
                        absolute = path_join(root, path);
12,950✔
634
                        if (!absolute)
12,950✔
635
                                return -ENOMEM;
636
                }
637

638
                flags |= CHASE_AT_RESOLVE_IN_ROOT;
78,616✔
639
        }
640

641
        if (!absolute) {
2,532,716✔
642
                r = path_make_absolute_cwd(path, &absolute);
2,519,766✔
643
                if (r < 0)
2,519,766✔
644
                        return r;
645
        }
646

647
        path = path_startswith(absolute, root);
2,532,716✔
648
        if (!path)
2,532,716✔
649
                return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG,
×
650
                                      SYNTHETIC_ERRNO(ECHRNG),
651
                                      "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
652
                                      absolute, root);
653

654
        fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
2,532,716✔
655
        if (fd < 0)
2,532,716✔
656
                return -errno;
1✔
657

658
        r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
2,942,153✔
659
        if (r < 0)
2,532,715✔
660
                return r;
661

662
        if (ret_path) {
1,873,277✔
663
                if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
1,812,362✔
664

665
                        /* When "root" points to the root directory, the result of chaseat() is always
666
                         * absolute, hence it is not necessary to prefix with the root. When "root" points to
667
                         * a non-root directory, the result path is always normalized and relative, hence
668
                         * we can simply call path_join() and not necessary to call path_simplify().
669
                         * As a special case, chaseat() may return "." or "./", which are normalized too,
670
                         * but we need to drop "." before merging with root. */
671

672
                        if (empty_or_root(root))
1,780,028✔
673
                                assert(path_is_absolute(p));
1,742,468✔
674
                        else {
675
                                char *q;
37,560✔
676

677
                                assert(!path_is_absolute(p));
37,560✔
678

679
                                q = path_join(root, p + STR_IN_SET(p, ".", "./"));
37,560✔
680
                                if (!q)
37,560✔
681
                                        return -ENOMEM;
×
682

683
                                free_and_replace(p, q);
37,560✔
684
                        }
685
                }
686

687
                *ret_path = TAKE_PTR(p);
1,812,362✔
688
        }
689

690
        if (ret_fd)
1,873,277✔
691
                *ret_fd = TAKE_FD(pfd);
1,737,586✔
692

693
        return r;
694
}
695

696
int chaseat_prefix_root(const char *path, const char *root, char **ret) {
697
        char *q;
106,520✔
698
        int r;
106,520✔
699

700
        assert(path);
106,520✔
701
        assert(ret);
106,520✔
702

703
        /* This is mostly for prefixing the result of chaseat(). */
704

705
        if (!path_is_absolute(path)) {
106,520✔
706
                _cleanup_free_ char *root_abs = NULL;
2,794✔
707

708
                r = empty_or_root_harder_to_null(&root);
2,794✔
709
                if (r < 0 && r != -ENOENT)
2,794✔
710
                        return r;
711

712
                /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
713
                if (empty_or_root(root))
2,794✔
714
                        return -EINVAL;
715

716
                r = path_make_absolute_cwd(root, &root_abs);
2,794✔
717
                if (r < 0)
2,794✔
718
                        return r;
719

720
                root = path_simplify(root_abs);
2,794✔
721

722
                q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
2,794✔
723
        } else
724
                q = strdup(path);
103,726✔
725
        if (!q)
106,520✔
726
                return -ENOMEM;
727

728
        *ret = q;
106,520✔
729
        return 0;
106,520✔
730
}
731

732
int chase_extract_filename(const char *path, const char *root, char **ret) {
733
        int r;
2,204✔
734

735
        /* This is similar to path_extract_filename(), but takes root directory.
736
         * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */
737

738
        assert(path);
2,204✔
739
        assert(ret);
2,204✔
740

741
        if (isempty(path))
2,204✔
742
                return -EINVAL;
743

744
        if (!path_is_absolute(path))
4,408✔
745
                return -EINVAL;
746

747
        r = empty_or_root_harder_to_null(&root);
2,204✔
748
        if (r < 0 && r != -ENOENT)
2,204✔
749
                return r;
750

751
        if (!empty_or_root(root)) {
2,204✔
752
                _cleanup_free_ char *root_abs = NULL;
1,119✔
753

754
                r = path_make_absolute_cwd(root, &root_abs);
1,119✔
755
                if (r < 0)
1,119✔
756
                        return r;
757

758
                path = path_startswith(path, root_abs);
1,119✔
759
                if (!path)
1,119✔
760
                        return -EINVAL;
761
        }
762

763
        if (!isempty(path)) {
2,204✔
764
                r = path_extract_filename(path, ret);
2,064✔
765
                if (r != -EADDRNOTAVAIL)
2,064✔
766
                        return r;
767
        }
768

769
        return strdup_to(ret, ".");
147✔
770
}
771

772
int chase_and_open(
773
                const char *path,
774
                const char *root,
775
                ChaseFlags chase_flags,
776
                int open_flags,
777
                char **ret_path) {
778

779
        _cleanup_close_ int path_fd = -EBADF;
52,592✔
780
        _cleanup_free_ char *p = NULL, *fname = NULL;
52,592✔
781
        int r;
52,592✔
782

783
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
52,592✔
784

785
        if (empty_or_root(root) && !ret_path &&
52,592✔
786
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
50,472✔
787
                /* Shortcut this call if none of the special features of this call are requested */
788
                return xopenat_full(AT_FDCWD, path,
49,982✔
789
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
49,982✔
790
                                    /* xopen_flags = */ 0,
791
                                    MODE_INVALID);
792

793
        r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
2,610✔
794
        if (r < 0)
2,610✔
795
                return r;
796
        assert(path_fd >= 0);
2,199✔
797

798
        if (!FLAGS_SET(chase_flags, CHASE_PARENT) &&
2,199✔
799
            !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) {
2,198✔
800
                r = chase_extract_filename(p, root, &fname);
2,198✔
801
                if (r < 0)
2,198✔
802
                        return r;
803
        }
804

805
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, MODE_INVALID);
2,200✔
806
        if (r < 0)
2,199✔
807
                return r;
808

809
        if (ret_path)
1,833✔
810
                *ret_path = TAKE_PTR(p);
724✔
811

812
        return r;
813
}
814

815
int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
816
        _cleanup_close_ int path_fd = -EBADF;
36,659✔
817
        _cleanup_free_ char *p = NULL;
36,659✔
818
        DIR *d;
36,659✔
819
        int r;
36,659✔
820

821
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
36,659✔
822
        assert(ret_dir);
36,659✔
823

824
        if (empty_or_root(root) && !ret_path &&
36,659✔
825
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
44✔
826
                /* Shortcut this call if none of the special features of this call are requested */
827
                d = opendir(path);
×
828
                if (!d)
×
829
                        return -errno;
×
830

831
                *ret_dir = d;
×
832
                return 0;
×
833
        }
834

835
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
36,703✔
836
        if (r < 0)
36,659✔
837
                return r;
838
        assert(path_fd >= 0);
8,008✔
839

840
        d = xopendirat(path_fd, ".", O_NOFOLLOW);
8,008✔
841
        if (!d)
8,008✔
842
                return -errno;
×
843

844
        if (ret_path)
8,008✔
845
                *ret_path = TAKE_PTR(p);
7,964✔
846

847
        *ret_dir = d;
8,008✔
848
        return 0;
8,008✔
849
}
850

851
int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
852
        _cleanup_close_ int path_fd = -EBADF;
612,646✔
853
        _cleanup_free_ char *p = NULL;
612,646✔
854
        int r;
612,646✔
855

856
        assert(path);
612,646✔
857
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
612,646✔
858
        assert(ret_stat);
612,646✔
859

860
        if (empty_or_root(root) && !ret_path &&
612,646✔
861
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
300,424✔
862
                /* Shortcut this call if none of the special features of this call are requested */
863
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
300,480✔
864
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
300,424✔
865

866
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
312,441✔
867
        if (r < 0)
312,222✔
868
                return r;
869
        assert(path_fd >= 0);
182,353✔
870

871
        if (fstat(path_fd, ret_stat) < 0)
182,353✔
872
                return -errno;
×
873

874
        if (ret_path)
182,353✔
875
                *ret_path = TAKE_PTR(p);
182,285✔
876

877
        return 0;
878
}
879

880
int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
881
        _cleanup_close_ int path_fd = -EBADF;
481✔
882
        _cleanup_free_ char *p = NULL;
481✔
883
        int r;
481✔
884

885
        assert(path);
481✔
886
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
481✔
887

888
        if (empty_or_root(root) && !ret_path &&
481✔
889
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
439✔
890
                /* Shortcut this call if none of the special features of this call are requested */
891
                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
878✔
892
                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
439✔
893

894
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
74✔
895
        if (r < 0)
42✔
896
                return r;
897
        assert(path_fd >= 0);
34✔
898

899
        r = access_fd(path_fd, access_mode);
34✔
900
        if (r < 0)
34✔
901
                return r;
902

903
        if (ret_path)
34✔
904
                *ret_path = TAKE_PTR(p);
10✔
905

906
        return 0;
907
}
908

909
int chase_and_fopen_unlocked(
910
                const char *path,
911
                const char *root,
912
                ChaseFlags chase_flags,
913
                const char *open_flags,
914
                char **ret_path,
915
                FILE **ret_file) {
916

917
        _cleanup_free_ char *final_path = NULL;
48,575✔
918
        _cleanup_close_ int fd = -EBADF;
48,575✔
919
        int mode_flags, r;
48,575✔
920

921
        assert(path);
48,575✔
922
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
48,575✔
923
        assert(open_flags);
48,575✔
924
        assert(ret_file);
48,575✔
925

926
        mode_flags = fopen_mode_to_flags(open_flags);
48,575✔
927
        if (mode_flags < 0)
48,575✔
928
                return mode_flags;
929

930
        fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
96,390✔
931
        if (fd < 0)
48,575✔
932
                return fd;
933

934
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
47,751✔
935
        if (r < 0)
47,751✔
936
                return r;
937

938
        if (ret_path)
47,751✔
939
                *ret_path = TAKE_PTR(final_path);
605✔
940

941
        return 0;
942
}
943

944
int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
945
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
946
        _cleanup_close_ int fd = -EBADF;
1✔
947
        int r;
1✔
948

949
        assert(path);
1✔
950
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
951

952
        fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
953
        if (fd < 0)
1✔
954
                return fd;
955

956
        r = path_extract_filename(p, &fname);
1✔
957
        if (r < 0)
1✔
958
                return r;
959

960
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
961
                return -errno;
×
962

963
        if (ret_path)
1✔
964
                *ret_path = TAKE_PTR(p);
1✔
965

966
        return 0;
967
}
968

969
int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
970
        int pfd, r;
27,461✔
971

972
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
27,461✔
973

974
        r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
27,461✔
975
        if (r < 0)
27,461✔
976
                return r;
27,461✔
977

978
        return pfd;
27,254✔
979
}
980

981
int chase_and_openat(
982
                int dir_fd,
983
                const char *path,
984
                ChaseFlags chase_flags,
985
                int open_flags,
986
                char **ret_path) {
987

988
        _cleanup_close_ int path_fd = -EBADF;
965✔
989
        _cleanup_free_ char *p = NULL, *fname = NULL;
965✔
990
        int r;
965✔
991

992
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
965✔
993

994
        if (dir_fd == AT_FDCWD && !ret_path &&
965✔
995
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
996
                /* Shortcut this call if none of the special features of this call are requested */
997
                return xopenat_full(dir_fd, path,
×
998
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
×
999
                                    /* xopen_flags = */ 0,
1000
                                    MODE_INVALID);
1001

1002
        r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
965✔
1003
        if (r < 0)
965✔
1004
                return r;
1005

1006
        if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
961✔
1007
                r = path_extract_filename(p, &fname);
175✔
1008
                if (r < 0 && r != -EADDRNOTAVAIL)
175✔
1009
                        return r;
1010
        }
1011

1012
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags= */ 0, MODE_INVALID);
1,747✔
1013
        if (r < 0)
961✔
1014
                return r;
1015

1016
        if (ret_path)
961✔
1017
                *ret_path = TAKE_PTR(p);
787✔
1018

1019
        return r;
1020
}
1021

1022
int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
1023
        _cleanup_close_ int path_fd = -EBADF;
649,009✔
1024
        _cleanup_free_ char *p = NULL;
649,009✔
1025
        DIR *d;
649,009✔
1026
        int r;
649,009✔
1027

1028
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
649,009✔
1029
        assert(ret_dir);
649,009✔
1030

1031
        if (dir_fd == AT_FDCWD && !ret_path &&
649,009✔
1032
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
×
1033
                /* Shortcut this call if none of the special features of this call are requested */
1034
                d = opendir(path);
×
1035
                if (!d)
×
1036
                        return -errno;
×
1037

1038
                *ret_dir = d;
×
1039
                return 0;
×
1040
        }
1041

1042
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
649,009✔
1043
        if (r < 0)
649,009✔
1044
                return r;
1045
        assert(path_fd >= 0);
40,149✔
1046

1047
        d = xopendirat(path_fd, ".", O_NOFOLLOW);
40,149✔
1048
        if (!d)
40,149✔
1049
                return -errno;
×
1050

1051
        if (ret_path)
40,149✔
1052
                *ret_path = TAKE_PTR(p);
40,149✔
1053

1054
        *ret_dir = d;
40,149✔
1055
        return 0;
40,149✔
1056
}
1057

1058
int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
1059
        _cleanup_close_ int path_fd = -EBADF;
326✔
1060
        _cleanup_free_ char *p = NULL;
326✔
1061
        int r;
326✔
1062

1063
        assert(path);
326✔
1064
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
326✔
1065
        assert(ret_stat);
326✔
1066

1067
        if (dir_fd == AT_FDCWD && !ret_path &&
326✔
1068
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
1069
                /* Shortcut this call if none of the special features of this call are requested */
1070
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
×
1071
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
1072

1073
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
326✔
1074
        if (r < 0)
326✔
1075
                return r;
1076
        assert(path_fd >= 0);
294✔
1077

1078
        if (fstat(path_fd, ret_stat) < 0)
294✔
1079
                return -errno;
×
1080

1081
        if (ret_path)
294✔
1082
                *ret_path = TAKE_PTR(p);
294✔
1083

1084
        return 0;
1085
}
1086

1087
int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
1088
        _cleanup_close_ int path_fd = -EBADF;
1✔
1089
        _cleanup_free_ char *p = NULL;
1✔
1090
        int r;
1✔
1091

1092
        assert(path);
1✔
1093
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1✔
1094

1095
        if (dir_fd == AT_FDCWD && !ret_path &&
1✔
1096
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
1097
                /* Shortcut this call if none of the special features of this call are requested */
1098
                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
×
1099
                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
1100

1101
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
1✔
1102
        if (r < 0)
1✔
1103
                return r;
1104
        assert(path_fd >= 0);
1✔
1105

1106
        r = access_fd(path_fd, access_mode);
1✔
1107
        if (r < 0)
1✔
1108
                return r;
1109

1110
        if (ret_path)
1✔
1111
                *ret_path = TAKE_PTR(p);
1✔
1112

1113
        return 0;
1114
}
1115

1116
int chase_and_fopenat_unlocked(
1117
                int dir_fd,
1118
                const char *path,
1119
                ChaseFlags chase_flags,
1120
                const char *open_flags,
1121
                char **ret_path,
1122
                FILE **ret_file) {
1123

1124
        _cleanup_free_ char *final_path = NULL;
176✔
1125
        _cleanup_close_ int fd = -EBADF;
176✔
1126
        int mode_flags, r;
176✔
1127

1128
        assert(path);
176✔
1129
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
176✔
1130
        assert(open_flags);
176✔
1131
        assert(ret_file);
176✔
1132

1133
        mode_flags = fopen_mode_to_flags(open_flags);
176✔
1134
        if (mode_flags < 0)
176✔
1135
                return mode_flags;
1136

1137
        fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
351✔
1138
        if (fd < 0)
176✔
1139
                return fd;
1140

1141
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
172✔
1142
        if (r < 0)
172✔
1143
                return r;
1144

1145
        if (ret_path)
172✔
1146
                *ret_path = TAKE_PTR(final_path);
1✔
1147

1148
        return 0;
1149
}
1150

1151
int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1152
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
1153
        _cleanup_close_ int fd = -EBADF;
1✔
1154
        int r;
1✔
1155

1156
        assert(path);
1✔
1157
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
1158

1159
        fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
1160
        if (fd < 0)
1✔
1161
                return fd;
1162

1163
        r = path_extract_filename(p, &fname);
1✔
1164
        if (r < 0)
1✔
1165
                return r;
1166

1167
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
1168
                return -errno;
×
1169

1170
        if (ret_path)
1✔
1171
                *ret_path = TAKE_PTR(p);
1✔
1172

1173
        return 0;
1174
}
1175

1176
int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
1177
        int pfd, r;
969✔
1178

1179
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
969✔
1180

1181
        r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
969✔
1182
        if (r < 0)
969✔
1183
                return r;
969✔
1184

1185
        return pfd;
969✔
1186
}
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