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

systemd / systemd / 15263807472

26 May 2025 08:53PM UTC coverage: 72.046% (-0.002%) from 72.048%
15263807472

push

github

yuwata
src/core/manager.c: log preset activity on first boot

This gives us a little more information about what units were enabled
or disabled on that first boot and will be useful for OS developers
tracking down the source of unit state.

An example with this enabled looks like:

```
NET: Registered PF_VSOCK protocol family
systemd[1]: Applying preset policy.
systemd[1]: Unit /etc/systemd/system/dnsmasq.service is masked, ignoring.
systemd[1]: Unit /etc/systemd/system/systemd-repart.service is masked, ignoring.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket'.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir.mount' → '/etc/systemd/system/var-mnt-workdir.mount'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir\x2dtmp.mount' → '/etc/systemd/system/var-mnt-workdir\x2dtmp.mount'.
systemd[1]: Created symlink '/etc/systemd/system/afterburn-sshkeys.target.requires/afterburn-sshkeys@core.service' → '/usr/lib/systemd/system/afterburn-sshkeys@.service'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket' → '/usr/lib/systemd/system/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket' → '/usr/lib/systemd/system/systemd-resolved-monitor.socket'.
systemd[1]: Populated /etc with preset unit settings.
```

Considering it only happens on first boot and not on every boot I think
the extra information is worth the extra verbosity in the logs just for
that boot.

5 of 6 new or added lines in 1 file covered. (83.33%)

5463 existing lines in 165 files now uncovered.

299151 of 415222 relevant lines covered (72.05%)

702386.45 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
#include <unistd.h>
5

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

83
        return dir_fd_is_root(dir_fd);
3,121,236✔
84
}
85

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

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

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

104
        if (FLAGS_SET(flags, CHASE_STEP))
3,129,649✔
105
                assert(!ret_fd);
26,344✔
106

107
        if (isempty(path))
3,130,771✔
108
                path = ".";
109

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

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

189
                r = dir_fd_is_root_or_cwd(dir_fd);
99,363✔
190
                if (r < 0)
91,871✔
191
                        return r;
192
                if (r > 0)
99,363✔
193
                        flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
16,607✔
194
        }
195

196
        if (!(flags &
3,129,649✔
197
              (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP|
198
               CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_PARENT)) &&
199
            !ret_path && ret_fd) {
1,972,231✔
200

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

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

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

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

222
        bool need_absolute = r;
3,128,817✔
223
        if (need_absolute) {
3,128,817✔
224
                done = strdup("/");
3,045,162✔
225
                if (!done)
3,045,162✔
226
                        return -ENOMEM;
227
        }
228

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

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

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

250
        if (ENDSWITH_SET(buffer, "/", "/.")) {
3,128,817✔
251
                flags |= CHASE_MUST_BE_DIRECTORY;
6,683✔
252
                if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
6,683✔
253
                        append_trail_slash = true;
10✔
254
        } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/.."))
3,122,134✔
255
                flags |= CHASE_MUST_BE_DIRECTORY;
1,131✔
256

257
        if (FLAGS_SET(flags, CHASE_PARENT))
3,128,817✔
258
                flags |= CHASE_MUST_BE_DIRECTORY;
36,235✔
259

260
        for (todo = buffer;;) {
3,128,817✔
261
                _cleanup_free_ char *first = NULL;
19,711,755✔
262
                _cleanup_close_ int child = -EBADF;
21,626,781✔
263
                struct stat st_child;
21,626,781✔
264
                const char *e;
21,626,781✔
265

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

272
                first = strndup(e, r);
19,762,094✔
273
                if (!first)
19,762,094✔
274
                        return -ENOMEM;
275

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

405
                if (fstat(child, &st_child) < 0)
17,466,289✔
UNCOV
406
                        return -errno;
×
407

408
                if (FLAGS_SET(flags, CHASE_SAFE) &&
17,501,472✔
409
                    unsafe_transition(&st, &st_child))
35,183✔
410
                        return log_unsafe_transition(fd, child, path, flags);
5✔
411

412
                if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
17,466,284✔
413
                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
7,602,580✔
UNCOV
414
                        return log_autofs_mount_point(child, path, flags);
×
415

416
                if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
17,466,284✔
417
                        _cleanup_free_ char *destination = NULL;
10✔
418

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

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

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

433
                        if (path_is_absolute(destination)) {
469,102✔
434

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

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

443
                                if (fstat(fd, &st) < 0)
1,753✔
UNCOV
444
                                        return -errno;
×
445

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

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

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

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

465
                        free_and_replace(buffer, destination);
469,099✔
466
                        todo = buffer;
469,099✔
467

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

471
                        continue;
469,091✔
472
                }
473

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

478
                if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
18,912,187✔
479
                        break;
480

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

486
        if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) {
1,915,012✔
487
                r = stat_verify_directory(&st);
42,026✔
488
                if (r < 0)
42,026✔
489
                        return r;
490
        }
491

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

498
        if (ret_path) {
1,915,012✔
499
                if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
1,846,074✔
UNCOV
500
                        _cleanup_free_ char *f = NULL;
×
501

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

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

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

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

521
                *ret_path = TAKE_PTR(done);
1,846,074✔
522
        }
523

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

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

532
        if (FLAGS_SET(flags, CHASE_STEP))
1,915,012✔
533
                return 1;
534

535
        return exists;
1,888,682✔
536

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

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

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

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

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

565
        return 0;
566
}
567

568
static int empty_or_root_to_null(const char **path) {
3,111,899✔
569
        int r;
3,111,899✔
570

571
        assert(path);
3,111,899✔
572

573
        /* This nullifies the input path when the path is empty or points to "/". */
574

575
        if (empty_or_root(*path)) {
3,111,899✔
576
                *path = NULL;
3,030,433✔
577
                return 0;
3,030,433✔
578
        }
579

580
        r = path_is_root(*path);
81,466✔
581
        if (r < 0)
81,466✔
582
                return r;
583
        if (r > 0)
81,455✔
584
                *path = NULL;
5✔
585

586
        return 0;
587
}
588

589
int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
3,107,457✔
590
        _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
3,107,457✔
591
        _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
6,214,914✔
592
        int r;
3,107,457✔
593

594
        assert(path);
3,107,457✔
595

596
        if (isempty(path))
6,214,913✔
597
                return -EINVAL;
598

599
        r = empty_or_root_to_null(&root);
3,107,456✔
600
        if (r < 0)
3,107,456✔
601
                return r;
602

603
        /* A root directory of "/" or "" is identical to "/". */
604
        if (empty_or_root(root)) {
3,107,449✔
605
                root = "/";
3,029,500✔
606

607
                /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(),
608
                 * hence below is not necessary, but let's shortcut. */
609
                flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
3,029,500✔
610

611
        } else {
612
                r = path_make_absolute_cwd(root, &root_abs);
77,949✔
613
                if (r < 0)
77,949✔
614
                        return r;
615

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

620
                assert(path_is_absolute(root));
77,949✔
621
                assert(!empty_or_root(root));
77,949✔
622

623
                if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
77,949✔
624
                        absolute = path_join(root, path);
12,705✔
625
                        if (!absolute)
12,705✔
626
                                return -ENOMEM;
627
                }
628

629
                flags |= CHASE_AT_RESOLVE_IN_ROOT;
77,949✔
630
        }
631

632
        if (!absolute) {
3,107,449✔
633
                r = path_make_absolute_cwd(path, &absolute);
3,094,744✔
634
                if (r < 0)
3,094,744✔
635
                        return r;
636
        }
637

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

645
        fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
3,107,449✔
646
        if (fd < 0)
3,107,449✔
647
                return -errno;
1✔
648

649
        r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
3,494,751✔
650
        if (r < 0)
3,107,448✔
651
                return r;
652

653
        if (ret_path) {
1,896,332✔
654
                if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
1,834,422✔
655

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

663
                        if (empty_or_root(root))
1,803,232✔
664
                                assert(path_is_absolute(p));
1,766,144✔
665
                        else {
666
                                char *q;
37,088✔
667

668
                                assert(!path_is_absolute(p));
37,088✔
669

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

674
                                free_and_replace(p, q);
37,088✔
675
                        }
676
                }
677

678
                *ret_path = TAKE_PTR(p);
1,834,422✔
679
        }
680

681
        if (ret_fd)
1,896,332✔
682
                *ret_fd = TAKE_FD(pfd);
1,763,611✔
683

684
        return r;
685
}
686

687
int chaseat_prefix_root(const char *path, const char *root, char **ret) {
3,397✔
688
        char *q;
3,397✔
689
        int r;
3,397✔
690

691
        assert(path);
3,397✔
692
        assert(ret);
3,397✔
693

694
        /* This is mostly for prefixing the result of chaseat(). */
695

696
        if (!path_is_absolute(path)) {
3,397✔
697
                _cleanup_free_ char *root_abs = NULL;
2,460✔
698

699
                r = empty_or_root_to_null(&root);
2,460✔
700
                if (r < 0 && r != -ENOENT)
2,460✔
701
                        return r;
702

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

707
                r = path_make_absolute_cwd(root, &root_abs);
2,460✔
708
                if (r < 0)
2,460✔
709
                        return r;
710

711
                root = path_simplify(root_abs);
2,460✔
712

713
                q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
2,460✔
714
        } else
715
                q = strdup(path);
937✔
716
        if (!q)
3,397✔
717
                return -ENOMEM;
718

719
        *ret = q;
3,397✔
720
        return 0;
3,397✔
721
}
722

723
int chase_extract_filename(const char *path, const char *root, char **ret) {
1,983✔
724
        int r;
1,983✔
725

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

729
        assert(path);
1,983✔
730
        assert(ret);
1,983✔
731

732
        if (isempty(path))
1,983✔
733
                return -EINVAL;
734

735
        if (!path_is_absolute(path))
3,966✔
736
                return -EINVAL;
737

738
        r = empty_or_root_to_null(&root);
1,983✔
739
        if (r < 0 && r != -ENOENT)
1,983✔
740
                return r;
741

742
        if (!empty_or_root(root)) {
1,983✔
743
                _cleanup_free_ char *root_abs = NULL;
1,045✔
744

745
                r = path_make_absolute_cwd(root, &root_abs);
1,045✔
746
                if (r < 0)
1,045✔
747
                        return r;
748

749
                path = path_startswith(path, root_abs);
1,045✔
750
                if (!path)
1,045✔
751
                        return -EINVAL;
752
        }
753

754
        if (!isempty(path)) {
1,983✔
755
                r = path_extract_filename(path, ret);
1,839✔
756
                if (r != -EADDRNOTAVAIL)
1,839✔
757
                        return r;
758
        }
759

760
        return strdup_to(ret, ".");
151✔
761
}
762

763
int chase_and_open(
50,593✔
764
                const char *path,
765
                const char *root,
766
                ChaseFlags chase_flags,
767
                int open_flags,
768
                char **ret_path) {
769

770
        _cleanup_close_ int path_fd = -EBADF;
50,593✔
771
        _cleanup_free_ char *p = NULL, *fname = NULL;
50,593✔
772
        int r;
50,593✔
773

774
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
50,593✔
775

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

784
        r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
2,375✔
785
        if (r < 0)
2,375✔
786
                return r;
787
        assert(path_fd >= 0);
1,978✔
788

789
        if (!FLAGS_SET(chase_flags, CHASE_PARENT) &&
1,978✔
790
            !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) {
1,977✔
791
                r = chase_extract_filename(p, root, &fname);
1,977✔
792
                if (r < 0)
1,977✔
793
                        return r;
794
        }
795

796
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, MODE_INVALID);
1,979✔
797
        if (r < 0)
1,978✔
798
                return r;
799

800
        if (ret_path)
1,612✔
801
                *ret_path = TAKE_PTR(p);
724✔
802

803
        return r;
804
}
805

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

812
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
650,792✔
813
        assert(ret_dir);
650,792✔
814

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

UNCOV
822
                *ret_dir = d;
×
UNCOV
823
                return 0;
×
824
        }
825

826
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
650,836✔
827
        if (r < 0)
650,792✔
828
                return r;
829
        assert(path_fd >= 0);
45,728✔
830

831
        d = xopendirat(path_fd, ".", O_NOFOLLOW);
45,728✔
832
        if (!d)
45,728✔
UNCOV
833
                return -errno;
×
834

835
        if (ret_path)
45,728✔
836
                *ret_path = TAKE_PTR(p);
45,684✔
837

838
        *ret_dir = d;
45,728✔
839
        return 0;
45,728✔
840
}
841

842
int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
603,800✔
843
        _cleanup_close_ int path_fd = -EBADF;
603,800✔
844
        _cleanup_free_ char *p = NULL;
603,800✔
845
        int r;
603,800✔
846

847
        assert(path);
603,800✔
848
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
603,800✔
849
        assert(ret_stat);
603,800✔
850

851
        if (empty_or_root(root) && !ret_path &&
603,800✔
852
            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
294,042✔
853
                /* Shortcut this call if none of the special features of this call are requested */
854
                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
294,098✔
855
                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
294,042✔
856

857
        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
309,977✔
858
        if (r < 0)
309,758✔
859
                return r;
860
        assert(path_fd >= 0);
181,229✔
861

862
        if (fstat(path_fd, ret_stat) < 0)
181,229✔
UNCOV
863
                return -errno;
×
864

865
        if (ret_path)
181,229✔
866
                *ret_path = TAKE_PTR(p);
181,161✔
867

868
        return 0;
869
}
870

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

876
        assert(path);
34✔
877
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
34✔
878

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

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

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

894
        if (ret_path)
34✔
895
                *ret_path = TAKE_PTR(p);
10✔
896

897
        return 0;
898
}
899

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

908
        _cleanup_free_ char *final_path = NULL;
46,726✔
909
        _cleanup_close_ int fd = -EBADF;
46,726✔
910
        int mode_flags, r;
46,726✔
911

912
        assert(path);
46,726✔
913
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
46,726✔
914
        assert(open_flags);
46,726✔
915
        assert(ret_file);
46,726✔
916

917
        mode_flags = fopen_mode_to_flags(open_flags);
46,726✔
918
        if (mode_flags < 0)
46,726✔
919
                return mode_flags;
920

921
        fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
92,698✔
922
        if (fd < 0)
46,726✔
923
                return fd;
924

925
        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
45,917✔
926
        if (r < 0)
45,917✔
927
                return r;
928

929
        if (ret_path)
45,917✔
930
                *ret_path = TAKE_PTR(final_path);
599✔
931

932
        return 0;
933
}
934

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

940
        assert(path);
1✔
941
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
942

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

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

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

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

957
        return 0;
958
}
959

960
int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
26,655✔
961
        int pfd, r;
26,655✔
962

963
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
26,655✔
964

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

969
        return pfd;
26,460✔
970
}
971

972
int chase_and_openat(
945✔
973
                int dir_fd,
974
                const char *path,
975
                ChaseFlags chase_flags,
976
                int open_flags,
977
                char **ret_path) {
978

979
        _cleanup_close_ int path_fd = -EBADF;
945✔
980
        _cleanup_free_ char *p = NULL, *fname = NULL;
945✔
981
        int r;
945✔
982

983
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
945✔
984

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

993
        r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
945✔
994
        if (r < 0)
945✔
995
                return r;
996

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

1003
        r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags= */ 0, MODE_INVALID);
1,711✔
1004
        if (r < 0)
941✔
1005
                return r;
1006

1007
        if (ret_path)
941✔
1008
                *ret_path = TAKE_PTR(p);
771✔
1009

1010
        return r;
1011
}
1012

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

1019
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
956✔
1020
        assert(ret_dir);
956✔
1021

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

UNCOV
1029
                *ret_dir = d;
×
UNCOV
1030
                return 0;
×
1031
        }
1032

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

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

1042
        if (ret_path)
546✔
1043
                *ret_path = TAKE_PTR(p);
546✔
1044

1045
        *ret_dir = d;
546✔
1046
        return 0;
546✔
1047
}
1048

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

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

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

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

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

1072
        if (ret_path)
1✔
1073
                *ret_path = TAKE_PTR(p);
1✔
1074

1075
        return 0;
1076
}
1077

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

1083
        assert(path);
1✔
1084
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1✔
1085

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

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

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

1101
        if (ret_path)
1✔
1102
                *ret_path = TAKE_PTR(p);
1✔
1103

1104
        return 0;
1105
}
1106

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

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

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

1124
        mode_flags = fopen_mode_to_flags(open_flags);
172✔
1125
        if (mode_flags < 0)
172✔
1126
                return mode_flags;
1127

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

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

1136
        if (ret_path)
168✔
1137
                *ret_path = TAKE_PTR(final_path);
1✔
1138

1139
        return 0;
1140
}
1141

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

1147
        assert(path);
1✔
1148
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1✔
1149

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

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

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

1161
        if (ret_path)
1✔
1162
                *ret_path = TAKE_PTR(p);
1✔
1163

1164
        return 0;
1165
}
1166

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

1170
        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
934✔
1171

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

1176
        return pfd;
934✔
1177
}
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