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

systemd / systemd / 15986406979

30 Jun 2025 05:03PM UTC coverage: 72.045% (-0.09%) from 72.13%
15986406979

push

github

bluca
man/systemd-sysext: list ephemeral/ephemeral-import in the list of options

ephemeral/ephemeral-import are described as possible '--mutable' options but
not present in the list. Note, "systemd-sysext --help" lists them correctly.

300514 of 417119 relevant lines covered (72.05%)

708586.28 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

79
static int chaseat_needs_absolute(int dir_fd, const char *path) {
3,304,174✔
80
        if (dir_fd < 0)
3,304,174✔
81
                return path_is_absolute(path);
15,328✔
82

83
        return dir_fd_is_root(dir_fd);
3,296,510✔
84
}
85

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

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

100
        /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
101
        if (FLAGS_SET(flags, CHASE_NONEXISTENT))
3,305,046✔
102
                assert(!ret_fd);
190,924✔
103

104
        if (FLAGS_SET(flags, CHASE_STEP))
3,305,046✔
105
                assert(!ret_fd);
26,887✔
106

107
        if (isempty(path))
3,306,175✔
108
                path = ".";
109

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

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

189
                r = dir_fd_is_root_or_cwd(dir_fd);
830,546✔
190
                if (r < 0)
822,961✔
191
                        return r;
192
                if (r > 0)
830,546✔
193
                        flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
745,790✔
194
        }
195

196
        if (!(flags &
3,305,046✔
197
              (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP|
198
               CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_PARENT)) &&
199
            !ret_path && ret_fd) {
2,029,975✔
200

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

207
                *ret_fd = r;
853✔
208
                return 0;
853✔
209
        }
210

211
        buffer = strdup(path);
3,304,174✔
212
        if (!buffer)
3,304,174✔
213
                return -ENOMEM;
214

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

222
        bool need_absolute = r;
3,304,174✔
223
        if (need_absolute) {
3,304,174✔
224
                done = strdup("/");
3,218,513✔
225
                if (!done)
3,218,513✔
226
                        return -ENOMEM;
227
        }
228

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

237
        if (fstat(fd, &st) < 0)
3,304,174✔
238
                return -errno;
×
239

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

250
        if (ENDSWITH_SET(buffer, "/", "/.")) {
3,304,174✔
251
                flags |= CHASE_MUST_BE_DIRECTORY;
6,230✔
252
                if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
6,230✔
253
                        append_trail_slash = true;
10✔
254
        } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/.."))
3,297,944✔
255
                flags |= CHASE_MUST_BE_DIRECTORY;
1,138✔
256

257
        if (FLAGS_SET(flags, CHASE_PARENT))
3,304,174✔
258
                flags |= CHASE_MUST_BE_DIRECTORY;
37,859✔
259

260
        for (todo = buffer;;) {
3,304,174✔
261
                _cleanup_free_ char *first = NULL;
20,608,236✔
262
                _cleanup_close_ int child = -EBADF;
22,658,229✔
263
                struct stat st_child;
22,658,229✔
264
                const char *e;
22,658,229✔
265

266
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
22,658,229✔
267
                if (r < 0)
22,658,229✔
268
                        return r;
269
                if (r == 0) /* We reached the end. */
22,658,229✔
270
                        break;
271

272
                first = strndup(e, r);
20,660,287✔
273
                if (!first)
20,660,287✔
274
                        return -ENOMEM;
275

276
                /* Two dots? Then chop off the last bit of what we already found out. */
277
                if (streq(first, "..")) {
20,660,287✔
278
                        _cleanup_free_ char *parent = NULL;
1,086,150✔
279
                        _cleanup_close_ int fd_parent = -EBADF;
1,086,150✔
280
                        struct stat st_parent;
1,086,150✔
281

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

290
                        fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
1,086,029✔
291
                        if (fd_parent < 0)
1,086,029✔
292
                                return -errno;
×
293

294
                        if (fstat(fd_parent, &st_parent) < 0)
1,086,029✔
295
                                return -errno;
×
296

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

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

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

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

337
                        if (FLAGS_SET(flags, CHASE_STEP))
1,085,987✔
338
                                goto chased_one;
6✔
339

340
                        if (FLAGS_SET(flags, CHASE_SAFE) &&
1,085,981✔
341
                            unsafe_transition(&st, &st_parent))
×
342
                                return log_unsafe_transition(fd, fd_parent, path, flags);
×
343

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

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

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

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

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

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

373
                /* Otherwise let's see what this is. */
374
                child = r = RET_NERRNO(openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH));
19,574,137✔
375
                if (r < 0) {
1,275,169✔
376
                        if (r != -ENOENT)
1,275,169✔
377
                                return r;
378

379
                        if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */
1,674,295✔
380
                                return r;
381

382
                        if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) {
1,275,801✔
383
                                child = xopenat_full(fd,
190✔
384
                                                     first,
385
                                                     O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC,
386
                                                     /* xopen_flags = */ 0,
387
                                                     0755);
388
                                if (child < 0)
190✔
389
                                        return child;
390
                        } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
1,274,975✔
391
                                if (!path_extend(&done, first))
5,817✔
392
                                        return -ENOMEM;
393

394
                                break;
395
                        } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
1,269,158✔
396
                                if (!path_extend(&done, first, todo))
14,997✔
397
                                        return -ENOMEM;
398

399
                                exists = false;
400
                                break;
401
                        } else
402
                                return r;
403
                }
404

405
                if (fstat(child, &st_child) < 0)
18,299,158✔
406
                        return -errno;
×
407

408
                if (FLAGS_SET(flags, CHASE_SAFE) &&
18,334,574✔
409
                    unsafe_transition(&st, &st_child))
35,416✔
410
                        return log_unsafe_transition(fd, child, path, flags);
5✔
411

412
                if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
18,299,153✔
413
                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
7,678,040✔
414
                        return log_autofs_mount_point(child, path, flags);
×
415

416
                if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
18,299,153✔
417
                        _cleanup_free_ char *destination = NULL;
11✔
418

419
                        if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
490,673✔
420
                                return log_prohibited_symlink(child, flags);
6✔
421

422
                        /* This is a symlink, in this case read the destination. But let's make sure we
423
                         * don't follow symlinks without bounds. */
424
                        if (--max_follow <= 0)
490,667✔
425
                                return -ELOOP;
426

427
                        r = readlinkat_malloc(fd, first, &destination);
490,666✔
428
                        if (r < 0)
490,666✔
429
                                return r;
430
                        if (isempty(destination))
490,676✔
431
                                return -EINVAL;
432

433
                        if (path_is_absolute(destination)) {
490,665✔
434

435
                                /* An absolute destination. Start the loop from the beginning, but use the
436
                                 * root file descriptor as base. */
437

438
                                safe_close(fd);
7,147✔
439
                                fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
7,147✔
440
                                if (fd < 0)
7,147✔
441
                                        return fd;
442

443
                                if (fstat(fd, &st) < 0)
7,147✔
444
                                        return -errno;
×
445

446
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
7,152✔
447
                                    unsafe_transition(&st_child, &st))
5✔
448
                                        return log_unsafe_transition(child, fd, path, flags);
3✔
449

450
                                /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be
451
                                 * outside of the specified dir_fd. Let's make the result absolute. */
452
                                if (!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
7,144✔
453
                                        need_absolute = true;
454

455
                                r = free_and_strdup(&done, need_absolute ? "/" : NULL);
7,443✔
456
                                if (r < 0)
7,144✔
457
                                        return r;
458
                        }
459

460
                        /* Prefix what's left to do with what we just read, and start the loop again, but
461
                         * remain in the current directory. */
462
                        if (!path_extend(&destination, todo))
490,662✔
463
                                return -ENOMEM;
464

465
                        free_and_replace(buffer, destination);
490,662✔
466
                        todo = buffer;
490,662✔
467

468
                        if (FLAGS_SET(flags, CHASE_STEP))
490,662✔
469
                                goto chased_one;
8✔
470

471
                        continue;
490,654✔
472
                }
473

474
                /* If this is not a symlink, then let's just add the name we read to what we already verified. */
475
                if (!path_extend(&done, first))
17,808,480✔
476
                        return -ENOMEM;
477

478
                if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
19,858,459✔
479
                        break;
480

481
                /* And iterate again, but go one directory further down. */
482
                st = st_child;
17,777,259✔
483
                close_and_replace(fd, child);
17,777,259✔
484
        }
485

486
        if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) {
2,049,979✔
487
                r = stat_verify_directory(&st);
43,593✔
488
                if (r < 0)
43,593✔
489
                        return r;
490
        }
491

492
        if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) {
2,049,979✔
493
                r = stat_verify_regular(&st);
42✔
494
                if (r < 0)
42✔
495
                        return r;
496
        }
497

498
        if (ret_path) {
2,049,979✔
499
                if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
1,980,629✔
500
                        _cleanup_free_ char *f = NULL;
×
501

502
                        r = path_extract_filename(done, &f);
34,027✔
503
                        if (r < 0 && r != -EADDRNOTAVAIL)
34,027✔
504
                                return r;
×
505

506
                        /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
507
                        free_and_replace(done, f);
34,027✔
508
                }
509

510
                if (!done) {
1,980,629✔
511
                        assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME));
252✔
512
                        done = strdup(".");
252✔
513
                        if (!done)
252✔
514
                                return -ENOMEM;
515
                }
516

517
                if (append_trail_slash)
1,980,629✔
518
                        if (!strextend(&done, "/"))
10✔
519
                                return -ENOMEM;
520

521
                *ret_path = TAKE_PTR(done);
1,980,629✔
522
        }
523

524
        if (ret_fd) {
2,049,979✔
525
                /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
526
                 * proper fd by opening /proc/self/fd/xyz. */
527

528
                assert(fd >= 0);
1,804,473✔
529
                *ret_fd = TAKE_FD(fd);
1,804,473✔
530
        }
531

532
        if (FLAGS_SET(flags, CHASE_STEP))
2,049,979✔
533
                return 1;
534

535
        return exists;
2,023,106✔
536

537
chased_one:
14✔
538
        if (ret_path) {
14✔
539
                const char *e;
14✔
540

541
                if (!done) {
14✔
542
                        assert(!need_absolute);
1✔
543
                        done = strdup(append_trail_slash ? "./" : ".");
1✔
544
                        if (!done)
1✔
545
                                return -ENOMEM;
×
546
                }
547

548
                /* todo may contain slashes at the beginning. */
549
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
14✔
550
                if (r < 0)
14✔
551
                        return r;
552
                if (r == 0)
14✔
553
                        *ret_path = TAKE_PTR(done);
×
554
                else {
555
                        char *c;
14✔
556

557
                        c = path_join(done, e);
14✔
558
                        if (!c)
14✔
559
                                return -ENOMEM;
560

561
                        *ret_path = c;
14✔
562
                }
563
        }
564

565
        return 0;
566
}
567

568
int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
2,552,649✔
569
        _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
2,552,649✔
570
        _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
5,105,298✔
571
        int r;
2,552,649✔
572

573
        assert(path);
2,552,649✔
574

575
        if (isempty(path))
5,105,297✔
576
                return -EINVAL;
577

578
        r = empty_or_root_harder_to_null(&root);
2,552,648✔
579
        if (r < 0)
2,552,648✔
580
                return r;
581

582
        /* A root directory of "/" or "" is identical to "/". */
583
        if (empty_or_root(root)) {
2,552,645✔
584
                root = "/";
2,473,694✔
585

586
                /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(),
587
                 * hence below is not necessary, but let's shortcut. */
588
                flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
2,473,694✔
589

590
        } else {
591
                r = path_make_absolute_cwd(root, &root_abs);
78,951✔
592
                if (r < 0)
78,951✔
593
                        return r;
594

595
                /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
596
                 * end. While we won't resolve the root path we still simplify it. */
597
                root = path_simplify(root_abs);
78,951✔
598

599
                assert(path_is_absolute(root));
78,951✔
600
                assert(!empty_or_root(root));
78,951✔
601

602
                if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
78,951✔
603
                        absolute = path_join(root, path);
12,924✔
604
                        if (!absolute)
12,924✔
605
                                return -ENOMEM;
606
                }
607

608
                flags |= CHASE_AT_RESOLVE_IN_ROOT;
78,951✔
609
        }
610

611
        if (!absolute) {
2,552,645✔
612
                r = path_make_absolute_cwd(path, &absolute);
2,539,721✔
613
                if (r < 0)
2,539,721✔
614
                        return r;
615
        }
616

617
        path = path_startswith(absolute, root);
2,552,645✔
618
        if (!path)
2,552,645✔
619
                return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG,
×
620
                                      SYNTHETIC_ERRNO(ECHRNG),
621
                                      "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
622
                                      absolute, root);
623

624
        fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
2,552,645✔
625
        if (fd < 0)
2,552,645✔
626
                return -errno;
1✔
627

628
        r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
2,963,110✔
629
        if (r < 0)
2,552,644✔
630
                return r;
631

632
        if (ret_path) {
1,889,962✔
633
                if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
1,827,739✔
634

635
                        /* When "root" points to the root directory, the result of chaseat() is always
636
                         * absolute, hence it is not necessary to prefix with the root. When "root" points to
637
                         * a non-root directory, the result path is always normalized and relative, hence
638
                         * we can simply call path_join() and not necessary to call path_simplify().
639
                         * As a special case, chaseat() may return "." or "./", which are normalized too,
640
                         * but we need to drop "." before merging with root. */
641

642
                        if (empty_or_root(root))
1,795,273✔
643
                                assert(path_is_absolute(p));
1,757,486✔
644
                        else {
645
                                char *q;
37,787✔
646

647
                                assert(!path_is_absolute(p));
37,787✔
648

649
                                q = path_join(root, p + STR_IN_SET(p, ".", "./"));
37,787✔
650
                                if (!q)
37,787✔
651
                                        return -ENOMEM;
×
652

653
                                free_and_replace(p, q);
37,787✔
654
                        }
655
                }
656

657
                *ret_path = TAKE_PTR(p);
1,827,739✔
658
        }
659

660
        if (ret_fd)
1,889,962✔
661
                *ret_fd = TAKE_FD(pfd);
1,753,596✔
662

663
        return r;
664
}
665

666
int chaseat_prefix_root(const char *path, const char *root, char **ret) {
104,693✔
667
        char *q;
104,693✔
668
        int r;
104,693✔
669

670
        assert(path);
104,693✔
671
        assert(ret);
104,693✔
672

673
        /* This is mostly for prefixing the result of chaseat(). */
674

675
        if (!path_is_absolute(path)) {
104,693✔
676
                _cleanup_free_ char *root_abs = NULL;
2,794✔
677

678
                r = empty_or_root_harder_to_null(&root);
2,794✔
679
                if (r < 0 && r != -ENOENT)
2,794✔
680
                        return r;
681

682
                /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
683
                if (empty_or_root(root))
2,794✔
684
                        return -EINVAL;
685

686
                r = path_make_absolute_cwd(root, &root_abs);
2,794✔
687
                if (r < 0)
2,794✔
688
                        return r;
689

690
                root = path_simplify(root_abs);
2,794✔
691

692
                q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
2,794✔
693
        } else
694
                q = strdup(path);
101,899✔
695
        if (!q)
104,693✔
696
                return -ENOMEM;
697

698
        *ret = q;
104,693✔
699
        return 0;
104,693✔
700
}
701

702
int chase_extract_filename(const char *path, const char *root, char **ret) {
2,267✔
703
        int r;
2,267✔
704

705
        /* This is similar to path_extract_filename(), but takes root directory.
706
         * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */
707

708
        assert(path);
2,267✔
709
        assert(ret);
2,267✔
710

711
        if (isempty(path))
2,267✔
712
                return -EINVAL;
713

714
        if (!path_is_absolute(path))
4,534✔
715
                return -EINVAL;
716

717
        r = empty_or_root_harder_to_null(&root);
2,267✔
718
        if (r < 0 && r != -ENOENT)
2,267✔
719
                return r;
720

721
        if (!empty_or_root(root)) {
2,267✔
722
                _cleanup_free_ char *root_abs = NULL;
1,123✔
723

724
                r = path_make_absolute_cwd(root, &root_abs);
1,123✔
725
                if (r < 0)
1,123✔
726
                        return r;
727

728
                path = path_startswith(path, root_abs);
1,123✔
729
                if (!path)
1,123✔
730
                        return -EINVAL;
731
        }
732

733
        if (!isempty(path)) {
2,267✔
734
                r = path_extract_filename(path, ret);
2,123✔
735
                if (r != -EADDRNOTAVAIL)
2,123✔
736
                        return r;
737
        }
738

739
        return strdup_to(ret, ".");
151✔
740
}
741

742
int chase_and_open(
52,232✔
743
                const char *path,
744
                const char *root,
745
                ChaseFlags chase_flags,
746
                int open_flags,
747
                char **ret_path) {
748

749
        _cleanup_close_ int path_fd = -EBADF;
52,232✔
750
        _cleanup_free_ char *p = NULL, *fname = NULL;
52,232✔
751
        int r;
52,232✔
752

753
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
52,232✔
754

755
        if (empty_or_root(root) && !ret_path &&
52,232✔
756
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
50,117✔
757
                /* Shortcut this call if none of the special features of this call are requested */
758
                return xopenat_full(AT_FDCWD, path,
49,564✔
759
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
49,564✔
760
                                    /* xopen_flags = */ 0,
761
                                    MODE_INVALID);
762

763
        r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
2,668✔
764
        if (r < 0)
2,668✔
765
                return r;
766
        assert(path_fd >= 0);
2,262✔
767

768
        if (!FLAGS_SET(chase_flags, CHASE_PARENT) &&
2,262✔
769
            !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) {
2,261✔
770
                r = chase_extract_filename(p, root, &fname);
2,261✔
771
                if (r < 0)
2,261✔
772
                        return r;
773
        }
774

775
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, MODE_INVALID);
2,263✔
776
        if (r < 0)
2,262✔
777
                return r;
778

779
        if (ret_path)
1,898✔
780
                *ret_path = TAKE_PTR(p);
724✔
781

782
        return r;
783
}
784

785
int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
36,647✔
786
        _cleanup_close_ int path_fd = -EBADF;
36,647✔
787
        _cleanup_free_ char *p = NULL;
36,647✔
788
        DIR *d;
36,647✔
789
        int r;
36,647✔
790

791
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
36,647✔
792
        assert(ret_dir);
36,647✔
793

794
        if (empty_or_root(root) && !ret_path &&
36,647✔
795
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
44✔
796
                /* Shortcut this call if none of the special features of this call are requested */
797
                d = opendir(path);
×
798
                if (!d)
×
799
                        return -errno;
×
800

801
                *ret_dir = d;
×
802
                return 0;
×
803
        }
804

805
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
36,691✔
806
        if (r < 0)
36,647✔
807
                return r;
808
        assert(path_fd >= 0);
8,000✔
809

810
        d = xopendirat(path_fd, ".", O_NOFOLLOW);
8,000✔
811
        if (!d)
8,000✔
812
                return -errno;
×
813

814
        if (ret_path)
8,000✔
815
                *ret_path = TAKE_PTR(p);
7,956✔
816

817
        *ret_dir = d;
8,000✔
818
        return 0;
8,000✔
819
}
820

821
int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
610,025✔
822
        _cleanup_close_ int path_fd = -EBADF;
610,025✔
823
        _cleanup_free_ char *p = NULL;
610,025✔
824
        int r;
610,025✔
825

826
        assert(path);
610,025✔
827
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
610,025✔
828
        assert(ret_stat);
610,025✔
829

830
        if (empty_or_root(root) && !ret_path &&
610,025✔
831
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
296,772✔
832
                /* Shortcut this call if none of the special features of this call are requested */
833
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
296,831✔
834
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
296,772✔
835

836
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
313,472✔
837
        if (r < 0)
313,253✔
838
                return r;
839
        assert(path_fd >= 0);
182,256✔
840

841
        if (fstat(path_fd, ret_stat) < 0)
182,256✔
842
                return -errno;
×
843

844
        if (ret_path)
182,256✔
845
                *ret_path = TAKE_PTR(p);
182,188✔
846

847
        return 0;
848
}
849

850
int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
34✔
851
        _cleanup_close_ int path_fd = -EBADF;
34✔
852
        _cleanup_free_ char *p = NULL;
34✔
853
        int r;
34✔
854

855
        assert(path);
34✔
856
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
34✔
857

858
        if (empty_or_root(root) && !ret_path &&
34✔
859
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
860
                /* Shortcut this call if none of the special features of this call are requested */
861
                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
×
862
                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
863

864
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
58✔
865
        if (r < 0)
34✔
866
                return r;
867
        assert(path_fd >= 0);
34✔
868

869
        r = access_fd(path_fd, access_mode);
34✔
870
        if (r < 0)
34✔
871
                return r;
872

873
        if (ret_path)
34✔
874
                *ret_path = TAKE_PTR(p);
10✔
875

876
        return 0;
877
}
878

879
int chase_and_fopen_unlocked(
48,152✔
880
                const char *path,
881
                const char *root,
882
                ChaseFlags chase_flags,
883
                const char *open_flags,
884
                char **ret_path,
885
                FILE **ret_file) {
886

887
        _cleanup_free_ char *final_path = NULL;
48,152✔
888
        _cleanup_close_ int fd = -EBADF;
48,152✔
889
        int mode_flags, r;
48,152✔
890

891
        assert(path);
48,152✔
892
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
48,152✔
893
        assert(open_flags);
48,152✔
894
        assert(ret_file);
48,152✔
895

896
        mode_flags = fopen_mode_to_flags(open_flags);
48,152✔
897
        if (mode_flags < 0)
48,152✔
898
                return mode_flags;
899

900
        fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
95,550✔
901
        if (fd < 0)
48,152✔
902
                return fd;
903

904
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
47,339✔
905
        if (r < 0)
47,339✔
906
                return r;
907

908
        if (ret_path)
47,339✔
909
                *ret_path = TAKE_PTR(final_path);
599✔
910

911
        return 0;
912
}
913

914
int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1✔
915
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
916
        _cleanup_close_ int fd = -EBADF;
1✔
917
        int r;
1✔
918

919
        assert(path);
1✔
920
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
921

922
        fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
923
        if (fd < 0)
1✔
924
                return fd;
925

926
        r = path_extract_filename(p, &fname);
1✔
927
        if (r < 0)
1✔
928
                return r;
929

930
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
931
                return -errno;
×
932

933
        if (ret_path)
1✔
934
                *ret_path = TAKE_PTR(p);
1✔
935

936
        return 0;
937
}
938

939
int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
27,595✔
940
        int pfd, r;
27,595✔
941

942
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
27,595✔
943

944
        r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
27,595✔
945
        if (r < 0)
27,595✔
946
                return r;
27,595✔
947

948
        return pfd;
27,386✔
949
}
950

951
int chase_and_openat(
965✔
952
                int dir_fd,
953
                const char *path,
954
                ChaseFlags chase_flags,
955
                int open_flags,
956
                char **ret_path) {
957

958
        _cleanup_close_ int path_fd = -EBADF;
965✔
959
        _cleanup_free_ char *p = NULL, *fname = NULL;
965✔
960
        int r;
965✔
961

962
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
965✔
963

964
        if (dir_fd == AT_FDCWD && !ret_path &&
965✔
965
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
966
                /* Shortcut this call if none of the special features of this call are requested */
967
                return xopenat_full(dir_fd, path,
×
968
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
×
969
                                    /* xopen_flags = */ 0,
970
                                    MODE_INVALID);
971

972
        r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
965✔
973
        if (r < 0)
965✔
974
                return r;
975

976
        if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
961✔
977
                r = path_extract_filename(p, &fname);
171✔
978
                if (r < 0 && r != -EADDRNOTAVAIL)
171✔
979
                        return r;
980
        }
981

982
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags= */ 0, MODE_INVALID);
1,751✔
983
        if (r < 0)
961✔
984
                return r;
985

986
        if (ret_path)
961✔
987
                *ret_path = TAKE_PTR(p);
791✔
988

989
        return r;
990
}
991

992
int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
628,953✔
993
        _cleanup_close_ int path_fd = -EBADF;
628,953✔
994
        _cleanup_free_ char *p = NULL;
628,953✔
995
        DIR *d;
628,953✔
996
        int r;
628,953✔
997

998
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
628,953✔
999
        assert(ret_dir);
628,953✔
1000

1001
        if (dir_fd == AT_FDCWD && !ret_path &&
628,953✔
1002
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
×
1003
                /* Shortcut this call if none of the special features of this call are requested */
1004
                d = opendir(path);
×
1005
                if (!d)
×
1006
                        return -errno;
×
1007

1008
                *ret_dir = d;
×
1009
                return 0;
×
1010
        }
1011

1012
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
628,953✔
1013
        if (r < 0)
628,953✔
1014
                return r;
1015
        assert(path_fd >= 0);
39,770✔
1016

1017
        d = xopendirat(path_fd, ".", O_NOFOLLOW);
39,770✔
1018
        if (!d)
39,770✔
1019
                return -errno;
×
1020

1021
        if (ret_path)
39,770✔
1022
                *ret_path = TAKE_PTR(p);
39,770✔
1023

1024
        *ret_dir = d;
39,770✔
1025
        return 0;
39,770✔
1026
}
1027

1028
int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
65✔
1029
        _cleanup_close_ int path_fd = -EBADF;
65✔
1030
        _cleanup_free_ char *p = NULL;
65✔
1031
        int r;
65✔
1032

1033
        assert(path);
65✔
1034
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
65✔
1035
        assert(ret_stat);
65✔
1036

1037
        if (dir_fd == AT_FDCWD && !ret_path &&
65✔
1038
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
1039
                /* Shortcut this call if none of the special features of this call are requested */
1040
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
×
1041
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
1042

1043
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
65✔
1044
        if (r < 0)
65✔
1045
                return r;
1046
        assert(path_fd >= 0);
33✔
1047

1048
        if (fstat(path_fd, ret_stat) < 0)
33✔
1049
                return -errno;
×
1050

1051
        if (ret_path)
33✔
1052
                *ret_path = TAKE_PTR(p);
33✔
1053

1054
        return 0;
1055
}
1056

1057
int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
1✔
1058
        _cleanup_close_ int path_fd = -EBADF;
1✔
1059
        _cleanup_free_ char *p = NULL;
1✔
1060
        int r;
1✔
1061

1062
        assert(path);
1✔
1063
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1✔
1064

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

1071
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
1✔
1072
        if (r < 0)
1✔
1073
                return r;
1074
        assert(path_fd >= 0);
1✔
1075

1076
        r = access_fd(path_fd, access_mode);
1✔
1077
        if (r < 0)
1✔
1078
                return r;
1079

1080
        if (ret_path)
1✔
1081
                *ret_path = TAKE_PTR(p);
1✔
1082

1083
        return 0;
1084
}
1085

1086
int chase_and_fopenat_unlocked(
172✔
1087
                int dir_fd,
1088
                const char *path,
1089
                ChaseFlags chase_flags,
1090
                const char *open_flags,
1091
                char **ret_path,
1092
                FILE **ret_file) {
1093

1094
        _cleanup_free_ char *final_path = NULL;
172✔
1095
        _cleanup_close_ int fd = -EBADF;
172✔
1096
        int mode_flags, r;
172✔
1097

1098
        assert(path);
172✔
1099
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
172✔
1100
        assert(open_flags);
172✔
1101
        assert(ret_file);
172✔
1102

1103
        mode_flags = fopen_mode_to_flags(open_flags);
172✔
1104
        if (mode_flags < 0)
172✔
1105
                return mode_flags;
1106

1107
        fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
343✔
1108
        if (fd < 0)
172✔
1109
                return fd;
1110

1111
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
168✔
1112
        if (r < 0)
168✔
1113
                return r;
1114

1115
        if (ret_path)
168✔
1116
                *ret_path = TAKE_PTR(final_path);
1✔
1117

1118
        return 0;
1119
}
1120

1121
int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1✔
1122
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
1123
        _cleanup_close_ int fd = -EBADF;
1✔
1124
        int r;
1✔
1125

1126
        assert(path);
1✔
1127
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
1128

1129
        fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
1130
        if (fd < 0)
1✔
1131
                return fd;
1132

1133
        r = path_extract_filename(p, &fname);
1✔
1134
        if (r < 0)
1✔
1135
                return r;
1136

1137
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
1138
                return -errno;
×
1139

1140
        if (ret_path)
1✔
1141
                *ret_path = TAKE_PTR(p);
1✔
1142

1143
        return 0;
1144
}
1145

1146
int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
955✔
1147
        int pfd, r;
955✔
1148

1149
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
955✔
1150

1151
        r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
955✔
1152
        if (r < 0)
955✔
1153
                return r;
955✔
1154

1155
        return pfd;
955✔
1156
}
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