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

systemd / systemd / 14895667988

07 May 2025 08:57PM UTC coverage: 72.225% (-0.007%) from 72.232%
14895667988

push

github

yuwata
network: log_link_message_debug_errno() automatically append %m if necessary

Follow-up for d28746ef5.
Fixes CID#1609753.

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

20297 existing lines in 338 files now uncovered.

297407 of 411780 relevant lines covered (72.22%)

695716.85 hits per line

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

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

3
#include <linux/magic.h>
4

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

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

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

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

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

33
        if (!FLAGS_SET(flags, CHASE_WARN))
8✔
34
                return -ENOLINK;
35

36
        (void) fd_get_path(a, &n1);
5✔
37
        (void) fd_get_path(b, &n2);
5✔
38

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

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

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

UNCOV
52
        if (!FLAGS_SET(flags, CHASE_WARN))
×
53
                return -EREMOTE;
54

55
        (void) fd_get_path(fd, &n1);
×
56

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

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

65
        assert(fd >= 0);
6✔
66

67
        if (!FLAGS_SET(flags, CHASE_WARN))
6✔
68
                return -EREMCHG;
69

70
        (void) fd_get_path(fd, &n1);
3✔
71

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

77
static int chaseat_needs_absolute(int dir_fd, const char *path) {
3,061,862✔
78
        if (dir_fd < 0)
3,061,862✔
79
                return path_is_absolute(path);
15,166✔
80

81
        return dir_fd_is_root(dir_fd);
3,054,279✔
82
}
83

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

93
        assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
3,062,643✔
94
        assert(!FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR));
3,062,643✔
95
        assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
3,062,643✔
96
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
3,062,643✔
97

98
        /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
99
        if (FLAGS_SET(flags, CHASE_NONEXISTENT))
3,062,643✔
100
                assert(!ret_fd);
88,265✔
101

102
        if (FLAGS_SET(flags, CHASE_STEP))
3,062,643✔
103
                assert(!ret_fd);
26,870✔
104

105
        if (isempty(path))
3,063,765✔
106
                path = ".";
107

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

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

187
                r = dir_fd_is_root_or_cwd(dir_fd);
99,620✔
188
                if (r < 0)
92,169✔
189
                        return r;
190
                if (r > 0)
99,620✔
191
                        flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
16,538✔
192
        }
193

194
        if (!(flags &
3,062,643✔
195
              (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP|
196
               CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_PARENT)) &&
197
            !ret_path && ret_fd) {
1,922,886✔
198

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

205
                *ret_fd = r;
762✔
206
                return 0;
762✔
207
        }
208

209
        buffer = strdup(path);
3,061,862✔
210
        if (!buffer)
3,061,862✔
211
                return -ENOMEM;
212

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

220
        bool need_absolute = r;
3,061,862✔
221
        if (need_absolute) {
3,061,862✔
222
                done = strdup("/");
2,977,881✔
223
                if (!done)
2,977,881✔
224
                        return -ENOMEM;
225
        }
226

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

235
        if (fstat(fd, &st) < 0)
3,061,862✔
UNCOV
236
                return -errno;
×
237

238
        /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
239
         * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
240
         * whether to resolve symlinks in it or not. */
241
        if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
3,061,862✔
242
                root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
83,082✔
243
        else
244
                root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
2,978,780✔
245
        if (root_fd < 0)
3,061,862✔
UNCOV
246
                return -errno;
×
247

248
        if (ENDSWITH_SET(buffer, "/", "/.")) {
3,061,862✔
249
                flags |= CHASE_MUST_BE_DIRECTORY;
5,635✔
250
                if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
5,635✔
251
                        append_trail_slash = true;
10✔
252
        } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/.."))
3,056,227✔
253
                flags |= CHASE_MUST_BE_DIRECTORY;
1,131✔
254

255
        if (FLAGS_SET(flags, CHASE_PARENT))
3,061,862✔
256
                flags |= CHASE_MUST_BE_DIRECTORY;
36,135✔
257

258
        for (todo = buffer;;) {
3,061,862✔
259
                _cleanup_free_ char *first = NULL;
19,399,751✔
260
                _cleanup_close_ int child = -EBADF;
21,279,641✔
261
                struct stat st_child;
21,279,641✔
262
                const char *e;
21,279,641✔
263

264
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
21,279,641✔
265
                if (r < 0)
21,279,641✔
266
                        return r;
267
                if (r == 0) /* We reached the end. */
21,279,641✔
268
                        break;
269

270
                first = strndup(e, r);
19,449,950✔
271
                if (!first)
19,449,950✔
272
                        return -ENOMEM;
273

274
                /* Two dots? Then chop off the last bit of what we already found out. */
275
                if (streq(first, "..")) {
19,449,950✔
276
                        _cleanup_free_ char *parent = NULL;
1,045,636✔
277
                        _cleanup_close_ int fd_parent = -EBADF;
1,045,636✔
278
                        struct stat st_parent;
1,045,636✔
279

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

288
                        fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
1,045,619✔
289
                        if (fd_parent < 0)
1,045,619✔
UNCOV
290
                                return -errno;
×
291

292
                        if (fstat(fd_parent, &st_parent) < 0)
1,045,619✔
UNCOV
293
                                return -errno;
×
294

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

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

UNCOV
326
                                if (!path_is_valid(done))
×
327
                                        return -EINVAL;
328

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

335
                        if (FLAGS_SET(flags, CHASE_STEP))
1,045,603✔
336
                                goto chased_one;
6✔
337

338
                        if (FLAGS_SET(flags, CHASE_SAFE) &&
1,045,597✔
UNCOV
339
                            unsafe_transition(&st, &st_parent))
×
UNCOV
340
                                return log_unsafe_transition(fd, fd_parent, path, flags);
×
341

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

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

353
                                if (fstat(fd_grandparent, &st_grandparent) < 0)
2✔
UNCOV
354
                                        return -errno;
×
355

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

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

365
                        /* update fd and stat */
366
                        st = st_parent;
1,045,595✔
367
                        close_and_replace(fd, fd_parent);
1,045,595✔
368
                        continue;
1,045,595✔
369
                }
370

371
                /* Otherwise let's see what this is. */
372
                child = r = RET_NERRNO(openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH));
18,404,314✔
373
                if (r < 0) {
1,202,509✔
374
                        if (r != -ENOENT)
1,202,509✔
375
                                return r;
376

377
                        if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */
1,578,321✔
378
                                return r;
379

380
                        if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) {
1,203,084✔
381
                                child = xopenat_full(fd,
189✔
382
                                                     first,
383
                                                     O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC,
384
                                                     /* xopen_flags = */ 0,
385
                                                     0755);
386
                                if (child < 0)
189✔
387
                                        return child;
388
                        } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
1,202,316✔
389
                                if (!path_extend(&done, first))
5,525✔
390
                                        return -ENOMEM;
391

392
                                break;
393
                        } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
1,196,791✔
394
                                if (!path_extend(&done, first, todo))
14,839✔
395
                                        return -ENOMEM;
396

397
                                exists = false;
398
                                break;
399
                        } else
400
                                return r;
401
                }
402

403
                if (fstat(child, &st_child) < 0)
17,201,994✔
UNCOV
404
                        return -errno;
×
405

406
                if (FLAGS_SET(flags, CHASE_SAFE) &&
17,237,177✔
407
                    unsafe_transition(&st, &st_child))
35,183✔
408
                        return log_unsafe_transition(fd, child, path, flags);
5✔
409

410
                if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
17,201,989✔
411
                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
7,493,412✔
UNCOV
412
                        return log_autofs_mount_point(child, path, flags);
×
413

414
                if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
17,201,989✔
415
                        _cleanup_free_ char *destination = NULL;
11✔
416

417
                        if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
461,728✔
418
                                return log_prohibited_symlink(child, flags);
6✔
419

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

425
                        r = readlinkat_malloc(fd, first, &destination);
461,721✔
426
                        if (r < 0)
461,721✔
427
                                return r;
428
                        if (isempty(destination))
461,731✔
429
                                return -EINVAL;
430

431
                        if (path_is_absolute(destination)) {
461,720✔
432

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

436
                                safe_close(fd);
1,743✔
437
                                fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
1,743✔
438
                                if (fd < 0)
1,743✔
439
                                        return fd;
440

441
                                if (fstat(fd, &st) < 0)
1,743✔
UNCOV
442
                                        return -errno;
×
443

444
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
1,748✔
445
                                    unsafe_transition(&st_child, &st))
5✔
446
                                        return log_unsafe_transition(child, fd, path, flags);
3✔
447

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

453
                                r = free_and_strdup(&done, need_absolute ? "/" : NULL);
1,968✔
454
                                if (r < 0)
1,740✔
455
                                        return r;
456
                        }
457

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

463
                        free_and_replace(buffer, destination);
461,717✔
464
                        todo = buffer;
461,717✔
465

466
                        if (FLAGS_SET(flags, CHASE_STEP))
461,717✔
467
                                goto chased_one;
8✔
468

469
                        continue;
461,709✔
470
                }
471

472
                /* If this is not a symlink, then let's just add the name we read to what we already verified. */
473
                if (!path_extend(&done, first))
16,740,261✔
474
                        return -ENOMEM;
475

476
                if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
18,620,137✔
477
                        break;
478

479
                /* And iterate again, but go one directory further down. */
480
                st = st_child;
16,710,442✔
481
                close_and_replace(fd, child);
16,710,442✔
482
        }
483

484
        if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) {
1,879,876✔
485
                r = stat_verify_directory(&st);
40,909✔
486
                if (r < 0)
40,909✔
487
                        return r;
488
        }
489

490
        if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) {
1,879,876✔
491
                r = stat_verify_regular(&st);
42✔
492
                if (r < 0)
42✔
493
                        return r;
494
        }
495

496
        if (ret_path) {
1,879,876✔
497
                if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
1,810,949✔
UNCOV
498
                        _cleanup_free_ char *f = NULL;
×
499

500
                        r = path_extract_filename(done, &f);
32,611✔
501
                        if (r < 0 && r != -EADDRNOTAVAIL)
32,611✔
UNCOV
502
                                return r;
×
503

504
                        /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
505
                        free_and_replace(done, f);
32,611✔
506
                }
507

508
                if (!done) {
1,810,949✔
509
                        assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME));
252✔
510
                        done = strdup(".");
252✔
511
                        if (!done)
252✔
512
                                return -ENOMEM;
513
                }
514

515
                if (append_trail_slash)
1,810,949✔
516
                        if (!strextend(&done, "/"))
10✔
517
                                return -ENOMEM;
518

519
                *ret_path = TAKE_PTR(done);
1,810,949✔
520
        }
521

522
        if (ret_fd) {
1,879,876✔
523
                /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
524
                 * proper fd by opening /proc/self/fd/xyz. */
525

526
                assert(fd >= 0);
1,745,894✔
527
                *ret_fd = TAKE_FD(fd);
1,745,894✔
528
        }
529

530
        if (FLAGS_SET(flags, CHASE_STEP))
1,879,876✔
531
                return 1;
532

533
        return exists;
1,853,020✔
534

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

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

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

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

559
                        *ret_path = c;
14✔
560
                }
561
        }
562

563
        return 0;
564
}
565

566
static int empty_or_root_to_null(const char **path) {
3,045,023✔
567
        int r;
3,045,023✔
568

569
        assert(path);
3,045,023✔
570

571
        /* This nullifies the input path when the path is empty or points to "/". */
572

573
        if (empty_or_root(*path)) {
3,045,023✔
574
                *path = NULL;
2,963,190✔
575
                return 0;
2,963,190✔
576
        }
577

578
        r = path_is_root(*path);
81,833✔
579
        if (r < 0)
81,833✔
580
                return r;
581
        if (r > 0)
81,822✔
582
                *path = NULL;
5✔
583

584
        return 0;
585
}
586

587
int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
3,040,596✔
588
        _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
3,040,596✔
589
        _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
6,081,192✔
590
        int r;
3,040,596✔
591

592
        assert(path);
3,040,596✔
593

594
        if (isempty(path))
6,081,191✔
595
                return -EINVAL;
596

597
        r = empty_or_root_to_null(&root);
3,040,595✔
598
        if (r < 0)
3,040,595✔
599
                return r;
600

601
        /* A root directory of "/" or "" is identical to "/". */
602
        if (empty_or_root(root)) {
3,040,588✔
603
                root = "/";
2,962,235✔
604

605
                /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(),
606
                 * hence below is not necessary, but let's shortcut. */
607
                flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
2,962,235✔
608

609
        } else {
610
                r = path_make_absolute_cwd(root, &root_abs);
78,353✔
611
                if (r < 0)
78,353✔
612
                        return r;
613

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

618
                assert(path_is_absolute(root));
78,353✔
619
                assert(!empty_or_root(root));
78,353✔
620

621
                if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
78,353✔
622
                        absolute = path_join(root, path);
12,584✔
623
                        if (!absolute)
12,584✔
624
                                return -ENOMEM;
625
                }
626

627
                flags |= CHASE_AT_RESOLVE_IN_ROOT;
78,353✔
628
        }
629

630
        if (!absolute) {
3,040,588✔
631
                r = path_make_absolute_cwd(path, &absolute);
3,028,004✔
632
                if (r < 0)
3,028,004✔
633
                        return r;
634
        }
635

636
        path = path_startswith(absolute, root);
3,040,588✔
637
        if (!path)
3,040,588✔
UNCOV
638
                return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG,
×
639
                                      SYNTHETIC_ERRNO(ECHRNG),
640
                                      "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
641
                                      absolute, root);
642

643
        fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
3,040,588✔
644
        if (fd < 0)
3,040,588✔
645
                return -errno;
1✔
646

647
        r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
3,390,239✔
648
        if (r < 0)
3,040,587✔
649
                return r;
650

651
        if (ret_path) {
1,861,240✔
652
                if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
1,799,340✔
653

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

661
                        if (empty_or_root(root))
1,768,254✔
662
                                assert(path_is_absolute(p));
1,730,680✔
663
                        else {
664
                                char *q;
37,574✔
665

666
                                assert(!path_is_absolute(p));
37,574✔
667

668
                                q = path_join(root, p + STR_IN_SET(p, ".", "./"));
37,574✔
669
                                if (!q)
37,574✔
UNCOV
670
                                        return -ENOMEM;
×
671

672
                                free_and_replace(p, q);
37,574✔
673
                        }
674
                }
675

676
                *ret_path = TAKE_PTR(p);
1,799,340✔
677
        }
678

679
        if (ret_fd)
1,861,240✔
680
                *ret_fd = TAKE_FD(pfd);
1,734,364✔
681

682
        return r;
683
}
684

685
int chaseat_prefix_root(const char *path, const char *root, char **ret) {
3,371✔
686
        char *q;
3,371✔
687
        int r;
3,371✔
688

689
        assert(path);
3,371✔
690
        assert(ret);
3,371✔
691

692
        /* This is mostly for prefixing the result of chaseat(). */
693

694
        if (!path_is_absolute(path)) {
3,371✔
695
                _cleanup_free_ char *root_abs = NULL;
2,436✔
696

697
                r = empty_or_root_to_null(&root);
2,436✔
698
                if (r < 0 && r != -ENOENT)
2,436✔
699
                        return r;
700

701
                /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
702
                if (empty_or_root(root))
2,436✔
703
                        return -EINVAL;
704

705
                r = path_make_absolute_cwd(root, &root_abs);
2,436✔
706
                if (r < 0)
2,436✔
707
                        return r;
708

709
                root = path_simplify(root_abs);
2,436✔
710

711
                q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
2,436✔
712
        } else
713
                q = strdup(path);
935✔
714
        if (!q)
3,371✔
715
                return -ENOMEM;
716

717
        *ret = q;
3,371✔
718
        return 0;
3,371✔
719
}
720

721
int chase_extract_filename(const char *path, const char *root, char **ret) {
1,992✔
722
        int r;
1,992✔
723

724
        /* This is similar to path_extract_filename(), but takes root directory.
725
         * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */
726

727
        assert(path);
1,992✔
728
        assert(ret);
1,992✔
729

730
        if (isempty(path))
3,984✔
731
                return -EINVAL;
732

733
        if (!path_is_absolute(path))
1,992✔
734
                return -EINVAL;
735

736
        r = empty_or_root_to_null(&root);
1,992✔
737
        if (r < 0 && r != -ENOENT)
1,992✔
738
                return r;
739

740
        if (!empty_or_root(root)) {
1,992✔
741
                _cleanup_free_ char *root_abs = NULL;
1,032✔
742

743
                r = path_make_absolute_cwd(root, &root_abs);
1,032✔
744
                if (r < 0)
1,032✔
745
                        return r;
746

747
                path = path_startswith(path, root_abs);
1,032✔
748
                if (!path)
1,032✔
749
                        return -EINVAL;
750
        }
751

752
        if (!isempty(path)) {
1,992✔
753
                r = path_extract_filename(path, ret);
1,848✔
754
                if (r != -EADDRNOTAVAIL)
1,848✔
755
                        return r;
756
        }
757

758
        return strdup_to(ret, ".");
151✔
759
}
760

761
int chase_and_open(
50,127✔
762
                const char *path,
763
                const char *root,
764
                ChaseFlags chase_flags,
765
                int open_flags,
766
                char **ret_path) {
767

768
        _cleanup_close_ int path_fd = -EBADF;
50,127✔
769
        _cleanup_free_ char *p = NULL, *fname = NULL;
50,127✔
770
        int r;
50,127✔
771

772
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
50,127✔
773

774
        if (empty_or_root(root) && !ret_path &&
50,127✔
775
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
48,121✔
776
                /* Shortcut this call if none of the special features of this call are requested */
777
                return xopenat_full(AT_FDCWD, path,
47,748✔
778
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
47,748✔
779
                                    /* xopen_flags = */ 0,
780
                                    MODE_INVALID);
781

782
        r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
2,379✔
783
        if (r < 0)
2,379✔
784
                return r;
785
        assert(path_fd >= 0);
1,987✔
786

787
        if (!FLAGS_SET(chase_flags, CHASE_PARENT) &&
1,987✔
788
            !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) {
1,986✔
789
                r = chase_extract_filename(p, root, &fname);
1,986✔
790
                if (r < 0)
1,986✔
791
                        return r;
792
        }
793

794
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, MODE_INVALID);
1,988✔
795
        if (r < 0)
1,987✔
796
                return r;
797

798
        if (ret_path)
1,625✔
799
                *ret_path = TAKE_PTR(p);
720✔
800

801
        return r;
802
}
803

804
int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
652,777✔
805
        _cleanup_close_ int path_fd = -EBADF;
652,777✔
806
        _cleanup_free_ char *p = NULL;
652,777✔
807
        DIR *d;
652,777✔
808
        int r;
652,777✔
809

810
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
652,777✔
811
        assert(ret_dir);
652,777✔
812

813
        if (empty_or_root(root) && !ret_path &&
652,777✔
814
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
44✔
815
                /* Shortcut this call if none of the special features of this call are requested */
816
                d = opendir(path);
×
UNCOV
817
                if (!d)
×
818
                        return -errno;
×
819

UNCOV
820
                *ret_dir = d;
×
UNCOV
821
                return 0;
×
822
        }
823

824
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
652,821✔
825
        if (r < 0)
652,777✔
826
                return r;
827
        assert(path_fd >= 0);
46,924✔
828

829
        d = xopendirat(path_fd, ".", O_NOFOLLOW);
46,924✔
830
        if (!d)
46,924✔
UNCOV
831
                return -errno;
×
832

833
        if (ret_path)
46,924✔
834
                *ret_path = TAKE_PTR(p);
46,880✔
835

836
        *ret_dir = d;
46,924✔
837
        return 0;
46,924✔
838
}
839

840
int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
601,704✔
841
        _cleanup_close_ int path_fd = -EBADF;
601,704✔
842
        _cleanup_free_ char *p = NULL;
601,704✔
843
        int r;
601,704✔
844

845
        assert(path);
601,704✔
846
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
601,704✔
847
        assert(ret_stat);
601,704✔
848

849
        if (empty_or_root(root) && !ret_path &&
601,704✔
850
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
292,732✔
851
                /* Shortcut this call if none of the special features of this call are requested */
852
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
292,789✔
853
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
292,732✔
854

855
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
309,191✔
856
        if (r < 0)
308,972✔
857
                return r;
858
        assert(path_fd >= 0);
178,062✔
859

860
        if (fstat(path_fd, ret_stat) < 0)
178,062✔
UNCOV
861
                return -errno;
×
862

863
        if (ret_path)
178,062✔
864
                *ret_path = TAKE_PTR(p);
177,994✔
865

866
        return 0;
867
}
868

869
int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
34✔
870
        _cleanup_close_ int path_fd = -EBADF;
34✔
871
        _cleanup_free_ char *p = NULL;
34✔
872
        int r;
34✔
873

874
        assert(path);
34✔
875
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
34✔
876

877
        if (empty_or_root(root) && !ret_path &&
34✔
878
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
879
                /* Shortcut this call if none of the special features of this call are requested */
UNCOV
880
                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
×
UNCOV
881
                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
882

883
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
58✔
884
        if (r < 0)
34✔
885
                return r;
886
        assert(path_fd >= 0);
34✔
887

888
        r = access_fd(path_fd, access_mode);
34✔
889
        if (r < 0)
34✔
890
                return r;
891

892
        if (ret_path)
34✔
893
                *ret_path = TAKE_PTR(p);
10✔
894

895
        return 0;
896
}
897

898
int chase_and_fopen_unlocked(
46,252✔
899
                const char *path,
900
                const char *root,
901
                ChaseFlags chase_flags,
902
                const char *open_flags,
903
                char **ret_path,
904
                FILE **ret_file) {
905

906
        _cleanup_free_ char *final_path = NULL;
46,252✔
907
        _cleanup_close_ int fd = -EBADF;
46,252✔
908
        int mode_flags, r;
46,252✔
909

910
        assert(path);
46,252✔
911
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
46,252✔
912
        assert(open_flags);
46,252✔
913
        assert(ret_file);
46,252✔
914

915
        mode_flags = fopen_mode_to_flags(open_flags);
46,252✔
916
        if (mode_flags < 0)
46,252✔
917
                return mode_flags;
918

919
        fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
91,754✔
920
        if (fd < 0)
46,252✔
921
                return fd;
922

923
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
45,444✔
924
        if (r < 0)
45,444✔
925
                return r;
926

927
        if (ret_path)
45,444✔
928
                *ret_path = TAKE_PTR(final_path);
595✔
929

930
        return 0;
931
}
932

933
int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1✔
934
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
935
        _cleanup_close_ int fd = -EBADF;
1✔
936
        int r;
1✔
937

938
        assert(path);
1✔
939
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
940

941
        fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
942
        if (fd < 0)
1✔
943
                return fd;
944

945
        r = path_extract_filename(p, &fname);
1✔
946
        if (r < 0)
1✔
947
                return r;
948

949
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
UNCOV
950
                return -errno;
×
951

952
        if (ret_path)
1✔
953
                *ret_path = TAKE_PTR(p);
1✔
954

955
        return 0;
956
}
957

958
int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
26,593✔
959
        int pfd, r;
26,593✔
960

961
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
26,593✔
962

963
        r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
26,593✔
964
        if (r < 0)
26,593✔
965
                return r;
26,593✔
966

967
        return pfd;
26,400✔
968
}
969

970
int chase_and_openat(
947✔
971
                int dir_fd,
972
                const char *path,
973
                ChaseFlags chase_flags,
974
                int open_flags,
975
                char **ret_path) {
976

977
        _cleanup_close_ int path_fd = -EBADF;
947✔
978
        _cleanup_free_ char *p = NULL, *fname = NULL;
947✔
979
        int r;
947✔
980

981
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
947✔
982

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

991
        r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
947✔
992
        if (r < 0)
947✔
993
                return r;
994

995
        if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
943✔
996
                r = path_extract_filename(p, &fname);
171✔
997
                if (r < 0 && r != -EADDRNOTAVAIL)
171✔
998
                        return r;
999
        }
1000

1001
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags= */ 0, MODE_INVALID);
1,715✔
1002
        if (r < 0)
943✔
1003
                return r;
1004

1005
        if (ret_path)
943✔
1006
                *ret_path = TAKE_PTR(p);
773✔
1007

1008
        return r;
1009
}
1010

1011
int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
932✔
1012
        _cleanup_close_ int path_fd = -EBADF;
932✔
1013
        _cleanup_free_ char *p = NULL;
932✔
1014
        DIR *d;
932✔
1015
        int r;
932✔
1016

1017
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
932✔
1018
        assert(ret_dir);
932✔
1019

1020
        if (dir_fd == AT_FDCWD && !ret_path &&
932✔
1021
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
×
1022
                /* Shortcut this call if none of the special features of this call are requested */
1023
                d = opendir(path);
×
UNCOV
1024
                if (!d)
×
1025
                        return -errno;
×
1026

UNCOV
1027
                *ret_dir = d;
×
UNCOV
1028
                return 0;
×
1029
        }
1030

1031
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
932✔
1032
        if (r < 0)
932✔
1033
                return r;
1034
        assert(path_fd >= 0);
546✔
1035

1036
        d = xopendirat(path_fd, ".", O_NOFOLLOW);
546✔
1037
        if (!d)
546✔
UNCOV
1038
                return -errno;
×
1039

1040
        if (ret_path)
546✔
1041
                *ret_path = TAKE_PTR(p);
546✔
1042

1043
        *ret_dir = d;
546✔
1044
        return 0;
546✔
1045
}
1046

1047
int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
1✔
1048
        _cleanup_close_ int path_fd = -EBADF;
1✔
1049
        _cleanup_free_ char *p = NULL;
1✔
1050
        int r;
1✔
1051

1052
        assert(path);
1✔
1053
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1✔
1054
        assert(ret_stat);
1✔
1055

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

1062
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
1✔
1063
        if (r < 0)
1✔
1064
                return r;
1065
        assert(path_fd >= 0);
1✔
1066

1067
        if (fstat(path_fd, ret_stat) < 0)
1✔
UNCOV
1068
                return -errno;
×
1069

1070
        if (ret_path)
1✔
1071
                *ret_path = TAKE_PTR(p);
1✔
1072

1073
        return 0;
1074
}
1075

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

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

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

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

1095
        r = access_fd(path_fd, access_mode);
1✔
1096
        if (r < 0)
1✔
1097
                return r;
1098

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

1102
        return 0;
1103
}
1104

1105
int chase_and_fopenat_unlocked(
172✔
1106
                int dir_fd,
1107
                const char *path,
1108
                ChaseFlags chase_flags,
1109
                const char *open_flags,
1110
                char **ret_path,
1111
                FILE **ret_file) {
1112

1113
        _cleanup_free_ char *final_path = NULL;
172✔
1114
        _cleanup_close_ int fd = -EBADF;
172✔
1115
        int mode_flags, r;
172✔
1116

1117
        assert(path);
172✔
1118
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
172✔
1119
        assert(open_flags);
172✔
1120
        assert(ret_file);
172✔
1121

1122
        mode_flags = fopen_mode_to_flags(open_flags);
172✔
1123
        if (mode_flags < 0)
172✔
1124
                return mode_flags;
1125

1126
        fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
343✔
1127
        if (fd < 0)
172✔
1128
                return fd;
1129

1130
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
168✔
1131
        if (r < 0)
168✔
1132
                return r;
1133

1134
        if (ret_path)
168✔
1135
                *ret_path = TAKE_PTR(final_path);
1✔
1136

1137
        return 0;
1138
}
1139

1140
int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1✔
1141
        _cleanup_free_ char *p = NULL, *fname = NULL;
1✔
1142
        _cleanup_close_ int fd = -EBADF;
1✔
1143
        int r;
1✔
1144

1145
        assert(path);
1✔
1146
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
1147

1148
        fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1✔
1149
        if (fd < 0)
1✔
1150
                return fd;
1151

1152
        r = path_extract_filename(p, &fname);
1✔
1153
        if (r < 0)
1✔
1154
                return r;
1155

1156
        if (unlinkat(fd, fname, unlink_flags) < 0)
1✔
UNCOV
1157
                return -errno;
×
1158

1159
        if (ret_path)
1✔
1160
                *ret_path = TAKE_PTR(p);
1✔
1161

1162
        return 0;
1163
}
1164

1165
int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
934✔
1166
        int pfd, r;
934✔
1167

1168
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
934✔
1169

1170
        r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
934✔
1171
        if (r < 0)
934✔
1172
                return r;
934✔
1173

1174
        return pfd;
934✔
1175
}
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