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

systemd / systemd / 21121026098

18 Jan 2026 06:15PM UTC coverage: 72.736% (+0.2%) from 72.561%
21121026098

push

github

YHNdnzj
cryptenroll,cryptsetup,shutdown: only call mlockall if we have CAP_IPC_LOCK

Calling mlockall in an unprivileged process most notably had the effect
of making systemd-cryptenroll OOM while trying to open a normal-sized
argon2 keyslot due to it hitting RLIMIT_MEMLOCK.

9 of 14 new or added lines in 4 files covered. (64.29%)

1479 existing lines in 62 files now uncovered.

310610 of 427035 relevant lines covered (72.74%)

1127441.3 hits per line

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

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

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

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

21
#define CHASE_NO_SHORTCUT_MASK                          \
22
        (CHASE_NONEXISTENT |                            \
23
         CHASE_NO_AUTOFS |                              \
24
         CHASE_TRIGGER_AUTOFS |                         \
25
         CHASE_SAFE |                                   \
26
         CHASE_STEP |                                   \
27
         CHASE_PROHIBIT_SYMLINKS |                      \
28
         CHASE_PARENT |                                 \
29
         CHASE_MKDIR_0755)
30

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

107
        if (automount && can_open_tree) {
18,829,366✔
108
                r = RET_NERRNO(open_tree(dir_fd, path, AT_SYMLINK_NOFOLLOW|OPEN_TREE_CLOEXEC));
68,793✔
109
                if (r >= 0 || (r != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(r)))
2,943✔
110
                        return r;
68,793✔
111

112
                can_open_tree = false;
×
113
        }
114

115
        return RET_NERRNO(openat(dir_fd, path, O_PATH|O_NOFOLLOW|O_CLOEXEC));
18,760,573✔
116
}
117

118
static int chaseat_needs_absolute(int dir_fd, const char *path) {
3,268,791✔
119
        if (dir_fd < 0)
3,268,791✔
120
                return path_is_absolute(path);
138✔
121

122
        return dir_fd_is_root(dir_fd);
3,268,722✔
123
}
124

125
int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
3,277,192✔
126
        _cleanup_free_ char *buffer = NULL, *done = NULL;
3,277,192✔
127
        _cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
6,554,384✔
128
        unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
3,277,192✔
129
        bool exists = true, append_trail_slash = false;
3,277,192✔
130
        struct stat st; /* stat obtained from fd */
3,277,192✔
131
        bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */
3,277,192✔
132
        const char *todo;
3,277,192✔
133
        int r;
3,277,192✔
134

135
        assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
3,277,192✔
136
        assert(!FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR));
3,277,192✔
137
        assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
3,277,192✔
138
        assert(!FLAGS_SET(flags, CHASE_NO_AUTOFS|CHASE_TRIGGER_AUTOFS));
3,277,192✔
139
        assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
3,277,192✔
140

141
        if (FLAGS_SET(flags, CHASE_STEP))
3,277,192✔
142
                assert(!ret_fd);
23,125✔
143

144
        if (isempty(path))
3,278,315✔
145
                path = ".";
146

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

222
        _cleanup_close_ int _dir_fd = -EBADF;
3,277,192✔
223
        r = dir_fd_is_root(dir_fd);
3,277,192✔
224
        if (r < 0)
800,357✔
225
                return r;
226
        if (r > 0) {
3,277,192✔
227

228
                /* Shortcut the common case where no root dir is specified, and no special flags are given to
229
                 * a regular open() */
230
                if (!ret_path &&
3,193,375✔
231
                    (flags & (CHASE_STEP|CHASE_NO_AUTOFS|CHASE_NONEXISTENT|CHASE_SAFE|CHASE_WARN|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
69,032✔
232
                        _cleanup_free_ char *slash_path = NULL;
19✔
233

234
                        if (!path_is_absolute(path)) {
8,391✔
235
                                slash_path = strjoin("/", path);
128✔
236
                                if (!slash_path)
128✔
237
                                        return -ENOMEM;
238
                        }
239

240
                        /* We use open_tree() rather than regular open() here, because it gives us direct
241
                         * control over automount behaviour, and otherwise is equivalent to open() with
242
                         * O_PATH */
243
                        fd = open_tree(-EBADF, slash_path ?: path, OPEN_TREE_CLOEXEC|(FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS) ? 0 : AT_NO_AUTOMOUNT));
25,035✔
244
                        if (fd < 0)
8,391✔
245
                                return -errno;
19✔
246

247
                        if (fstat(fd, &st) < 0)
8,372✔
UNCOV
248
                                return -errno;
×
249

250
                        exists = true;
8,372✔
251
                        goto success;
8,372✔
252
                }
253

254
                _dir_fd = open("/", O_DIRECTORY|O_RDONLY|O_CLOEXEC);
3,184,984✔
255
                if (_dir_fd < 0)
3,184,984✔
UNCOV
256
                        return -errno;
×
257

258
                dir_fd = _dir_fd;
3,184,984✔
259
                flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
3,184,984✔
260
        } else if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
83,817✔
261
                /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
262
                 * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
263

264
                r = dir_fd_is_root_or_cwd(dir_fd);
83,795✔
265
                if (r < 0)
83,795✔
266
                        return r;
267
                if (r > 0)
83,795✔
268
                        flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
73✔
269
        }
270

271
        if (!ret_path && ret_fd && (flags & (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NO_SHORTCUT_MASK)) == 0) {
3,268,801✔
272
                /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
273
                 * set and doesn't care about any of the other special features we provide either. */
274
                r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
10✔
275
                if (r < 0)
10✔
UNCOV
276
                        return -errno;
×
277

278
                *ret_fd = r;
10✔
279
                return 0;
10✔
280
        }
281

282
        buffer = strdup(path);
3,268,791✔
283
        if (!buffer)
3,268,791✔
284
                return -ENOMEM;
285

286
        /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
287
         * a relative path would be interpreted relative to the current working directory. Also, let's make
288
         * the result absolute when the file descriptor of the root directory is specified. */
289
        r = chaseat_needs_absolute(dir_fd, path);
3,268,791✔
290
        if (r < 0)
3,268,791✔
291
                return r;
292

293
        need_absolute = r;
3,268,791✔
294
        if (need_absolute) {
3,268,791✔
295
                done = strdup("/");
3,185,047✔
296
                if (!done)
3,185,047✔
297
                        return -ENOMEM;
298
        }
299

300
        /* If a positive directory file descriptor is provided, always resolve the given path relative to it,
301
         * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat()
302
         * semantics, if the path is relative, resolve against the current working directory. Otherwise,
303
         * resolve against root. */
304
        fd = openat(dir_fd, done ?: ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
3,352,535✔
305
        if (fd < 0)
3,268,791✔
UNCOV
306
                return -errno;
×
307

308
        if (fstat(fd, &st) < 0)
3,268,791✔
UNCOV
309
                return -errno;
×
310

311
        /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
312
         * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
313
         * whether to resolve symlinks in it or not. */
314
        if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
3,268,791✔
315
                root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
83,722✔
316
        else
317
                root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
3,185,069✔
318
        if (root_fd < 0)
3,268,791✔
UNCOV
319
                return -errno;
×
320

321
        if (ENDSWITH_SET(buffer, "/", "/.")) {
3,268,791✔
322
                flags |= CHASE_MUST_BE_DIRECTORY;
5,924✔
323
                if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
5,924✔
324
                        append_trail_slash = true;
4✔
325
        } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/.."))
3,262,867✔
326
                flags |= CHASE_MUST_BE_DIRECTORY;
1,132✔
327

328
        if (FLAGS_SET(flags, CHASE_PARENT))
3,268,791✔
329
                flags |= CHASE_MUST_BE_DIRECTORY;
11,611✔
330

331
        for (todo = buffer;;) {
3,268,791✔
332
                _cleanup_free_ char *first = NULL;
19,687,571✔
333
                _cleanup_close_ int child = -EBADF;
21,698,024✔
334
                struct stat st_child;
21,698,024✔
335
                const char *e;
21,698,024✔
336

337
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
21,698,024✔
338
                if (r < 0)
21,698,024✔
339
                        return r;
340
                if (r == 0) /* We reached the end. */
21,698,024✔
341
                        break;
342

343
                first = strndup(e, r);
19,712,758✔
344
                if (!first)
19,712,758✔
345
                        return -ENOMEM;
346

347
                /* Two dots? Then chop off the last bit of what we already found out. */
348
                if (streq(first, "..")) {
19,712,758✔
349
                        _cleanup_free_ char *parent = NULL;
883,392✔
350
                        _cleanup_close_ int fd_parent = -EBADF;
883,392✔
351
                        struct stat st_parent;
883,392✔
352

353
                        /* If we already are at the top, then going up will not change anything. This is
354
                         * in-line with how the kernel handles this. */
355
                        if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
883,392✔
356
                                if (FLAGS_SET(flags, CHASE_STEP))
121✔
357
                                        goto chased_one;
×
358
                                continue;
121✔
359
                        }
360

361
                        fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
883,271✔
362
                        if (fd_parent < 0)
883,271✔
UNCOV
363
                                return -errno;
×
364

365
                        if (fstat(fd_parent, &st_parent) < 0)
883,271✔
UNCOV
366
                                return -errno;
×
367

368
                        /* If we opened the same directory, that _may_ indicate that we're at the host root
369
                         * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
370
                         * going up won't change anything. */
371
                        if (stat_inode_same(&st_parent, &st)) {
883,271✔
372
                                r = dir_fd_is_root(fd);
42✔
373
                                if (r < 0)
42✔
374
                                        return r;
375
                                if (r > 0) {
42✔
376
                                        if (FLAGS_SET(flags, CHASE_STEP))
42✔
UNCOV
377
                                                goto chased_one;
×
378
                                        continue;
42✔
379
                                }
380
                        }
381

382
                        r = path_extract_directory(done, &parent);
883,229✔
383
                        if (r >= 0) {
883,229✔
384
                                assert(!need_absolute || path_is_absolute(parent));
881,972✔
385
                                free_and_replace(done, parent);
881,972✔
386
                        } else if (r == -EDESTADDRREQ) {
1,257✔
387
                                /* 'done' contains filename only (i.e. no slash). */
388
                                assert(!need_absolute);
1,257✔
389
                                done = mfree(done);
1,257✔
UNCOV
390
                        } else if (r == -EADDRNOTAVAIL) {
×
391
                                /* 'done' is "/". This branch should be already handled in the above. */
392
                                assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT));
×
UNCOV
393
                                assert_not_reached();
×
UNCOV
394
                        } else if (r == -EINVAL) {
×
395
                                /* 'done' is an empty string, ends with '..', or an invalid path. */
UNCOV
396
                                assert(!need_absolute);
×
UNCOV
397
                                assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT));
×
398

UNCOV
399
                                if (!path_is_valid(done))
×
400
                                        return -EINVAL;
401

402
                                /* If we're at the top of "dir_fd", start appending ".." to "done". */
UNCOV
403
                                if (!path_extend(&done, ".."))
×
404
                                        return -ENOMEM;
405
                        } else
406
                                return r;
407

408
                        if (FLAGS_SET(flags, CHASE_STEP))
883,229✔
409
                                goto chased_one;
6✔
410

411
                        if (FLAGS_SET(flags, CHASE_SAFE) &&
883,223✔
UNCOV
412
                            unsafe_transition(&st, &st_parent))
×
UNCOV
413
                                return log_unsafe_transition(fd, fd_parent, path, flags);
×
414

415
                        /* If the path ends on a "..", and CHASE_PARENT is specified then our current 'fd' is
416
                         * the child of the returned normalized path, not the parent as requested. To correct
417
                         * this we have to go *two* levels up. */
418
                        if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
883,223✔
419
                                _cleanup_close_ int fd_grandparent = -EBADF;
2✔
420
                                struct stat st_grandparent;
2✔
421

422
                                fd_grandparent = openat(fd_parent, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
2✔
423
                                if (fd_grandparent < 0)
2✔
UNCOV
424
                                        return -errno;
×
425

426
                                if (fstat(fd_grandparent, &st_grandparent) < 0)
2✔
UNCOV
427
                                        return -errno;
×
428

429
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
2✔
UNCOV
430
                                    unsafe_transition(&st_parent, &st_grandparent))
×
UNCOV
431
                                        return log_unsafe_transition(fd_parent, fd_grandparent, path, flags);
×
432

433
                                st = st_grandparent;
2✔
434
                                close_and_replace(fd, fd_grandparent);
2✔
435
                                break;
2✔
436
                        }
437

438
                        /* update fd and stat */
439
                        st = st_parent;
883,221✔
440
                        close_and_replace(fd, fd_parent);
883,221✔
441
                        continue;
883,221✔
442
                }
443

444
                /* Otherwise let's pin it by file descriptor, via O_PATH. */
445
                child = r = openat_opath_with_automount(fd, first, /* automount= */ FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS));
18,829,366✔
446
                if (r < 0) {
18,829,366✔
447
                        if (r != -ENOENT)
1,275,264✔
448
                                return r;
449

450
                        if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */
1,712,112✔
451
                                return r;
452

453
                        if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) {
1,275,939✔
454
                                child = xopenat(fd,
203✔
455
                                                first,
456
                                                O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC);
457
                                if (child < 0)
203✔
458
                                        return child;
459
                        } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
1,275,057✔
460
                                if (!path_extend(&done, first))
1,970✔
461
                                        return -ENOMEM;
462

463
                                break;
464
                        } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
1,273,087✔
465
                                if (!path_extend(&done, first, todo))
14,770✔
466
                                        return -ENOMEM;
467

468
                                exists = false;
469
                                break;
470
                        } else
471
                                return r;
472
                }
473

474
                /* ... and then check what it actually is. */
475
                if (fstat(child, &st_child) < 0)
17,554,305✔
UNCOV
476
                        return -errno;
×
477

478
                if (FLAGS_SET(flags, CHASE_SAFE) &&
17,587,130✔
479
                    unsafe_transition(&st, &st_child))
32,825✔
480
                        return log_unsafe_transition(fd, child, path, flags);
5✔
481

482
                if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
17,554,300✔
483
                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
7,424,612✔
UNCOV
484
                        return log_autofs_mount_point(child, path, flags);
×
485

486
                if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
17,554,300✔
487
                        _cleanup_free_ char *destination = NULL;
12✔
488

489
                        if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
400,946✔
490
                                return log_prohibited_symlink(child, flags);
7✔
491

492
                        /* This is a symlink, in this case read the destination. But let's make sure we
493
                         * don't follow symlinks without bounds. */
494
                        if (--max_follow <= 0)
400,939✔
495
                                return -ELOOP;
496

497
                        r = readlinkat_malloc(fd, first, &destination);
400,938✔
498
                        if (r < 0)
400,938✔
499
                                return r;
500
                        if (isempty(destination))
400,949✔
501
                                return -EINVAL;
502

503
                        if (path_is_absolute(destination)) {
400,937✔
504

505
                                /* An absolute destination. Start the loop from the beginning, but use the
506
                                 * root file descriptor as base. */
507

508
                                safe_close(fd);
8,057✔
509
                                fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
8,057✔
510
                                if (fd < 0)
8,057✔
511
                                        return fd;
512

513
                                if (fstat(fd, &st) < 0)
8,057✔
UNCOV
514
                                        return -errno;
×
515

516
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
8,062✔
517
                                    unsafe_transition(&st_child, &st))
5✔
518
                                        return log_unsafe_transition(child, fd, path, flags);
3✔
519

520
                                /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be
521
                                 * outside of the specified dir_fd. Let's make the result absolute. */
522
                                if (!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
8,054✔
523
                                        need_absolute = true;
524

525
                                r = free_and_strdup(&done, need_absolute ? "/" : NULL);
8,353✔
526
                                if (r < 0)
8,054✔
527
                                        return r;
528
                        }
529

530
                        /* Prefix what's left to do with what we just read, and start the loop again, but
531
                         * remain in the current directory. */
532
                        if (!path_extend(&destination, todo))
400,934✔
533
                                return -ENOMEM;
534

535
                        free_and_replace(buffer, destination);
400,934✔
536
                        todo = buffer;
400,934✔
537

538
                        if (FLAGS_SET(flags, CHASE_STEP))
400,934✔
539
                                goto chased_one;
8✔
540

541
                        continue;
400,926✔
542
                }
543

544
                /* If this is not a symlink, then let's just add the name we read to what we already verified. */
545
                if (!path_extend(&done, first))
17,153,354✔
546
                        return -ENOMEM;
547

548
                if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
19,163,793✔
549
                        break;
550

551
                /* And iterate again, but go one directory further down. */
552
                st = st_child;
17,144,923✔
553
                close_and_replace(fd, child);
17,144,923✔
554
        }
555

556
success:
2,010,439✔
557
        if (exists) {
2,018,811✔
558
                if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) {
2,004,041✔
559
                        r = stat_verify_directory(&st);
25,933✔
560
                        if (r < 0)
25,933✔
561
                                return r;
562
                }
563

564
                if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) {
2,004,041✔
565
                        r = stat_verify_regular(&st);
42✔
566
                        if (r < 0)
42✔
567
                                return r;
568
                }
569
        }
570

571
        if (ret_path) {
2,018,811✔
572
                if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
1,940,690✔
UNCOV
573
                        _cleanup_free_ char *f = NULL;
×
574

575
                        r = path_extract_filename(done, &f);
7,192✔
576
                        if (r < 0 && r != -EADDRNOTAVAIL)
7,192✔
UNCOV
577
                                return r;
×
578

579
                        /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
580
                        free_and_replace(done, f);
7,192✔
581
                }
582

583
                if (!done) {
1,940,690✔
584
                        assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME));
217✔
585
                        done = strdup(".");
217✔
586
                        if (!done)
217✔
587
                                return -ENOMEM;
588
                }
589

590
                if (append_trail_slash)
1,940,690✔
591
                        if (!strextend(&done, "/"))
4✔
592
                                return -ENOMEM;
593

594
                *ret_path = TAKE_PTR(done);
1,940,690✔
595
        }
596

597
        if (ret_fd) {
2,018,811✔
598
                if (exists) {
1,877,470✔
599
                        /* Return the O_PATH fd we currently are looking to the caller. It can translate it
600
                         * to a proper fd by opening /proc/self/fd/xyz. */
601
                        assert(fd >= 0);
1,876,651✔
602
                        *ret_fd = TAKE_FD(fd);
1,876,651✔
603
                } else
604
                        *ret_fd = -EBADF;
819✔
605
        }
606

607
        if (FLAGS_SET(flags, CHASE_STEP))
2,018,811✔
608
                return 1;
609

610
        return exists;
1,995,700✔
611

612
chased_one:
14✔
613
        if (ret_path) {
14✔
614
                const char *e;
14✔
615

616
                if (!done) {
14✔
617
                        assert(!need_absolute);
1✔
618
                        done = strdup(append_trail_slash ? "./" : ".");
1✔
619
                        if (!done)
1✔
UNCOV
620
                                return -ENOMEM;
×
621
                }
622

623
                /* todo may contain slashes at the beginning. */
624
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
14✔
625
                if (r < 0)
14✔
626
                        return r;
627
                if (r == 0)
14✔
UNCOV
628
                        *ret_path = TAKE_PTR(done);
×
629
                else {
630
                        char *c;
14✔
631

632
                        c = path_join(done, e);
14✔
633
                        if (!c)
14✔
634
                                return -ENOMEM;
635

636
                        *ret_path = c;
14✔
637
                }
638
        }
639

640
        return 0;
641
}
642

643
int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
2,551,869✔
644
        _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
2,551,869✔
645
        _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
5,103,738✔
646
        int r;
2,551,869✔
647

648
        assert(path);
2,551,869✔
649

650
        if (isempty(path))
5,103,737✔
651
                return -EINVAL;
652

653
        r = empty_or_root_harder_to_null(&root);
2,551,868✔
654
        if (r < 0)
2,551,868✔
655
                return r;
656

657
        /* A root directory of "/" or "" is identical to "/". */
658
        if (empty_or_root(root)) {
2,551,865✔
659
                root = "/";
2,475,975✔
660

661
                /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(),
662
                 * hence below is not necessary, but let's shortcut. */
663
                flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
2,475,975✔
664

665
        } else {
666
                r = path_make_absolute_cwd(root, &root_abs);
75,890✔
667
                if (r < 0)
75,890✔
668
                        return r;
669

670
                /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
671
                 * end. While we won't resolve the root path we still simplify it. */
672
                root = path_simplify(root_abs);
75,890✔
673

674
                assert(path_is_absolute(root));
75,890✔
675
                assert(!empty_or_root(root));
75,890✔
676

677
                if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
75,890✔
678
                        absolute = path_join(root, path);
14,069✔
679
                        if (!absolute)
14,069✔
680
                                return -ENOMEM;
681
                }
682

683
                flags |= CHASE_AT_RESOLVE_IN_ROOT;
75,890✔
684
        }
685

686
        if (!absolute) {
2,551,865✔
687
                r = path_make_absolute_cwd(path, &absolute);
2,537,796✔
688
                if (r < 0)
2,537,796✔
689
                        return r;
690
        }
691

692
        path = path_startswith(absolute, root);
2,551,865✔
693
        if (!path)
2,551,865✔
UNCOV
694
                return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG,
×
695
                                      SYNTHETIC_ERRNO(ECHRNG),
696
                                      "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
697
                                      absolute, root);
698

699
        if (empty_or_root(root))
2,551,865✔
700
                fd = XAT_FDROOT;
701
        else {
702
                fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
75,890✔
703
                if (fd < 0)
75,890✔
704
                        return -errno;
1✔
705
        }
706

707
        r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
3,053,666✔
708
        if (r < 0)
2,551,864✔
709
                return r;
710

711
        if (ret_path) {
1,834,930✔
712
                if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
1,766,518✔
713

714
                        /* When "root" points to the root directory, the result of chaseat() is always
715
                         * absolute, hence it is not necessary to prefix with the root. When "root" points to
716
                         * a non-root directory, the result path is always normalized and relative, hence
717
                         * we can simply call path_join() and not necessary to call path_simplify().
718
                         * As a special case, chaseat() may return "." or "./", which are normalized too,
719
                         * but we need to drop "." before merging with root. */
720

721
                        if (empty_or_root(root))
1,760,976✔
722
                                assert(path_is_absolute(p));
1,726,410✔
723
                        else {
724
                                char *q;
34,566✔
725

726
                                assert(!path_is_absolute(p));
34,566✔
727

728
                                q = path_join(root, p + STR_IN_SET(p, ".", "./"));
34,566✔
729
                                if (!q)
34,566✔
UNCOV
730
                                        return -ENOMEM;
×
731

732
                                free_and_replace(p, q);
34,566✔
733
                        }
734
                }
735

736
                *ret_path = TAKE_PTR(p);
1,766,518✔
737
        }
738

739
        if (ret_fd)
1,834,930✔
740
                *ret_fd = TAKE_FD(pfd);
1,702,334✔
741

742
        return r;
743
}
744

745
int chaseat_prefix_root(const char *path, const char *root, char **ret) {
127,210✔
746
        char *q;
127,210✔
747
        int r;
127,210✔
748

749
        assert(path);
127,210✔
750
        assert(ret);
127,210✔
751

752
        /* This is mostly for prefixing the result of chaseat(). */
753

754
        if (!path_is_absolute(path)) {
127,210✔
755
                _cleanup_free_ char *root_abs = NULL;
2,988✔
756

757
                r = empty_or_root_harder_to_null(&root);
2,988✔
758
                if (r < 0 && r != -ENOENT)
2,988✔
759
                        return r;
760

761
                /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
762
                if (empty_or_root(root))
2,988✔
763
                        return -EINVAL;
764

765
                r = path_make_absolute_cwd(root, &root_abs);
2,988✔
766
                if (r < 0)
2,988✔
767
                        return r;
768

769
                root = path_simplify(root_abs);
2,988✔
770

771
                q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
2,988✔
772
        } else
773
                q = strdup(path);
124,222✔
774
        if (!q)
127,210✔
775
                return -ENOMEM;
776

777
        *ret = q;
127,210✔
778
        return 0;
127,210✔
779
}
780

781
int chase_extract_filename(const char *path, const char *root, char **ret) {
2,361✔
782
        int r;
2,361✔
783

784
        /* This is similar to path_extract_filename(), but takes root directory.
785
         * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */
786

787
        assert(path);
2,361✔
788
        assert(ret);
2,361✔
789

790
        if (isempty(path))
2,361✔
791
                return -EINVAL;
792

793
        if (!path_is_absolute(path))
4,722✔
794
                return -EINVAL;
795

796
        r = empty_or_root_harder_to_null(&root);
2,361✔
797
        if (r < 0 && r != -ENOENT)
2,361✔
798
                return r;
799

800
        if (!empty_or_root(root)) {
2,361✔
801
                _cleanup_free_ char *root_abs = NULL;
1,210✔
802

803
                r = path_make_absolute_cwd(root, &root_abs);
1,210✔
804
                if (r < 0)
1,210✔
805
                        return r;
806

807
                path = path_startswith(path, root_abs);
1,210✔
808
                if (!path)
1,210✔
809
                        return -EINVAL;
810
        }
811

812
        if (!isempty(path)) {
2,361✔
813
                r = path_extract_filename(path, ret);
2,208✔
814
                if (r != -EADDRNOTAVAIL)
2,208✔
815
                        return r;
816
        }
817

818
        return strdup_to(ret, ".");
160✔
819
}
820

821
int chase_and_open(
42,988✔
822
                const char *path,
823
                const char *root,
824
                ChaseFlags chase_flags,
825
                int open_flags,
826
                char **ret_path) {
827

828
        _cleanup_close_ int path_fd = -EBADF;
42,988✔
829
        _cleanup_free_ char *p = NULL, *fname = NULL;
42,988✔
830
        int r;
42,988✔
831

832
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
42,988✔
833

834
        XOpenFlags xopen_flags = 0;
42,988✔
835
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY))
42,988✔
836
                open_flags |= O_DIRECTORY;
14✔
837
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR))
42,988✔
838
                xopen_flags |= XO_REGULAR;
499✔
839

840
        if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
42,988✔
841
                /* Shortcut this call if none of the special features of this call are requested */
842
                return xopenat_full(AT_FDCWD, path,
39,592✔
843
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
39,592✔
844
                                    xopen_flags,
845
                                    MODE_INVALID);
846

847
        r = chase(path, root, (CHASE_PARENT|chase_flags)&~CHASE_MUST_BE_REGULAR, &p, &path_fd);
3,396✔
848
        if (r < 0)
3,396✔
849
                return r;
850
        assert(path_fd >= 0);
2,356✔
851

852
        if (!FLAGS_SET(chase_flags, CHASE_PARENT) &&
2,356✔
853
            !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) {
2,355✔
854
                r = chase_extract_filename(p, root, &fname);
2,355✔
855
                if (r < 0)
2,355✔
856
                        return r;
857
        }
858

859
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, xopen_flags, MODE_INVALID);
2,357✔
860
        if (r < 0)
2,356✔
861
                return r;
862

863
        if (ret_path)
1,966✔
864
                *ret_path = TAKE_PTR(p);
774✔
865

866
        return r;
867
}
868

869
int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
36,819✔
870
        _cleanup_close_ int path_fd = -EBADF;
36,819✔
871
        _cleanup_free_ char *p = NULL;
36,819✔
872
        DIR *d;
36,819✔
873
        int r;
36,819✔
874

875
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_REGULAR)));
36,819✔
876
        assert(ret_dir);
36,819✔
877

878
        if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) {
36,819✔
879
                /* Shortcut this call if none of the special features of this call are requested */
880
                d = opendir(path);
×
UNCOV
881
                if (!d)
×
UNCOV
882
                        return -errno;
×
883

UNCOV
884
                *ret_dir = d;
×
UNCOV
885
                return 0;
×
886
        }
887

888
        r = chase(path, root, chase_flags|CHASE_MUST_BE_DIRECTORY, ret_path ? &p : NULL, &path_fd);
36,866✔
889
        if (r < 0)
36,819✔
890
                return r;
891
        assert(path_fd >= 0);
8,095✔
892

893
        d = xopendirat(path_fd, /* path= */ NULL, /* flags= */ 0);
8,095✔
894
        if (!d)
8,095✔
UNCOV
895
                return -errno;
×
896

897
        if (ret_path)
8,095✔
898
                *ret_path = TAKE_PTR(p);
8,048✔
899

900
        *ret_dir = d;
8,095✔
901
        return 0;
8,095✔
902
}
903

904
int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
530,629✔
905
        _cleanup_close_ int path_fd = -EBADF;
530,629✔
906
        _cleanup_free_ char *p = NULL;
530,629✔
907
        int r;
530,629✔
908

909
        assert(path);
530,629✔
910
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
530,629✔
911
        assert(ret_stat);
530,629✔
912

913
        if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
530,629✔
914
                /* Shortcut this call if none of the special features of this call are requested */
915
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
306,049✔
916
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
305,990✔
917

918
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
224,882✔
919
        if (r < 0)
224,639✔
920
                return r;
921
        assert(path_fd >= 0);
144,226✔
922

923
        if (fstat(path_fd, ret_stat) < 0)
144,226✔
UNCOV
924
                return -errno;
×
925

926
        if (ret_path)
144,226✔
927
                *ret_path = TAKE_PTR(p);
144,158✔
928

929
        return 0;
930
}
931

932
int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
6,765✔
933
        _cleanup_close_ int path_fd = -EBADF;
6,765✔
934
        _cleanup_free_ char *p = NULL;
6,765✔
935
        int r;
6,765✔
936

937
        assert(path);
6,765✔
938
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
6,765✔
939

940
        if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
6,765✔
941
                /* Shortcut this call if none of the special features of this call are requested */
942
                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
13,169✔
943
                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
6,586✔
944

945
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
348✔
946
        if (r < 0)
179✔
947
                return r;
948
        assert(path_fd >= 0);
46✔
949

950
        r = access_fd(path_fd, access_mode);
46✔
951
        if (r < 0)
46✔
952
                return r;
953

954
        if (ret_path)
46✔
955
                *ret_path = TAKE_PTR(p);
10✔
956

957
        return 0;
958
}
959

960
int chase_and_fopen_unlocked(
38,793✔
961
                const char *path,
962
                const char *root,
963
                ChaseFlags chase_flags,
964
                const char *open_flags,
965
                char **ret_path,
966
                FILE **ret_file) {
967

968
        _cleanup_free_ char *final_path = NULL;
38,793✔
969
        _cleanup_close_ int fd = -EBADF;
38,793✔
970
        int mode_flags, r;
38,793✔
971

972
        assert(path);
38,793✔
973
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT|CHASE_MUST_BE_DIRECTORY)));
38,793✔
974
        assert(open_flags);
38,793✔
975
        assert(ret_file);
38,793✔
976

977
        mode_flags = fopen_mode_to_flags(open_flags);
38,793✔
978
        if (mode_flags < 0)
38,793✔
979
                return mode_flags;
980

981
        fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
76,793✔
982
        if (fd < 0)
38,793✔
983
                return fd;
984

985
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
36,479✔
986
        if (r < 0)
36,479✔
987
                return r;
988

989
        if (ret_path)
36,479✔
990
                *ret_path = TAKE_PTR(final_path);
639✔
991

992
        return 0;
993
}
994

995
int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1✔
996
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
997
        _cleanup_close_ int fd = -EBADF;
1✔
998
        int r;
1✔
999

1000
        assert(path);
1✔
1001
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
1002

1003
        fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
1004
        if (fd < 0)
1✔
1005
                return fd;
1006

1007
        r = path_extract_filename(p, &fname);
1✔
1008
        if (r < 0)
1✔
1009
                return r;
1010

1011
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
UNCOV
1012
                return -errno;
×
1013

1014
        if (ret_path)
1✔
1015
                *ret_path = TAKE_PTR(p);
1✔
1016

1017
        return 0;
1018
}
1019

1020
int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
162✔
1021
        int pfd, r;
162✔
1022

1023
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
162✔
1024

1025
        r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
162✔
1026
        if (r < 0)
162✔
1027
                return r;
162✔
1028

1029
        return pfd;
161✔
1030
}
1031

1032
int chase_and_openat(
997✔
1033
                int dir_fd,
1034
                const char *path,
1035
                ChaseFlags chase_flags,
1036
                int open_flags,
1037
                char **ret_path) {
1038

1039
        _cleanup_close_ int path_fd = -EBADF;
997✔
1040
        _cleanup_free_ char *p = NULL, *fname = NULL;
997✔
1041
        int r;
997✔
1042

1043
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
997✔
1044

1045
        XOpenFlags xopen_flags = 0;
997✔
1046
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY))
997✔
UNCOV
1047
                open_flags |= O_DIRECTORY;
×
1048
        if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR))
997✔
1049
                xopen_flags |= XO_REGULAR;
1✔
1050

1051
        if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0)
997✔
1052
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
1053
                return xopenat_full(dir_fd, path,
×
1054
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
×
1055
                                    xopen_flags,
1056
                                    MODE_INVALID);
1057

1058
        r = chaseat(dir_fd, path, (chase_flags|CHASE_PARENT)&~CHASE_MUST_BE_REGULAR, &p, &path_fd);
997✔
1059
        if (r < 0)
997✔
1060
                return r;
1061

1062
        if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
993✔
1063
                r = path_extract_filename(p, &fname);
184✔
1064
                if (r < 0 && r != -EADDRNOTAVAIL)
184✔
1065
                        return r;
1066
        }
1067

1068
        r = xopenat_full(
2,979✔
1069
                        path_fd,
1070
                        strempty(fname),
1,802✔
1071
                        open_flags|O_NOFOLLOW,
1072
                        xopen_flags,
1073
                        MODE_INVALID);
1074
        if (r < 0)
993✔
1075
                return r;
1076

1077
        if (ret_path)
993✔
1078
                *ret_path = TAKE_PTR(p);
810✔
1079

1080
        return r;
1081
}
1082

1083
int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
580,714✔
1084
        _cleanup_close_ int path_fd = -EBADF;
580,714✔
1085
        _cleanup_free_ char *p = NULL;
580,714✔
1086
        DIR *d;
580,714✔
1087
        int r;
580,714✔
1088

1089
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_REGULAR)));
580,714✔
1090
        assert(ret_dir);
580,714✔
1091

1092
        if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) {
580,714✔
1093
                /* Shortcut this call if none of the special features of this call are requested */
1094
                d = opendir(path);
×
UNCOV
1095
                if (!d)
×
UNCOV
1096
                        return -errno;
×
1097

UNCOV
1098
                *ret_dir = d;
×
UNCOV
1099
                return 0;
×
1100
        }
1101

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

1107
        d = xopendirat(path_fd, /* path= */ NULL, /* flags= */ 0);
41,731✔
1108
        if (!d)
41,731✔
UNCOV
1109
                return -errno;
×
1110

1111
        if (ret_path)
41,731✔
1112
                *ret_path = TAKE_PTR(p);
41,731✔
1113

1114
        *ret_dir = d;
41,731✔
1115
        return 0;
41,731✔
1116
}
1117

1118
int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
1✔
1119
        _cleanup_close_ int path_fd = -EBADF;
1✔
1120
        _cleanup_free_ char *p = NULL;
1✔
1121
        int r;
1✔
1122

1123
        assert(path);
1✔
1124
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1✔
1125
        assert(ret_stat);
1✔
1126

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

1132
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
1✔
1133
        if (r < 0)
1✔
1134
                return r;
1135
        assert(path_fd >= 0);
1✔
1136

1137
        if (fstat(path_fd, ret_stat) < 0)
1✔
UNCOV
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_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
1✔
1147
        _cleanup_close_ int path_fd = -EBADF;
1✔
1148
        _cleanup_free_ char *p = NULL;
1✔
1149
        int r;
1✔
1150

1151
        assert(path);
1✔
1152
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1✔
1153

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

1159
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
1✔
1160
        if (r < 0)
1✔
1161
                return r;
1162
        assert(path_fd >= 0);
1✔
1163

1164
        r = access_fd(path_fd, access_mode);
1✔
1165
        if (r < 0)
1✔
1166
                return r;
1167

1168
        if (ret_path)
1✔
1169
                *ret_path = TAKE_PTR(p);
1✔
1170

1171
        return 0;
1172
}
1173

1174
int chase_and_fopenat_unlocked(
185✔
1175
                int dir_fd,
1176
                const char *path,
1177
                ChaseFlags chase_flags,
1178
                const char *open_flags,
1179
                char **ret_path,
1180
                FILE **ret_file) {
1181

1182
        _cleanup_free_ char *final_path = NULL;
185✔
1183
        _cleanup_close_ int fd = -EBADF;
185✔
1184
        int mode_flags, r;
185✔
1185

1186
        assert(path);
185✔
1187
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
185✔
1188
        assert(open_flags);
185✔
1189
        assert(ret_file);
185✔
1190

1191
        mode_flags = fopen_mode_to_flags(open_flags);
185✔
1192
        if (mode_flags < 0)
185✔
1193
                return mode_flags;
1194

1195
        fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
369✔
1196
        if (fd < 0)
185✔
1197
                return fd;
1198

1199
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
181✔
1200
        if (r < 0)
181✔
1201
                return r;
1202

1203
        if (ret_path)
181✔
1204
                *ret_path = TAKE_PTR(final_path);
1✔
1205

1206
        return 0;
1207
}
1208

1209
int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1✔
1210
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
1211
        _cleanup_close_ int fd = -EBADF;
1✔
1212
        int r;
1✔
1213

1214
        assert(path);
1✔
1215
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
1216

1217
        fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
1218
        if (fd < 0)
1✔
1219
                return fd;
1220

1221
        r = path_extract_filename(p, &fname);
1✔
1222
        if (r < 0)
1✔
1223
                return r;
1224

1225
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
UNCOV
1226
                return -errno;
×
1227

1228
        if (ret_path)
1✔
1229
                *ret_path = TAKE_PTR(p);
1✔
1230

1231
        return 0;
1232
}
1233

1234
int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
983✔
1235
        int pfd, r;
983✔
1236

1237
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
983✔
1238

1239
        r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
983✔
1240
        if (r < 0)
983✔
1241
                return r;
983✔
1242

1243
        return pfd;
983✔
1244
}
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