]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/chase.c
Merge pull request #25608 from poettering/dissect-moar
[thirdparty/systemd.git] / src / basic / chase.c
CommitLineData
f4351959
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <linux/magic.h>
4
5#include "alloc-util.h"
f461a28d 6#include "chase.h"
f4351959 7#include "fd-util.h"
01bebba3 8#include "fileio.h"
f4351959
LP
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
16bool 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
f461a28d 27static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) {
f4351959
LP
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.",
fc03e80c 44 strna(n1), strna(user_a), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path);
f4351959
LP
45}
46
f461a28d 47static int log_autofs_mount_point(int fd, const char *path, ChaseFlags flags) {
f4351959
LP
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
f461a28d 60static int log_prohibited_symlink(int fd, ChaseFlags flags) {
d43e78b6
LP
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
11659e48 75int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
5bc244aa 76 _cleanup_free_ char *buffer = NULL, *done = NULL;
254d1313 77 _cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
f461a28d 78 unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
f4351959 79 bool exists = true, append_trail_slash = false;
ad66c7f1 80 struct stat st; /* stat obtained from fd */
f4351959
LP
81 const char *todo;
82 int r;
83
84 assert(path);
5bc244aa 85 assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
63bfd52f
DDM
86 assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
87 assert(!FLAGS_SET(flags, CHASE_TRAIL_SLASH|CHASE_EXTRACT_FILENAME));
5bc244aa 88 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
f4351959
LP
89
90 /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
ea8282b6
DDM
91 if ((flags & CHASE_NONEXISTENT))
92 assert(!ret_fd);
f4351959 93
ea8282b6
DDM
94 if ((flags & CHASE_STEP))
95 assert(!ret_fd);
f4351959
LP
96
97 if (isempty(path))
5bc244aa 98 path = ".";
f4351959 99
5bc244aa 100 /* This function resolves symlinks of the path relative to the given directory file descriptor. If
f461a28d 101 * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
c677e13c
DDM
102 * are resolved relative to the given directory file descriptor. Otherwise, they are resolved
103 * relative to the root directory of the host.
5bc244aa 104 *
c677e13c
DDM
105 * Note that when a positive directory file descriptor is provided and CHASE_AT_RESOLVE_IN_ROOT is
106 * specified and we find an absolute symlink, it is resolved relative to given directory file
107 * descriptor and not the root of the host. Also, when following relative symlinks, this functions
108 * ensures they cannot be used to "escape" the given directory file descriptor. If a positive
109 * directory file descriptor is provided, the "path" parameter is always interpreted relative to the
110 * given directory file descriptor, even if it is absolute. If the given directory file descriptor is
111 * AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host.
f4351959 112 *
5bc244aa
DDM
113 * If "dir_fd" is a valid directory fd, "path" is an absolute path and "ret_path" is not NULL, this
114 * functions returns a relative path in "ret_path" because openat() like functions generally ignore
115 * the directory fd if they are provided with an absolute path. On the other hand, if "dir_fd" is
c677e13c 116 * AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path" because
5bc244aa
DDM
117 * otherwise, if the caller passes the returned relative path to another openat() like function, it
118 * would be resolved relative to the current working directory instead of to "/".
f4351959 119 *
5bc244aa
DDM
120 * Algorithmically this operates on two path buffers: "done" are the components of the path we
121 * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we
122 * still need to process. On each iteration, we move one component from "todo" to "done", processing
18fe76eb 123 * its special meaning each time. We always keep an O_PATH fd to the component we are currently
5bc244aa 124 * processing, thus keeping lookup races to a minimum.
f4351959 125 *
5bc244aa
DDM
126 * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute
127 * path you got as-is: fully qualified and relative to your host's root. Optionally, specify the
128 * "dir_fd" parameter to tell this function what to do when encountering a symlink with an absolute
129 * path as directory: resolve it relative to the given directory file descriptor.
f4351959
LP
130 *
131 * There are five ways to invoke this function:
132 *
133 * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is
134 * returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0
135 * is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is
136 * returned if the destination was found, -ENOENT if it wasn't.
137 *
138 * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file
139 * descriptor is returned as return value. This is useful to open files relative to some root
5bc244aa
DDM
140 * directory. Note that the returned O_PATH file descriptors must be converted into a regular one
141 * (using fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be
142 * combined with CHASE_NONEXISTENT.
f4351959 143 *
5bc244aa
DDM
144 * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only
145 * the first symlink or ".." component of the path is resolved, and the resulting path is
146 * returned. This is useful if a caller wants to trace the path through the file system verbosely.
147 * Returns < 0 on error, > 0 if the path is fully normalized, and == 0 for each normalization
148 * step. This may be combined with CHASE_NONEXISTENT, in which case 1 is returned when a component
149 * is not found.
f4351959 150 *
5bc244aa
DDM
151 * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions
152 * from unprivileged to privileged files or directories. In such cases the return value is
153 * -ENOLINK. If CHASE_WARN is also set, a warning describing the unsafe transition is emitted.
154 * CHASE_WARN cannot be used in PID 1.
f4351959
LP
155 *
156 * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization
157 * is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of
9154bd57 158 * the mount point is emitted. CHASE_WARN cannot be used in PID 1.
f4351959
LP
159 */
160
e115daa6
YW
161 if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
162 /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
163 * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
164
e212f422 165 r = dir_fd_is_root_or_cwd(dir_fd);
e115daa6
YW
166 if (r < 0)
167 return r;
168 if (r > 0)
169 flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
170 }
171
577efd82
DDM
172 if (!(flags &
173 (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP|
e864dfa6 174 CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755)) &&
577efd82 175 !ret_path && ret_fd) {
f4351959 176
5bc244aa
DDM
177 /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
178 * set and doesn't care about any of the other special features we provide either. */
db0096f2 179 r = openat(dir_fd, path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
f4351959
LP
180 if (r < 0)
181 return -errno;
182
183 *ret_fd = r;
184 return 0;
185 }
186
db0096f2
YW
187 buffer = strdup(path);
188 if (!buffer)
189 return -ENOMEM;
f4351959 190
c677e13c
DDM
191 /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
192 * a relative path would be interpreted relative to the current working directory. */
193 bool need_absolute = dir_fd == AT_FDCWD && path_is_absolute(path);
5bc244aa
DDM
194 if (need_absolute) {
195 done = strdup("/");
196 if (!done)
197 return -ENOMEM;
f4351959
LP
198 }
199
c677e13c 200 /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
52576a75 201 * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
c677e13c
DDM
202 * whether to resolve symlinks in it or not. */
203 if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
5bc244aa
DDM
204 root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
205 else
206 root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
207 if (root_fd < 0)
208 return -errno;
f4351959 209
c677e13c
DDM
210 /* If a positive directory file descriptor is provided, always resolve the given path relative to it,
211 * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat()
212 * semantics, if the path is relative, resolve against the current working directory. Otherwise,
213 * resolve against root. */
214 if (dir_fd >= 0 || !path_is_absolute(path))
5bc244aa
DDM
215 fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
216 else
217 fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
f4351959
LP
218 if (fd < 0)
219 return -errno;
220
ad66c7f1 221 if (fstat(fd, &st) < 0)
5bc244aa 222 return -errno;
f4351959
LP
223
224 if (flags & CHASE_TRAIL_SLASH)
7bf4057d 225 append_trail_slash = ENDSWITH_SET(buffer, "/", "/.");
f4351959 226
5bc244aa 227 for (todo = buffer;;) {
f4351959 228 _cleanup_free_ char *first = NULL;
5bb1d7fb 229 _cleanup_close_ int child = -EBADF;
ad66c7f1 230 struct stat st_child;
f4351959
LP
231 const char *e;
232
860f4c6a 233 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
f4351959
LP
234 if (r < 0)
235 return r;
236 if (r == 0) { /* We reached the end. */
237 if (append_trail_slash)
238 if (!strextend(&done, "/"))
239 return -ENOMEM;
240 break;
241 }
242
243 first = strndup(e, r);
244 if (!first)
245 return -ENOMEM;
246
247 /* Two dots? Then chop off the last bit of what we already found out. */
248 if (path_equal(first, "..")) {
249 _cleanup_free_ char *parent = NULL;
254d1313 250 _cleanup_close_ int fd_parent = -EBADF;
ad66c7f1 251 struct stat st_parent;
f4351959 252
5bc244aa
DDM
253 /* If we already are at the top, then going up will not change anything. This is
254 * in-line with how the kernel handles this. */
255 if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
f4351959
LP
256 continue;
257
5bc244aa
DDM
258 fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
259 if (fd_parent < 0)
260 return -errno;
f4351959 261
ad66c7f1 262 if (fstat(fd_parent, &st_parent) < 0)
5bc244aa
DDM
263 return -errno;
264
a6ef5ef7
YW
265 /* If we opened the same directory, that _may_ indicate that we're at the host root
266 * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
5bc244aa 267 * going up won't change anything. */
a6ef5ef7
YW
268 if (stat_inode_same(&st_parent, &st)) {
269 r = dir_fd_is_root(fd);
270 if (r < 0)
271 return r;
272 if (r > 0)
273 continue;
274 }
f4351959 275
5bc244aa
DDM
276 r = path_extract_directory(done, &parent);
277 if (r >= 0 || r == -EDESTADDRREQ)
278 free_and_replace(done, parent);
279 else if (IN_SET(r, -EINVAL, -EADDRNOTAVAIL)) {
280 /* If we're at the top of "dir_fd", start appending ".." to "done". */
281 if (!path_extend(&done, ".."))
282 return -ENOMEM;
283 } else
284 return r;
f4351959
LP
285
286 if (flags & CHASE_STEP)
287 goto chased_one;
288
ad66c7f1
YW
289 if (flags & CHASE_SAFE &&
290 unsafe_transition(&st, &st_parent))
291 return log_unsafe_transition(fd, fd_parent, path, flags);
f4351959 292
47f0e1b5
DDM
293 if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
294 break;
295
ad66c7f1
YW
296 /* update fd and stat */
297 st = st_parent;
ee3455cf 298 close_and_replace(fd, fd_parent);
f4351959
LP
299 continue;
300 }
301
302 /* Otherwise let's see what this is. */
e864dfa6
DDM
303 child = r = RET_NERRNO(openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH));
304 if (r < 0) {
305 if (r != -ENOENT)
306 return r;
307
308 if (!isempty(todo) && !path_is_safe(todo))
309 return r;
f4351959 310
47f0e1b5 311 if (FLAGS_SET(flags, CHASE_MKDIR_0755) && !isempty(todo)) {
ba54d730 312 child = xopenat(fd, first, O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, 0755);
47f0e1b5
DDM
313 if (child < 0)
314 return child;
315 } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
316 if (!path_extend(&done, first))
317 return -ENOMEM;
318
319 break;
320 } else if (flags & CHASE_NONEXISTENT) {
f4351959
LP
321 if (!path_extend(&done, first, todo))
322 return -ENOMEM;
323
324 exists = false;
325 break;
47f0e1b5 326 } else
e864dfa6 327 return r;
f4351959
LP
328 }
329
ad66c7f1 330 if (fstat(child, &st_child) < 0)
f4351959 331 return -errno;
ad66c7f1 332
f4351959 333 if ((flags & CHASE_SAFE) &&
ad66c7f1 334 unsafe_transition(&st, &st_child))
f4351959
LP
335 return log_unsafe_transition(fd, child, path, flags);
336
f4351959
LP
337 if ((flags & CHASE_NO_AUTOFS) &&
338 fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
339 return log_autofs_mount_point(child, path, flags);
340
ad66c7f1 341 if (S_ISLNK(st_child.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
f4351959
LP
342 _cleanup_free_ char *destination = NULL;
343
d43e78b6
LP
344 if (flags & CHASE_PROHIBIT_SYMLINKS)
345 return log_prohibited_symlink(child, flags);
346
f4351959
LP
347 /* This is a symlink, in this case read the destination. But let's make sure we
348 * don't follow symlinks without bounds. */
349 if (--max_follow <= 0)
350 return -ELOOP;
351
352 r = readlinkat_malloc(fd, first, &destination);
353 if (r < 0)
354 return r;
355 if (isempty(destination))
356 return -EINVAL;
357
358 if (path_is_absolute(destination)) {
359
5bc244aa
DDM
360 /* An absolute destination. Start the loop from the beginning, but use the
361 * root file descriptor as base. */
f4351959
LP
362
363 safe_close(fd);
5bc244aa 364 fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
f4351959 365 if (fd < 0)
5bc244aa 366 return fd;
f4351959 367
ad66c7f1
YW
368 if (fstat(fd, &st) < 0)
369 return -errno;
f4351959 370
ad66c7f1
YW
371 if (flags & CHASE_SAFE &&
372 unsafe_transition(&st_child, &st))
373 return log_unsafe_transition(child, fd, path, flags);
f4351959 374
5bc244aa 375 r = free_and_strdup(&done, need_absolute ? "/" : NULL);
f4351959
LP
376 if (r < 0)
377 return r;
378 }
379
380 /* Prefix what's left to do with what we just read, and start the loop again, but
381 * remain in the current directory. */
382 if (!path_extend(&destination, todo))
383 return -ENOMEM;
384
385 free_and_replace(buffer, destination);
386 todo = buffer;
387
388 if (flags & CHASE_STEP)
389 goto chased_one;
390
391 continue;
392 }
393
394 /* If this is not a symlink, then let's just add the name we read to what we already verified. */
395 if (!path_extend(&done, first))
396 return -ENOMEM;
397
47f0e1b5
DDM
398 if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
399 break;
400
f4351959 401 /* And iterate again, but go one directory further down. */
ad66c7f1 402 st = st_child;
ee3455cf 403 close_and_replace(fd, child);
f4351959
LP
404 }
405
47f0e1b5 406 if (flags & CHASE_PARENT) {
75adfc3b 407 r = stat_verify_directory(&st);
28aa650e
DDM
408 if (r < 0)
409 return r;
410 }
411
7bf4057d 412 if (ret_path) {
63bfd52f
DDM
413 if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
414 _cleanup_free_ char *f = NULL;
415
416 r = path_extract_filename(done, &f);
21eac258 417 if (r < 0 && r != -EADDRNOTAVAIL)
63bfd52f
DDM
418 return r;
419
21eac258 420 /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
63bfd52f
DDM
421 free_and_replace(done, f);
422 }
423
7bf4057d
DDM
424 if (!done) {
425 done = strdup(append_trail_slash ? "./" : ".");
426 if (!done)
427 return -ENOMEM;
428 }
429
f4351959 430 *ret_path = TAKE_PTR(done);
7bf4057d 431 }
f4351959
LP
432
433 if (ret_fd) {
434 /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
435 * proper fd by opening /proc/self/fd/xyz. */
436
437 assert(fd >= 0);
438 *ret_fd = TAKE_FD(fd);
439 }
440
441 if (flags & CHASE_STEP)
442 return 1;
443
444 return exists;
445
446chased_one:
447 if (ret_path) {
448 const char *e;
449
7bf4057d
DDM
450 if (!done) {
451 done = strdup(append_trail_slash ? "./" : ".");
452 if (!done)
453 return -ENOMEM;
454 }
455
f4351959 456 /* todo may contain slashes at the beginning. */
860f4c6a 457 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
f4351959
LP
458 if (r < 0)
459 return r;
460 if (r == 0)
461 *ret_path = TAKE_PTR(done);
462 else {
463 char *c;
464
465 c = path_join(done, e);
466 if (!c)
467 return -ENOMEM;
468
469 *ret_path = c;
470 }
471 }
472
473 return 0;
474}
475
11659e48 476int chase(const char *path, const char *original_root, ChaseFlags flags, char **ret_path, int *ret_fd) {
5bc244aa 477 _cleanup_free_ char *root = NULL, *absolute = NULL, *p = NULL;
254d1313 478 _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
5bc244aa
DDM
479 int r;
480
481 assert(path);
482
483 if (isempty(path))
484 return -EINVAL;
485
486 /* A root directory of "/" or "" is identical to none */
487 if (empty_or_root(original_root))
488 original_root = NULL;
489
490 if (original_root) {
491 r = path_make_absolute_cwd(original_root, &root);
492 if (r < 0)
493 return r;
494
495 /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
0d68cd72 496 * end. While we won't resolve the root path we still simplify it. */
5bc244aa
DDM
497 path_simplify(root);
498
0d68cd72
YW
499 assert(path_is_absolute(root));
500 assert(!empty_or_root(root));
501
5bc244aa
DDM
502 if (flags & CHASE_PREFIX_ROOT) {
503 absolute = path_join(root, path);
504 if (!absolute)
505 return -ENOMEM;
506 }
0d68cd72
YW
507
508 flags |= CHASE_AT_RESOLVE_IN_ROOT;
5bc244aa
DDM
509 }
510
511 if (!absolute) {
512 r = path_make_absolute_cwd(path, &absolute);
513 if (r < 0)
514 return r;
515 }
516
accc26a0
DDM
517 path = path_startswith(absolute, empty_to_root(root));
518 if (!path)
519 return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG,
6d5d3e20
YW
520 SYNTHETIC_ERRNO(ECHRNG),
521 "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
522 absolute, empty_to_root(root));
accc26a0
DDM
523
524 fd = open(empty_to_root(root), O_CLOEXEC|O_DIRECTORY|O_PATH);
525 if (fd < 0)
526 return -errno;
5bc244aa 527
8bf26bfe 528 r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
5bc244aa
DDM
529 if (r < 0)
530 return r;
531
532 if (ret_path) {
63bfd52f
DDM
533 if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
534 _cleanup_free_ char *q = NULL;
0a9b8878 535
63bfd52f
DDM
536 q = path_join(empty_to_root(root), p);
537 if (!q)
538 return -ENOMEM;
5bc244aa 539
63bfd52f 540 path_simplify(q);
7bf4057d 541
63bfd52f
DDM
542 if (FLAGS_SET(flags, CHASE_TRAIL_SLASH) && ENDSWITH_SET(path, "/", "/."))
543 if (!strextend(&q, "/"))
544 return -ENOMEM;
545
546 free_and_replace(p, q);
547 }
7bf4057d 548
63bfd52f 549 *ret_path = TAKE_PTR(p);
5bc244aa
DDM
550 }
551
552 if (ret_fd)
553 *ret_fd = TAKE_FD(pfd);
554
555 return r;
556}
557
11659e48 558int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path) {
254d1313 559 _cleanup_close_ int path_fd = -EBADF;
47f0e1b5
DDM
560 _cleanup_free_ char *p = NULL, *fname = NULL;
561 mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
562 const char *q;
f4351959
LP
563 int r;
564
ea8282b6 565 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
f4351959 566
577efd82 567 if (empty_or_root(root) && !ret_path &&
47f0e1b5 568 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
f4351959 569 /* Shortcut this call if none of the special features of this call are requested */
47f0e1b5
DDM
570 return RET_NERRNO(xopenat(AT_FDCWD, path,
571 open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
572 mode));
f4351959 573
f461a28d 574 r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
f4351959
LP
575 if (r < 0)
576 return r;
577 assert(path_fd >= 0);
578
47f0e1b5
DDM
579 assert_se(q = path_startswith(p, empty_to_root(root)));
580 if (isempty(q))
581 q = ".";
582
708e8870
DDM
583 if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
584 r = path_extract_filename(q, &fname);
585 if (r < 0 && r != -EADDRNOTAVAIL)
586 return r;
587 }
47f0e1b5 588
708e8870 589 r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, mode);
f4351959
LP
590 if (r < 0)
591 return r;
592
593 if (ret_path)
594 *ret_path = TAKE_PTR(p);
595
596 return r;
597}
598
11659e48 599int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
254d1313 600 _cleanup_close_ int path_fd = -EBADF;
f4351959
LP
601 _cleanup_free_ char *p = NULL;
602 DIR *d;
603 int r;
604
ea8282b6
DDM
605 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
606 assert(ret_dir);
f4351959 607
577efd82 608 if (empty_or_root(root) && !ret_path &&
e864dfa6 609 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
f4351959
LP
610 /* Shortcut this call if none of the special features of this call are requested */
611 d = opendir(path);
612 if (!d)
613 return -errno;
614
615 *ret_dir = d;
616 return 0;
617 }
618
f461a28d 619 r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
f4351959
LP
620 if (r < 0)
621 return r;
622 assert(path_fd >= 0);
623
2075b6dd 624 d = xopendirat(path_fd, ".", O_NOFOLLOW);
1fe6e5c1
LP
625 if (!d)
626 return -errno;
f4351959
LP
627
628 if (ret_path)
629 *ret_path = TAKE_PTR(p);
630
631 *ret_dir = d;
632 return 0;
633}
634
11659e48 635int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
254d1313 636 _cleanup_close_ int path_fd = -EBADF;
f4351959
LP
637 _cleanup_free_ char *p = NULL;
638 int r;
639
640 assert(path);
ea8282b6
DDM
641 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
642 assert(ret_stat);
f4351959 643
577efd82 644 if (empty_or_root(root) && !ret_path &&
88f2ee86 645 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
f4351959 646 /* Shortcut this call if none of the special features of this call are requested */
88f2ee86
DDM
647 return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
648 FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
f4351959 649
f461a28d 650 r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
f4351959
LP
651 if (r < 0)
652 return r;
653 assert(path_fd >= 0);
654
655 if (fstat(path_fd, ret_stat) < 0)
656 return -errno;
657
658 if (ret_path)
659 *ret_path = TAKE_PTR(p);
2b2caea2 660
88f2ee86 661 return 0;
2b2caea2
LP
662}
663
11659e48 664int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
254d1313 665 _cleanup_close_ int path_fd = -EBADF;
2b2caea2
LP
666 _cleanup_free_ char *p = NULL;
667 int r;
668
669 assert(path);
ea8282b6 670 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
2b2caea2 671
577efd82 672 if (empty_or_root(root) && !ret_path &&
88f2ee86 673 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
2b2caea2 674 /* Shortcut this call if none of the special features of this call are requested */
88f2ee86
DDM
675 return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
676 FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
2b2caea2 677
f461a28d 678 r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
2b2caea2
LP
679 if (r < 0)
680 return r;
681 assert(path_fd >= 0);
682
683 r = access_fd(path_fd, access_mode);
684 if (r < 0)
685 return r;
686
687 if (ret_path)
688 *ret_path = TAKE_PTR(p);
f4351959 689
88f2ee86 690 return 0;
f4351959 691}
01bebba3 692
f461a28d 693int chase_and_fopen_unlocked(
01bebba3
LP
694 const char *path,
695 const char *root,
f461a28d 696 ChaseFlags chase_flags,
01bebba3
LP
697 const char *open_flags,
698 char **ret_path,
699 FILE **ret_file) {
700
701 _cleanup_free_ char *final_path = NULL;
254d1313 702 _cleanup_close_ int fd = -EBADF;
01bebba3
LP
703 int mode_flags, r;
704
705 assert(path);
ea8282b6 706 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
01bebba3
LP
707 assert(open_flags);
708 assert(ret_file);
709
710 mode_flags = fopen_mode_to_flags(open_flags);
711 if (mode_flags < 0)
712 return mode_flags;
713
f461a28d 714 fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
01bebba3
LP
715 if (fd < 0)
716 return fd;
717
718 r = take_fdopen_unlocked(&fd, open_flags, ret_file);
719 if (r < 0)
720 return r;
721
722 if (ret_path)
723 *ret_path = TAKE_PTR(final_path);
724
725 return 0;
726}
1132fd73 727
11659e48 728int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
47f0e1b5 729 _cleanup_free_ char *p = NULL, *fname = NULL;
92651a7a 730 _cleanup_close_ int fd = -EBADF;
1132fd73
LN
731 int r;
732
733 assert(path);
ea8282b6 734 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1132fd73 735
f461a28d 736 fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1132fd73
LN
737 if (fd < 0)
738 return fd;
739
47f0e1b5
DDM
740 r = path_extract_filename(p, &fname);
741 if (r < 0)
742 return r;
1132fd73
LN
743
744 if (unlinkat(fd, fname, unlink_flags) < 0)
745 return -errno;
746
747 if (ret_path)
47f0e1b5 748 *ret_path = TAKE_PTR(p);
1132fd73
LN
749
750 return 0;
751}
9a98c0f2 752
670afc18
DDM
753int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
754 int pfd, r;
755
756 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
757
758 r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
759 if (r < 0)
760 return r;
761
762 return pfd;
763}
764
11659e48 765int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path) {
9a98c0f2 766 _cleanup_close_ int path_fd = -EBADF;
47f0e1b5
DDM
767 _cleanup_free_ char *p = NULL, *fname = NULL;
768 mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
9a98c0f2
DDM
769 int r;
770
ea8282b6 771 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
9a98c0f2
DDM
772
773 if (dir_fd == AT_FDCWD && !ret_path &&
774 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
775 /* Shortcut this call if none of the special features of this call are requested */
47f0e1b5
DDM
776 return RET_NERRNO(xopenat(dir_fd, path,
777 open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
778 mode));
9a98c0f2 779
f461a28d 780 r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
9a98c0f2
DDM
781 if (r < 0)
782 return r;
9a98c0f2 783
708e8870
DDM
784 if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
785 r = path_extract_filename(p, &fname);
786 if (r < 0 && r != -EADDRNOTAVAIL)
787 return r;
788 }
47f0e1b5 789
708e8870 790 r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, mode);
9a98c0f2
DDM
791 if (r < 0)
792 return r;
793
794 if (ret_path)
795 *ret_path = TAKE_PTR(p);
796
797 return r;
798}
799
11659e48 800int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
12ef2617
DDM
801 _cleanup_close_ int path_fd = -EBADF;
802 _cleanup_free_ char *p = NULL;
803 DIR *d;
804 int r;
805
806 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
807 assert(ret_dir);
808
809 if (dir_fd == AT_FDCWD && !ret_path &&
810 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
811 /* Shortcut this call if none of the special features of this call are requested */
812 d = opendir(path);
813 if (!d)
814 return -errno;
815
816 *ret_dir = d;
817 return 0;
818 }
819
f461a28d 820 r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
12ef2617
DDM
821 if (r < 0)
822 return r;
823 assert(path_fd >= 0);
824
825 d = xopendirat(path_fd, ".", O_NOFOLLOW);
826 if (!d)
827 return -errno;
828
829 if (ret_path)
830 *ret_path = TAKE_PTR(p);
831
832 *ret_dir = d;
833 return 0;
834}
835
11659e48 836int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
12ef2617
DDM
837 _cleanup_close_ int path_fd = -EBADF;
838 _cleanup_free_ char *p = NULL;
839 int r;
840
841 assert(path);
842 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
843 assert(ret_stat);
844
845 if (dir_fd == AT_FDCWD && !ret_path &&
846 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
847 /* Shortcut this call if none of the special features of this call are requested */
848 return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
849 FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
850
f461a28d 851 r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
12ef2617
DDM
852 if (r < 0)
853 return r;
854 assert(path_fd >= 0);
855
856 if (fstat(path_fd, ret_stat) < 0)
857 return -errno;
858
859 if (ret_path)
860 *ret_path = TAKE_PTR(p);
861
862 return 0;
863}
864
11659e48 865int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
12ef2617
DDM
866 _cleanup_close_ int path_fd = -EBADF;
867 _cleanup_free_ char *p = NULL;
868 int r;
869
870 assert(path);
871 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
872
873 if (dir_fd == AT_FDCWD && !ret_path &&
874 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
875 /* Shortcut this call if none of the special features of this call are requested */
876 return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
877 FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
878
f461a28d 879 r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
12ef2617
DDM
880 if (r < 0)
881 return r;
882 assert(path_fd >= 0);
883
884 r = access_fd(path_fd, access_mode);
885 if (r < 0)
886 return r;
887
888 if (ret_path)
889 *ret_path = TAKE_PTR(p);
890
891 return 0;
892}
893
f461a28d 894int chase_and_fopenat_unlocked(
12ef2617
DDM
895 int dir_fd,
896 const char *path,
f461a28d 897 ChaseFlags chase_flags,
12ef2617
DDM
898 const char *open_flags,
899 char **ret_path,
900 FILE **ret_file) {
901
902 _cleanup_free_ char *final_path = NULL;
903 _cleanup_close_ int fd = -EBADF;
904 int mode_flags, r;
905
906 assert(path);
907 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
908 assert(open_flags);
909 assert(ret_file);
910
911 mode_flags = fopen_mode_to_flags(open_flags);
912 if (mode_flags < 0)
913 return mode_flags;
914
f461a28d 915 fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
12ef2617
DDM
916 if (fd < 0)
917 return fd;
918
919 r = take_fdopen_unlocked(&fd, open_flags, ret_file);
920 if (r < 0)
921 return r;
922
923 if (ret_path)
924 *ret_path = TAKE_PTR(final_path);
925
926 return 0;
927}
928
11659e48 929int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
12ef2617
DDM
930 _cleanup_free_ char *p = NULL, *fname = NULL;
931 _cleanup_close_ int fd = -EBADF;
932 int r;
933
934 assert(path);
935 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
936
f461a28d 937 fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
12ef2617
DDM
938 if (fd < 0)
939 return fd;
940
941 r = path_extract_filename(p, &fname);
942 if (r < 0)
943 return r;
944
945 if (unlinkat(fd, fname, unlink_flags) < 0)
946 return -errno;
947
948 if (ret_path)
949 *ret_path = TAKE_PTR(p);
950
951 return 0;
952}
670afc18 953
11659e48 954int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
670afc18
DDM
955 int pfd, r;
956
957 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
958
959 r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
960 if (r < 0)
961 return r;
962
963 return pfd;
964}