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

systemd / systemd / 12877533250

20 Jan 2025 11:16PM UTC coverage: 0.117%. Remained the same
12877533250

push

github

web-flow
pidfd: cache our own pidfd inode id, and use it at various places (#36060)

This is split out of and preparation for #35224, but makes a ton of
sense on its own

0 of 95 new or added lines in 10 files covered. (0.0%)

4667 existing lines in 34 files now uncovered.

478 of 408040 relevant lines covered (0.12%)

1.45 hits per line

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

0.0
/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 "fd-util.h"
8
#include "fileio.h"
9
#include "fs-util.h"
10
#include "glyph-util.h"
11
#include "log.h"
12
#include "path-util.h"
13
#include "string-util.h"
14
#include "user-util.h"
15

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

21
        if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */
×
22
                return false;
23

24
        return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
×
25
}
26

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

31
        if (!FLAGS_SET(flags, CHASE_WARN))
×
32
                return -ENOLINK;
33

34
        (void) fd_get_path(a, &n1);
×
35
        (void) fd_get_path(b, &n2);
×
36

37
        if (fstat(a, &st) == 0)
×
38
                user_a = uid_to_name(st.st_uid);
×
39
        if (fstat(b, &st) == 0)
×
40
                user_b = uid_to_name(st.st_uid);
×
41

42
        return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK),
×
43
                                 "Detected unsafe path transition %s (owned by %s) %s %s (owned by %s) during canonicalization of %s.",
44
                                 strna(n1), strna(user_a), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path);
45
}
46

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

50
        if (!FLAGS_SET(flags, CHASE_WARN))
×
51
                return -EREMOTE;
52

53
        (void) fd_get_path(fd, &n1);
×
54

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

60
static int log_prohibited_symlink(int fd, ChaseFlags flags) {
×
61
        _cleanup_free_ char *n1 = NULL;
×
62

63
        assert(fd >= 0);
×
64

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

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

70
        return log_warning_errno(SYNTHETIC_ERRNO(EREMCHG),
×
71
                                 "Detected symlink where not symlink is allowed at %s, refusing.",
72
                                 strna(n1));
73
}
74

75
static int chaseat_needs_absolute(int dir_fd, const char *path) {
×
76
        if (dir_fd < 0)
×
77
                return path_is_absolute(path);
×
78

79
        return dir_fd_is_root(dir_fd);
×
80
}
81

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

91
        assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
×
92
        assert(!FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR));
×
93
        assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
×
94
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
×
95

96
        /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
UNCOV
97
        if (FLAGS_SET(flags, CHASE_NONEXISTENT))
×
98
                assert(!ret_fd);
×
99

UNCOV
100
        if (FLAGS_SET(flags, CHASE_STEP))
×
101
                assert(!ret_fd);
×
102

UNCOV
103
        if (isempty(path))
×
104
                path = ".";
105

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

UNCOV
181
        if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
×
182
                /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
183
                 * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
184

UNCOV
185
                r = dir_fd_is_root_or_cwd(dir_fd);
×
186
                if (r < 0)
×
187
                        return r;
UNCOV
188
                if (r > 0)
×
189
                        flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
×
190
        }
191

UNCOV
192
        if (!(flags &
×
193
              (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP|
194
               CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_PARENT)) &&
UNCOV
195
            !ret_path && ret_fd) {
×
196

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

UNCOV
203
                *ret_fd = r;
×
204
                return 0;
×
205
        }
206

UNCOV
207
        buffer = strdup(path);
×
208
        if (!buffer)
×
209
                return -ENOMEM;
210

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

UNCOV
218
        bool need_absolute = r;
×
219
        if (need_absolute) {
×
220
                done = strdup("/");
×
221
                if (!done)
×
222
                        return -ENOMEM;
223
        }
224

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

UNCOV
233
        if (fstat(fd, &st) < 0)
×
234
                return -errno;
×
235

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

UNCOV
246
        if (ENDSWITH_SET(buffer, "/", "/.")) {
×
247
                flags |= CHASE_MUST_BE_DIRECTORY;
×
248
                if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
×
UNCOV
249
                        append_trail_slash = true;
×
250
        } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/.."))
×
251
                flags |= CHASE_MUST_BE_DIRECTORY;
×
252

253
        if (FLAGS_SET(flags, CHASE_PARENT))
×
254
                flags |= CHASE_MUST_BE_DIRECTORY;
×
255

256
        for (todo = buffer;;) {
×
257
                _cleanup_free_ char *first = NULL;
×
UNCOV
258
                _cleanup_close_ int child = -EBADF;
×
259
                struct stat st_child;
×
260
                const char *e;
×
261

UNCOV
262
                r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
×
UNCOV
263
                if (r < 0)
×
264
                        return r;
UNCOV
265
                if (r == 0) /* We reached the end. */
×
266
                        break;
267

UNCOV
268
                first = strndup(e, r);
×
UNCOV
269
                if (!first)
×
270
                        return -ENOMEM;
271

272
                /* Two dots? Then chop off the last bit of what we already found out. */
273
                if (streq(first, "..")) {
×
274
                        _cleanup_free_ char *parent = NULL;
×
UNCOV
275
                        _cleanup_close_ int fd_parent = -EBADF;
×
UNCOV
276
                        struct stat st_parent;
×
277

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

286
                        fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
×
UNCOV
287
                        if (fd_parent < 0)
×
288
                                return -errno;
×
289

UNCOV
290
                        if (fstat(fd_parent, &st_parent) < 0)
×
UNCOV
291
                                return -errno;
×
292

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

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

UNCOV
324
                                if (!path_is_valid(done))
×
325
                                        return -EINVAL;
326

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

UNCOV
333
                        if (FLAGS_SET(flags, CHASE_STEP))
×
334
                                goto chased_one;
×
335

336
                        if (FLAGS_SET(flags, CHASE_SAFE) &&
×
UNCOV
337
                            unsafe_transition(&st, &st_parent))
×
UNCOV
338
                                return log_unsafe_transition(fd, fd_parent, path, flags);
×
339

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

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

UNCOV
351
                                if (fstat(fd_grandparent, &st_grandparent) < 0)
×
352
                                        return -errno;
×
353

354
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
×
UNCOV
355
                                    unsafe_transition(&st_parent, &st_grandparent))
×
356
                                        return log_unsafe_transition(fd_parent, fd_grandparent, path, flags);
×
357

358
                                st = st_grandparent;
×
UNCOV
359
                                close_and_replace(fd, fd_grandparent);
×
UNCOV
360
                                break;
×
361
                        }
362

363
                        /* update fd and stat */
364
                        st = st_parent;
×
UNCOV
365
                        close_and_replace(fd, fd_parent);
×
UNCOV
366
                        continue;
×
367
                }
368

369
                /* Otherwise let's see what this is. */
370
                child = r = RET_NERRNO(openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH));
×
UNCOV
371
                if (r < 0) {
×
UNCOV
372
                        if (r != -ENOENT)
×
373
                                return r;
374

UNCOV
375
                        if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */
×
376
                                return r;
377

UNCOV
378
                        if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) {
×
UNCOV
379
                                child = xopenat_full(fd,
×
380
                                                     first,
381
                                                     O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC,
382
                                                     /* xopen_flags = */ 0,
383
                                                     0755);
384
                                if (child < 0)
×
385
                                        return child;
UNCOV
386
                        } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
×
UNCOV
387
                                if (!path_extend(&done, first))
×
388
                                        return -ENOMEM;
389

390
                                break;
UNCOV
391
                        } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
×
UNCOV
392
                                if (!path_extend(&done, first, todo))
×
393
                                        return -ENOMEM;
394

395
                                exists = false;
396
                                break;
397
                        } else
398
                                return r;
399
                }
400

UNCOV
401
                if (fstat(child, &st_child) < 0)
×
402
                        return -errno;
×
403

404
                if (FLAGS_SET(flags, CHASE_SAFE) &&
×
UNCOV
405
                    unsafe_transition(&st, &st_child))
×
406
                        return log_unsafe_transition(fd, child, path, flags);
×
407

408
                if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
×
UNCOV
409
                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
×
410
                        return log_autofs_mount_point(child, path, flags);
×
411

UNCOV
412
                if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
×
413
                        _cleanup_free_ char *destination = NULL;
×
414

UNCOV
415
                        if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
×
UNCOV
416
                                return log_prohibited_symlink(child, flags);
×
417

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

UNCOV
423
                        r = readlinkat_malloc(fd, first, &destination);
×
424
                        if (r < 0)
×
425
                                return r;
UNCOV
426
                        if (isempty(destination))
×
427
                                return -EINVAL;
428

UNCOV
429
                        if (path_is_absolute(destination)) {
×
430

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

434
                                safe_close(fd);
×
UNCOV
435
                                fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
×
UNCOV
436
                                if (fd < 0)
×
437
                                        return fd;
438

UNCOV
439
                                if (fstat(fd, &st) < 0)
×
440
                                        return -errno;
×
441

442
                                if (FLAGS_SET(flags, CHASE_SAFE) &&
×
UNCOV
443
                                    unsafe_transition(&st_child, &st))
×
UNCOV
444
                                        return log_unsafe_transition(child, fd, path, flags);
×
445

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

UNCOV
451
                                r = free_and_strdup(&done, need_absolute ? "/" : NULL);
×
UNCOV
452
                                if (r < 0)
×
453
                                        return r;
454
                        }
455

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

UNCOV
461
                        free_and_replace(buffer, destination);
×
462
                        todo = buffer;
×
463

UNCOV
464
                        if (FLAGS_SET(flags, CHASE_STEP))
×
465
                                goto chased_one;
×
466

UNCOV
467
                        continue;
×
468
                }
469

470
                /* If this is not a symlink, then let's just add the name we read to what we already verified. */
UNCOV
471
                if (!path_extend(&done, first))
×
472
                        return -ENOMEM;
473

UNCOV
474
                if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
×
475
                        break;
476

477
                /* And iterate again, but go one directory further down. */
UNCOV
478
                st = st_child;
×
UNCOV
479
                close_and_replace(fd, child);
×
480
        }
481

482
        if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) {
×
UNCOV
483
                r = stat_verify_directory(&st);
×
UNCOV
484
                if (r < 0)
×
485
                        return r;
486
        }
487

488
        if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) {
×
UNCOV
489
                r = stat_verify_regular(&st);
×
490
                if (r < 0)
×
491
                        return r;
492
        }
493

UNCOV
494
        if (ret_path) {
×
495
                if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
×
UNCOV
496
                        _cleanup_free_ char *f = NULL;
×
497

498
                        r = path_extract_filename(done, &f);
×
499
                        if (r < 0 && r != -EADDRNOTAVAIL)
×
500
                                return r;
×
501

502
                        /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
UNCOV
503
                        free_and_replace(done, f);
×
504
                }
505

UNCOV
506
                if (!done) {
×
UNCOV
507
                        assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME));
×
508
                        done = strdup(".");
×
UNCOV
509
                        if (!done)
×
510
                                return -ENOMEM;
511
                }
512

513
                if (append_trail_slash)
×
UNCOV
514
                        if (!strextend(&done, "/"))
×
515
                                return -ENOMEM;
516

UNCOV
517
                *ret_path = TAKE_PTR(done);
×
518
        }
519

UNCOV
520
        if (ret_fd) {
×
521
                /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
522
                 * proper fd by opening /proc/self/fd/xyz. */
523

UNCOV
524
                assert(fd >= 0);
×
525
                *ret_fd = TAKE_FD(fd);
×
526
        }
527

528
        if (FLAGS_SET(flags, CHASE_STEP))
×
529
                return 1;
530

UNCOV
531
        return exists;
×
532

533
chased_one:
×
534
        if (ret_path) {
×
UNCOV
535
                const char *e;
×
536

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

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

553
                        c = path_join(done, e);
×
UNCOV
554
                        if (!c)
×
555
                                return -ENOMEM;
556

UNCOV
557
                        *ret_path = c;
×
558
                }
559
        }
560

561
        return 0;
562
}
563

564
static int empty_or_root_to_null(const char **path) {
×
565
        int r;
×
566

567
        assert(path);
×
568

569
        /* This nullifies the input path when the path is empty or points to "/". */
570

UNCOV
571
        if (empty_or_root(*path)) {
×
UNCOV
572
                *path = NULL;
×
573
                return 0;
×
574
        }
575

576
        r = path_is_root(*path);
×
UNCOV
577
        if (r < 0)
×
578
                return r;
UNCOV
579
        if (r > 0)
×
580
                *path = NULL;
×
581

582
        return 0;
583
}
584

UNCOV
585
int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
×
UNCOV
586
        _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
×
UNCOV
587
        _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
×
588
        int r;
×
589

UNCOV
590
        assert(path);
×
591

UNCOV
592
        if (isempty(path))
×
593
                return -EINVAL;
594

UNCOV
595
        r = empty_or_root_to_null(&root);
×
596
        if (r < 0)
×
597
                return r;
598

599
        /* A root directory of "/" or "" is identical to "/". */
UNCOV
600
        if (empty_or_root(root)) {
×
UNCOV
601
                root = "/";
×
602

603
                /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(),
604
                 * hence below is not necessary, but let's shortcut. */
605
                flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
×
606

607
        } else {
608
                r = path_make_absolute_cwd(root, &root_abs);
×
609
                if (r < 0)
×
610
                        return r;
611

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

616
                assert(path_is_absolute(root));
×
617
                assert(!empty_or_root(root));
×
618

UNCOV
619
                if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
×
UNCOV
620
                        absolute = path_join(root, path);
×
UNCOV
621
                        if (!absolute)
×
622
                                return -ENOMEM;
623
                }
624

UNCOV
625
                flags |= CHASE_AT_RESOLVE_IN_ROOT;
×
626
        }
627

UNCOV
628
        if (!absolute) {
×
629
                r = path_make_absolute_cwd(path, &absolute);
×
630
                if (r < 0)
×
631
                        return r;
632
        }
633

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

UNCOV
641
        fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
×
UNCOV
642
        if (fd < 0)
×
UNCOV
643
                return -errno;
×
644

UNCOV
645
        r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
×
UNCOV
646
        if (r < 0)
×
647
                return r;
648

UNCOV
649
        if (ret_path) {
×
650
                if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
×
651

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

UNCOV
659
                        if (empty_or_root(root))
×
UNCOV
660
                                assert(path_is_absolute(p));
×
661
                        else {
662
                                char *q;
×
663

UNCOV
664
                                assert(!path_is_absolute(p));
×
665

666
                                q = path_join(root, p + STR_IN_SET(p, ".", "./"));
×
UNCOV
667
                                if (!q)
×
UNCOV
668
                                        return -ENOMEM;
×
669

UNCOV
670
                                free_and_replace(p, q);
×
671
                        }
672
                }
673

UNCOV
674
                *ret_path = TAKE_PTR(p);
×
675
        }
676

UNCOV
677
        if (ret_fd)
×
UNCOV
678
                *ret_fd = TAKE_FD(pfd);
×
679

680
        return r;
681
}
682

683
int chaseat_prefix_root(const char *path, const char *root, char **ret) {
×
684
        char *q;
×
UNCOV
685
        int r;
×
686

UNCOV
687
        assert(path);
×
688
        assert(ret);
×
689

690
        /* This is mostly for prefixing the result of chaseat(). */
691

692
        if (!path_is_absolute(path)) {
×
UNCOV
693
                _cleanup_free_ char *root_abs = NULL;
×
694

695
                r = empty_or_root_to_null(&root);
×
UNCOV
696
                if (r < 0 && r != -ENOENT)
×
697
                        return r;
698

699
                /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
700
                if (empty_or_root(root))
×
701
                        return -EINVAL;
702

703
                r = path_make_absolute_cwd(root, &root_abs);
×
704
                if (r < 0)
×
705
                        return r;
706

707
                root = path_simplify(root_abs);
×
708

UNCOV
709
                q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
×
710
        } else
UNCOV
711
                q = strdup(path);
×
UNCOV
712
        if (!q)
×
713
                return -ENOMEM;
714

UNCOV
715
        *ret = q;
×
716
        return 0;
×
717
}
718

719
int chase_extract_filename(const char *path, const char *root, char **ret) {
×
UNCOV
720
        int r;
×
721

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

UNCOV
725
        assert(path);
×
726
        assert(ret);
×
727

UNCOV
728
        if (isempty(path))
×
729
                return -EINVAL;
730

UNCOV
731
        if (!path_is_absolute(path))
×
732
                return -EINVAL;
733

734
        r = empty_or_root_to_null(&root);
×
UNCOV
735
        if (r < 0 && r != -ENOENT)
×
736
                return r;
737

738
        if (!empty_or_root(root)) {
×
739
                _cleanup_free_ char *root_abs = NULL;
×
740

UNCOV
741
                r = path_make_absolute_cwd(root, &root_abs);
×
UNCOV
742
                if (r < 0)
×
743
                        return r;
744

UNCOV
745
                path = path_startswith(path, root_abs);
×
UNCOV
746
                if (!path)
×
747
                        return -EINVAL;
748
        }
749

UNCOV
750
        if (!isempty(path)) {
×
UNCOV
751
                r = path_extract_filename(path, ret);
×
UNCOV
752
                if (r != -EADDRNOTAVAIL)
×
753
                        return r;
754
        }
755

756
        return strdup_to(ret, ".");
×
757
}
758

UNCOV
759
int chase_and_open(
×
760
                const char *path,
761
                const char *root,
762
                ChaseFlags chase_flags,
763
                int open_flags,
764
                char **ret_path) {
765

UNCOV
766
        _cleanup_close_ int path_fd = -EBADF;
×
UNCOV
767
        _cleanup_free_ char *p = NULL, *fname = NULL;
×
768
        int r;
×
769

UNCOV
770
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
×
771

UNCOV
772
        if (empty_or_root(root) && !ret_path &&
×
773
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
774
                /* Shortcut this call if none of the special features of this call are requested */
775
                return xopenat_full(AT_FDCWD, path,
×
776
                                    open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
×
777
                                    /* xopen_flags = */ 0,
778
                                    MODE_INVALID);
779

780
        r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
×
781
        if (r < 0)
×
782
                return r;
UNCOV
783
        assert(path_fd >= 0);
×
784

785
        if (!FLAGS_SET(chase_flags, CHASE_PARENT) &&
×
786
            !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) {
UNCOV
787
                r = chase_extract_filename(p, root, &fname);
×
UNCOV
788
                if (r < 0)
×
789
                        return r;
790
        }
791

792
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, MODE_INVALID);
×
793
        if (r < 0)
×
794
                return r;
795

796
        if (ret_path)
×
797
                *ret_path = TAKE_PTR(p);
×
798

799
        return r;
800
}
801

802
int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
×
803
        _cleanup_close_ int path_fd = -EBADF;
×
804
        _cleanup_free_ char *p = NULL;
×
UNCOV
805
        DIR *d;
×
806
        int r;
×
807

UNCOV
808
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
×
UNCOV
809
        assert(ret_dir);
×
810

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

UNCOV
818
                *ret_dir = d;
×
819
                return 0;
×
820
        }
821

822
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
×
823
        if (r < 0)
×
824
                return r;
UNCOV
825
        assert(path_fd >= 0);
×
826

827
        d = xopendirat(path_fd, ".", O_NOFOLLOW);
×
828
        if (!d)
×
829
                return -errno;
×
830

831
        if (ret_path)
×
832
                *ret_path = TAKE_PTR(p);
×
833

UNCOV
834
        *ret_dir = d;
×
835
        return 0;
×
836
}
837

838
int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
×
839
        _cleanup_close_ int path_fd = -EBADF;
×
UNCOV
840
        _cleanup_free_ char *p = NULL;
×
841
        int r;
×
842

UNCOV
843
        assert(path);
×
844
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
×
UNCOV
845
        assert(ret_stat);
×
846

847
        if (empty_or_root(root) && !ret_path &&
×
UNCOV
848
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
×
849
                /* Shortcut this call if none of the special features of this call are requested */
850
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
×
UNCOV
851
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
×
852

UNCOV
853
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
×
UNCOV
854
        if (r < 0)
×
855
                return r;
856
        assert(path_fd >= 0);
×
857

858
        if (fstat(path_fd, ret_stat) < 0)
×
UNCOV
859
                return -errno;
×
860

861
        if (ret_path)
×
UNCOV
862
                *ret_path = TAKE_PTR(p);
×
863

864
        return 0;
865
}
866

867
int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
×
UNCOV
868
        _cleanup_close_ int path_fd = -EBADF;
×
869
        _cleanup_free_ char *p = NULL;
×
870
        int r;
×
871

872
        assert(path);
×
UNCOV
873
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
×
874

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

UNCOV
881
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
×
UNCOV
882
        if (r < 0)
×
883
                return r;
884
        assert(path_fd >= 0);
×
885

UNCOV
886
        r = access_fd(path_fd, access_mode);
×
UNCOV
887
        if (r < 0)
×
888
                return r;
889

UNCOV
890
        if (ret_path)
×
UNCOV
891
                *ret_path = TAKE_PTR(p);
×
892

893
        return 0;
894
}
895

896
int chase_and_fopen_unlocked(
×
897
                const char *path,
898
                const char *root,
899
                ChaseFlags chase_flags,
900
                const char *open_flags,
901
                char **ret_path,
902
                FILE **ret_file) {
903

UNCOV
904
        _cleanup_free_ char *final_path = NULL;
×
905
        _cleanup_close_ int fd = -EBADF;
×
906
        int mode_flags, r;
×
907

UNCOV
908
        assert(path);
×
909
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
×
910
        assert(open_flags);
×
UNCOV
911
        assert(ret_file);
×
912

913
        mode_flags = fopen_mode_to_flags(open_flags);
×
914
        if (mode_flags < 0)
×
915
                return mode_flags;
916

UNCOV
917
        fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
×
UNCOV
918
        if (fd < 0)
×
919
                return fd;
920

921
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
×
922
        if (r < 0)
×
923
                return r;
924

925
        if (ret_path)
×
UNCOV
926
                *ret_path = TAKE_PTR(final_path);
×
927

928
        return 0;
929
}
930

931
int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
×
932
        _cleanup_free_ char *p = NULL, *fname = NULL;
×
UNCOV
933
        _cleanup_close_ int fd = -EBADF;
×
UNCOV
934
        int r;
×
935

936
        assert(path);
×
UNCOV
937
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
×
938

939
        fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
×
UNCOV
940
        if (fd < 0)
×
941
                return fd;
942

UNCOV
943
        r = path_extract_filename(p, &fname);
×
944
        if (r < 0)
×
945
                return r;
946

947
        if (unlinkat(fd, fname, unlink_flags) < 0)
×
UNCOV
948
                return -errno;
×
949

950
        if (ret_path)
×
951
                *ret_path = TAKE_PTR(p);
×
952

953
        return 0;
954
}
955

956
int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
×
UNCOV
957
        int pfd, r;
×
958

UNCOV
959
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
×
960

UNCOV
961
        r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
×
UNCOV
962
        if (r < 0)
×
963
                return r;
×
964

965
        return pfd;
×
966
}
967

UNCOV
968
int chase_and_openat(
×
969
                int dir_fd,
970
                const char *path,
971
                ChaseFlags chase_flags,
972
                int open_flags,
973
                char **ret_path) {
974

UNCOV
975
        _cleanup_close_ int path_fd = -EBADF;
×
UNCOV
976
        _cleanup_free_ char *p = NULL, *fname = NULL;
×
977
        int r;
×
978

UNCOV
979
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
×
980

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

UNCOV
989
        r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
×
UNCOV
990
        if (r < 0)
×
991
                return r;
992

UNCOV
993
        if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
×
UNCOV
994
                r = path_extract_filename(p, &fname);
×
UNCOV
995
                if (r < 0 && r != -EADDRNOTAVAIL)
×
996
                        return r;
997
        }
998

999
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags= */ 0, MODE_INVALID);
×
1000
        if (r < 0)
×
1001
                return r;
1002

1003
        if (ret_path)
×
1004
                *ret_path = TAKE_PTR(p);
×
1005

1006
        return r;
1007
}
1008

1009
int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
×
1010
        _cleanup_close_ int path_fd = -EBADF;
×
1011
        _cleanup_free_ char *p = NULL;
×
UNCOV
1012
        DIR *d;
×
1013
        int r;
×
1014

UNCOV
1015
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
×
UNCOV
1016
        assert(ret_dir);
×
1017

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

UNCOV
1025
                *ret_dir = d;
×
1026
                return 0;
×
1027
        }
1028

1029
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
×
1030
        if (r < 0)
×
1031
                return r;
UNCOV
1032
        assert(path_fd >= 0);
×
1033

1034
        d = xopendirat(path_fd, ".", O_NOFOLLOW);
×
1035
        if (!d)
×
1036
                return -errno;
×
1037

1038
        if (ret_path)
×
1039
                *ret_path = TAKE_PTR(p);
×
1040

UNCOV
1041
        *ret_dir = d;
×
1042
        return 0;
×
1043
}
1044

1045
int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
×
1046
        _cleanup_close_ int path_fd = -EBADF;
×
UNCOV
1047
        _cleanup_free_ char *p = NULL;
×
1048
        int r;
×
1049

UNCOV
1050
        assert(path);
×
1051
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
×
UNCOV
1052
        assert(ret_stat);
×
1053

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

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

1065
        if (fstat(path_fd, ret_stat) < 0)
×
UNCOV
1066
                return -errno;
×
1067

1068
        if (ret_path)
×
UNCOV
1069
                *ret_path = TAKE_PTR(p);
×
1070

1071
        return 0;
1072
}
1073

1074
int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
×
UNCOV
1075
        _cleanup_close_ int path_fd = -EBADF;
×
1076
        _cleanup_free_ char *p = NULL;
×
1077
        int r;
×
1078

1079
        assert(path);
×
UNCOV
1080
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
×
1081

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

UNCOV
1088
        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
×
UNCOV
1089
        if (r < 0)
×
1090
                return r;
1091
        assert(path_fd >= 0);
×
1092

UNCOV
1093
        r = access_fd(path_fd, access_mode);
×
UNCOV
1094
        if (r < 0)
×
1095
                return r;
1096

UNCOV
1097
        if (ret_path)
×
UNCOV
1098
                *ret_path = TAKE_PTR(p);
×
1099

1100
        return 0;
1101
}
1102

1103
int chase_and_fopenat_unlocked(
×
1104
                int dir_fd,
1105
                const char *path,
1106
                ChaseFlags chase_flags,
1107
                const char *open_flags,
1108
                char **ret_path,
1109
                FILE **ret_file) {
1110

UNCOV
1111
        _cleanup_free_ char *final_path = NULL;
×
1112
        _cleanup_close_ int fd = -EBADF;
×
1113
        int mode_flags, r;
×
1114

UNCOV
1115
        assert(path);
×
1116
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
×
1117
        assert(open_flags);
×
UNCOV
1118
        assert(ret_file);
×
1119

1120
        mode_flags = fopen_mode_to_flags(open_flags);
×
1121
        if (mode_flags < 0)
×
1122
                return mode_flags;
1123

UNCOV
1124
        fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
×
UNCOV
1125
        if (fd < 0)
×
1126
                return fd;
1127

1128
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
×
1129
        if (r < 0)
×
1130
                return r;
1131

1132
        if (ret_path)
×
UNCOV
1133
                *ret_path = TAKE_PTR(final_path);
×
1134

1135
        return 0;
1136
}
1137

1138
int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
×
1139
        _cleanup_free_ char *p = NULL, *fname = NULL;
×
UNCOV
1140
        _cleanup_close_ int fd = -EBADF;
×
UNCOV
1141
        int r;
×
1142

1143
        assert(path);
×
UNCOV
1144
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
×
1145

1146
        fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
×
UNCOV
1147
        if (fd < 0)
×
1148
                return fd;
1149

UNCOV
1150
        r = path_extract_filename(p, &fname);
×
1151
        if (r < 0)
×
1152
                return r;
1153

1154
        if (unlinkat(fd, fname, unlink_flags) < 0)
×
UNCOV
1155
                return -errno;
×
1156

1157
        if (ret_path)
×
1158
                *ret_path = TAKE_PTR(p);
×
1159

1160
        return 0;
1161
}
1162

UNCOV
1163
int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
×
UNCOV
1164
        int pfd, r;
×
1165

UNCOV
1166
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
×
1167

UNCOV
1168
        r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
×
UNCOV
1169
        if (r < 0)
×
UNCOV
1170
                return r;
×
1171

UNCOV
1172
        return pfd;
×
1173
}
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