udev: String substitutions can be done in ENV, too
[systemd_ALT.git] / src / basic / chase.c
blobb79096f960cb5f797a8ec9a02d8f7ff12b079abc
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include <linux/magic.h>
5 #include "alloc-util.h"
6 #include "chase.h"
7 #include "fd-util.h"
8 #include "fileio.h"
9 #include "fs-util.h"
10 #include "glyph-util.h"
11 #include "log.h"
12 #include "path-util.h"
13 #include "string-util.h"
14 #include "user-util.h"
16 bool unsafe_transition(const struct stat *a, const struct stat *b) {
17 /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
18 * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files
19 * making us believe we read something safe even though it isn't safe in the specific context we open it in. */
21 if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */
22 return false;
24 return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
27 static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) {
28 _cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL;
29 struct stat st;
31 if (!FLAGS_SET(flags, CHASE_WARN))
32 return -ENOLINK;
34 (void) fd_get_path(a, &n1);
35 (void) fd_get_path(b, &n2);
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);
42 return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK),
43 "Detected unsafe path transition %s (owned by %s) %s %s (owned by %s) during canonicalization of %s.",
44 strna(n1), strna(user_a), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path);
47 static int log_autofs_mount_point(int fd, const char *path, ChaseFlags flags) {
48 _cleanup_free_ char *n1 = NULL;
50 if (!FLAGS_SET(flags, CHASE_WARN))
51 return -EREMOTE;
53 (void) fd_get_path(fd, &n1);
55 return log_warning_errno(SYNTHETIC_ERRNO(EREMOTE),
56 "Detected autofs mount point %s during canonicalization of %s.",
57 strna(n1), path);
60 static int log_prohibited_symlink(int fd, ChaseFlags flags) {
61 _cleanup_free_ char *n1 = NULL;
63 assert(fd >= 0);
65 if (!FLAGS_SET(flags, CHASE_WARN))
66 return -EREMCHG;
68 (void) fd_get_path(fd, &n1);
70 return log_warning_errno(SYNTHETIC_ERRNO(EREMCHG),
71 "Detected symlink where not symlink is allowed at %s, refusing.",
72 strna(n1));
75 static int chaseat_needs_absolute(int dir_fd, const char *path) {
76 if (dir_fd < 0)
77 return path_is_absolute(path);
79 return dir_fd_is_root(dir_fd);
82 int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
83 _cleanup_free_ char *buffer = NULL, *done = NULL;
84 _cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
85 unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
86 bool exists = true, append_trail_slash = false;
87 struct stat st; /* stat obtained from fd */
88 const char *todo;
89 int r;
91 assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
92 assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
93 assert(!FLAGS_SET(flags, CHASE_TRAIL_SLASH|CHASE_EXTRACT_FILENAME));
94 assert(!FLAGS_SET(flags, CHASE_MKDIR_0755) || (flags & (CHASE_NONEXISTENT | CHASE_PARENT)) != 0);
95 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
97 /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
98 if (FLAGS_SET(flags, CHASE_NONEXISTENT))
99 assert(!ret_fd);
101 if (FLAGS_SET(flags, CHASE_STEP))
102 assert(!ret_fd);
104 if (isempty(path))
105 path = ".";
107 /* This function resolves symlinks of the path relative to the given directory file descriptor. If
108 * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
109 * are resolved relative to the given directory file descriptor. Otherwise, they are resolved
110 * relative to the root directory of the host.
112 * Note that when a positive directory file descriptor is provided and CHASE_AT_RESOLVE_IN_ROOT is
113 * specified and we find an absolute symlink, it is resolved relative to given directory file
114 * descriptor and not the root of the host. Also, when following relative symlinks, this functions
115 * ensures they cannot be used to "escape" the given directory file descriptor. If a positive
116 * directory file descriptor is provided, the "path" parameter is always interpreted relative to the
117 * given directory file descriptor, even if it is absolute. If the given directory file descriptor is
118 * AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host.
120 * When "dir_fd" points to a non-root directory and CHASE_AT_RESOLVE_IN_ROOT is set, this function
121 * always returns a relative path in "ret_path", even if "path" is an absolute path, because openat()
122 * like functions generally ignore the directory fd if they are provided with an absolute path. When
123 * CHASE_AT_RESOLVE_IN_ROOT is not set, then this returns relative path to the specified file
124 * descriptor if all resolved symlinks are relative, otherwise absolute path will be returned. When
125 * "dir_fd" is AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path"
126 * because otherwise, if the caller passes the returned relative path to another openat() like
127 * function, it would be resolved relative to the current working directory instead of to "/".
129 * Summary about the result path:
130 * - "dir_fd" points to the root directory
131 * → result will be absolute
132 * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is set
133 * → relative
134 * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is not set
135 * → relative when all resolved symlinks are relative, otherwise absolute
136 * - "dir_fd" is AT_FDCWD, and "path" is absolute
137 * → absolute
138 * - "dir_fd" is AT_FDCWD, and "path" is relative
139 * → relative when all resolved symlinks are relative, otherwise absolute
141 * Algorithmically this operates on two path buffers: "done" are the components of the path we
142 * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we
143 * still need to process. On each iteration, we move one component from "todo" to "done", processing
144 * its special meaning each time. We always keep an O_PATH fd to the component we are currently
145 * processing, thus keeping lookup races to a minimum.
147 * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute
148 * path you got as-is: fully qualified and relative to your host's root. Optionally, specify the
149 * "dir_fd" parameter to tell this function what to do when encountering a symlink with an absolute
150 * path as directory: resolve it relative to the given directory file descriptor.
152 * There are five ways to invoke this function:
154 * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is
155 * returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0
156 * is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is
157 * returned if the destination was found, -ENOENT if it wasn't.
159 * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file
160 * descriptor is returned as return value. This is useful to open files relative to some root
161 * directory. Note that the returned O_PATH file descriptors must be converted into a regular one
162 * (using fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be
163 * combined with CHASE_NONEXISTENT.
165 * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only
166 * the first symlink or ".." component of the path is resolved, and the resulting path is
167 * returned. This is useful if a caller wants to trace the path through the file system verbosely.
168 * Returns < 0 on error, > 0 if the path is fully normalized, and == 0 for each normalization
169 * step. This may be combined with CHASE_NONEXISTENT, in which case 1 is returned when a component
170 * is not found.
172 * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions
173 * from unprivileged to privileged files or directories. In such cases the return value is
174 * -ENOLINK. If CHASE_WARN is also set, a warning describing the unsafe transition is emitted.
175 * CHASE_WARN cannot be used in PID 1.
177 * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization
178 * is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of
179 * the mount point is emitted. CHASE_WARN cannot be used in PID 1.
182 if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
183 /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
184 * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
186 r = dir_fd_is_root_or_cwd(dir_fd);
187 if (r < 0)
188 return r;
189 if (r > 0)
190 flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
193 if (!(flags &
194 (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP|
195 CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755)) &&
196 !ret_path && ret_fd) {
198 /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
199 * set and doesn't care about any of the other special features we provide either. */
200 r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
201 if (r < 0)
202 return -errno;
204 *ret_fd = r;
205 return 0;
208 buffer = strdup(path);
209 if (!buffer)
210 return -ENOMEM;
212 /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
213 * a relative path would be interpreted relative to the current working directory. Also, let's make
214 * the result absolute when the file descriptor of the root directory is specified. */
215 r = chaseat_needs_absolute(dir_fd, path);
216 if (r < 0)
217 return r;
219 bool need_absolute = r;
220 if (need_absolute) {
221 done = strdup("/");
222 if (!done)
223 return -ENOMEM;
226 /* If a positive directory file descriptor is provided, always resolve the given path relative to it,
227 * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat()
228 * semantics, if the path is relative, resolve against the current working directory. Otherwise,
229 * resolve against root. */
230 fd = openat(dir_fd, done ?: ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
231 if (fd < 0)
232 return -errno;
234 if (fstat(fd, &st) < 0)
235 return -errno;
237 /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
238 * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
239 * whether to resolve symlinks in it or not. */
240 if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
241 root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
242 else
243 root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
244 if (root_fd < 0)
245 return -errno;
247 if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
248 append_trail_slash = ENDSWITH_SET(buffer, "/", "/.");
250 for (todo = buffer;;) {
251 _cleanup_free_ char *first = NULL;
252 _cleanup_close_ int child = -EBADF;
253 struct stat st_child;
254 const char *e;
256 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
257 if (r < 0)
258 return r;
259 if (r == 0) { /* We reached the end. */
260 if (append_trail_slash)
261 if (!strextend(&done, "/"))
262 return -ENOMEM;
263 break;
266 first = strndup(e, r);
267 if (!first)
268 return -ENOMEM;
270 /* Two dots? Then chop off the last bit of what we already found out. */
271 if (path_equal(first, "..")) {
272 _cleanup_free_ char *parent = NULL;
273 _cleanup_close_ int fd_parent = -EBADF;
274 struct stat st_parent;
276 /* If we already are at the top, then going up will not change anything. This is
277 * in-line with how the kernel handles this. */
278 if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
279 if (FLAGS_SET(flags, CHASE_STEP))
280 goto chased_one;
281 continue;
284 fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
285 if (fd_parent < 0)
286 return -errno;
288 if (fstat(fd_parent, &st_parent) < 0)
289 return -errno;
291 /* If we opened the same directory, that _may_ indicate that we're at the host root
292 * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
293 * going up won't change anything. */
294 if (stat_inode_same(&st_parent, &st)) {
295 r = dir_fd_is_root(fd);
296 if (r < 0)
297 return r;
298 if (r > 0) {
299 if (FLAGS_SET(flags, CHASE_STEP))
300 goto chased_one;
301 continue;
305 r = path_extract_directory(done, &parent);
306 if (r >= 0) {
307 assert(!need_absolute || path_is_absolute(parent));
308 free_and_replace(done, parent);
309 } else if (r == -EDESTADDRREQ) {
310 /* 'done' contains filename only (i.e. no slash). */
311 assert(!need_absolute);
312 done = mfree(done);
313 } else if (r == -EADDRNOTAVAIL) {
314 /* 'done' is "/". This branch should be already handled in the above. */
315 assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT));
316 assert_not_reached();
317 } else if (r == -EINVAL) {
318 /* 'done' is an empty string, ends with '..', or an invalid path. */
319 assert(!need_absolute);
320 assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT));
322 if (!path_is_valid(done))
323 return -EINVAL;
325 /* If we're at the top of "dir_fd", start appending ".." to "done". */
326 if (!path_extend(&done, ".."))
327 return -ENOMEM;
328 } else
329 return r;
331 if (FLAGS_SET(flags, CHASE_STEP))
332 goto chased_one;
334 if (FLAGS_SET(flags, CHASE_SAFE) &&
335 unsafe_transition(&st, &st_parent))
336 return log_unsafe_transition(fd, fd_parent, path, flags);
338 if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
339 break;
341 /* update fd and stat */
342 st = st_parent;
343 close_and_replace(fd, fd_parent);
344 continue;
347 /* Otherwise let's see what this is. */
348 child = r = RET_NERRNO(openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH));
349 if (r < 0) {
350 if (r != -ENOENT)
351 return r;
353 if (!isempty(todo) && !path_is_safe(todo))
354 return r;
356 if (FLAGS_SET(flags, CHASE_MKDIR_0755) && !isempty(todo)) {
357 child = xopenat(fd,
358 first,
359 O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,
360 /* xopen_flags = */ 0,
361 0755);
362 if (child < 0)
363 return child;
364 } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
365 if (!path_extend(&done, first))
366 return -ENOMEM;
368 break;
369 } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
370 if (!path_extend(&done, first, todo))
371 return -ENOMEM;
373 exists = false;
374 break;
375 } else
376 return r;
379 if (fstat(child, &st_child) < 0)
380 return -errno;
382 if (FLAGS_SET(flags, CHASE_SAFE) &&
383 unsafe_transition(&st, &st_child))
384 return log_unsafe_transition(fd, child, path, flags);
386 if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
387 fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
388 return log_autofs_mount_point(child, path, flags);
390 if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
391 _cleanup_free_ char *destination = NULL;
393 if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
394 return log_prohibited_symlink(child, flags);
396 /* This is a symlink, in this case read the destination. But let's make sure we
397 * don't follow symlinks without bounds. */
398 if (--max_follow <= 0)
399 return -ELOOP;
401 r = readlinkat_malloc(fd, first, &destination);
402 if (r < 0)
403 return r;
404 if (isempty(destination))
405 return -EINVAL;
407 if (path_is_absolute(destination)) {
409 /* An absolute destination. Start the loop from the beginning, but use the
410 * root file descriptor as base. */
412 safe_close(fd);
413 fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
414 if (fd < 0)
415 return fd;
417 if (fstat(fd, &st) < 0)
418 return -errno;
420 if (FLAGS_SET(flags, CHASE_SAFE) &&
421 unsafe_transition(&st_child, &st))
422 return log_unsafe_transition(child, fd, path, flags);
424 /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be
425 * outside of the specified dir_fd. Let's make the result absolute. */
426 if (!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
427 need_absolute = true;
429 r = free_and_strdup(&done, need_absolute ? "/" : NULL);
430 if (r < 0)
431 return r;
434 /* Prefix what's left to do with what we just read, and start the loop again, but
435 * remain in the current directory. */
436 if (!path_extend(&destination, todo))
437 return -ENOMEM;
439 free_and_replace(buffer, destination);
440 todo = buffer;
442 if (FLAGS_SET(flags, CHASE_STEP))
443 goto chased_one;
445 continue;
448 /* If this is not a symlink, then let's just add the name we read to what we already verified. */
449 if (!path_extend(&done, first))
450 return -ENOMEM;
452 if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
453 break;
455 /* And iterate again, but go one directory further down. */
456 st = st_child;
457 close_and_replace(fd, child);
460 if (FLAGS_SET(flags, CHASE_PARENT)) {
461 r = stat_verify_directory(&st);
462 if (r < 0)
463 return r;
466 if (ret_path) {
467 if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
468 _cleanup_free_ char *f = NULL;
470 r = path_extract_filename(done, &f);
471 if (r < 0 && r != -EADDRNOTAVAIL)
472 return r;
474 /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
475 free_and_replace(done, f);
478 if (!done) {
479 assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME));
480 done = strdup(append_trail_slash ? "./" : ".");
481 if (!done)
482 return -ENOMEM;
485 *ret_path = TAKE_PTR(done);
488 if (ret_fd) {
489 /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
490 * proper fd by opening /proc/self/fd/xyz. */
492 assert(fd >= 0);
493 *ret_fd = TAKE_FD(fd);
496 if (FLAGS_SET(flags, CHASE_STEP))
497 return 1;
499 return exists;
501 chased_one:
502 if (ret_path) {
503 const char *e;
505 if (!done) {
506 assert(!need_absolute);
507 done = strdup(append_trail_slash ? "./" : ".");
508 if (!done)
509 return -ENOMEM;
512 /* todo may contain slashes at the beginning. */
513 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
514 if (r < 0)
515 return r;
516 if (r == 0)
517 *ret_path = TAKE_PTR(done);
518 else {
519 char *c;
521 c = path_join(done, e);
522 if (!c)
523 return -ENOMEM;
525 *ret_path = c;
529 return 0;
532 static int empty_or_root_to_null(const char **path) {
533 int r;
535 assert(path);
537 /* This nullifies the input path when the path is empty or points to "/". */
539 if (empty_or_root(*path)) {
540 *path = NULL;
541 return 0;
544 r = path_is_root(*path);
545 if (r < 0)
546 return r;
547 if (r > 0)
548 *path = NULL;
550 return 0;
553 int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
554 _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
555 _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
556 int r;
558 assert(path);
560 if (isempty(path))
561 return -EINVAL;
563 r = empty_or_root_to_null(&root);
564 if (r < 0)
565 return r;
567 /* A root directory of "/" or "" is identical to "/". */
568 if (empty_or_root(root)) {
569 root = "/";
571 /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(),
572 * hence below is not necessary, but let's shortcut. */
573 flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
575 } else {
576 r = path_make_absolute_cwd(root, &root_abs);
577 if (r < 0)
578 return r;
580 /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
581 * end. While we won't resolve the root path we still simplify it. */
582 root = path_simplify(root_abs);
584 assert(path_is_absolute(root));
585 assert(!empty_or_root(root));
587 if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
588 absolute = path_join(root, path);
589 if (!absolute)
590 return -ENOMEM;
593 flags |= CHASE_AT_RESOLVE_IN_ROOT;
596 if (!absolute) {
597 r = path_make_absolute_cwd(path, &absolute);
598 if (r < 0)
599 return r;
602 path = path_startswith(absolute, root);
603 if (!path)
604 return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG,
605 SYNTHETIC_ERRNO(ECHRNG),
606 "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
607 absolute, root);
609 fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
610 if (fd < 0)
611 return -errno;
613 r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
614 if (r < 0)
615 return r;
617 if (ret_path) {
618 if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
620 /* When "root" points to the root directory, the result of chaseat() is always
621 * absolute, hence it is not necessary to prefix with the root. When "root" points to
622 * a non-root directory, the result path is always normalized and relative, hence
623 * we can simply call path_join() and not necessary to call path_simplify().
624 * Note that the result of chaseat() may start with "." (more specifically, it may be
625 * "." or "./"), and we need to drop "." in that case. */
627 if (empty_or_root(root))
628 assert(path_is_absolute(p));
629 else {
630 char *q;
632 assert(!path_is_absolute(p));
634 q = path_join(root, p + (*p == '.'));
635 if (!q)
636 return -ENOMEM;
638 free_and_replace(p, q);
642 *ret_path = TAKE_PTR(p);
645 if (ret_fd)
646 *ret_fd = TAKE_FD(pfd);
648 return r;
651 int chaseat_prefix_root(const char *path, const char *root, char **ret) {
652 char *q;
653 int r;
655 assert(path);
656 assert(ret);
658 /* This is mostly for prefixing the result of chaseat(). */
660 if (!path_is_absolute(path)) {
661 _cleanup_free_ char *root_abs = NULL;
663 r = empty_or_root_to_null(&root);
664 if (r < 0 && r != -ENOENT)
665 return r;
667 /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
668 if (empty_or_root(root))
669 return -EINVAL;
671 r = path_make_absolute_cwd(root, &root_abs);
672 if (r < 0)
673 return r;
675 root = path_simplify(root_abs);
677 q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
678 } else
679 q = strdup(path);
680 if (!q)
681 return -ENOMEM;
683 *ret = q;
684 return 0;
687 int chase_extract_filename(const char *path, const char *root, char **ret) {
688 int r;
690 /* This is similar to path_extract_filename(), but takes root directory.
691 * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */
693 assert(path);
694 assert(ret);
696 if (isempty(path))
697 return -EINVAL;
699 if (!path_is_absolute(path))
700 return -EINVAL;
702 r = empty_or_root_to_null(&root);
703 if (r < 0 && r != -ENOENT)
704 return r;
706 if (!empty_or_root(root)) {
707 _cleanup_free_ char *root_abs = NULL;
709 r = path_make_absolute_cwd(root, &root_abs);
710 if (r < 0)
711 return r;
713 path = path_startswith(path, root_abs);
714 if (!path)
715 return -EINVAL;
718 if (!isempty(path)) {
719 r = path_extract_filename(path, ret);
720 if (r != -EADDRNOTAVAIL)
721 return r;
724 char *fname = strdup(".");
725 if (!fname)
726 return -ENOMEM;
728 *ret = fname;
729 return 0;
732 int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path) {
733 _cleanup_close_ int path_fd = -EBADF;
734 _cleanup_free_ char *p = NULL, *fname = NULL;
735 mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
736 int r;
738 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
740 if (empty_or_root(root) && !ret_path &&
741 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
742 /* Shortcut this call if none of the special features of this call are requested */
743 return xopenat(AT_FDCWD, path,
744 open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
745 /* xopen_flags = */ 0,
746 mode);
748 r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
749 if (r < 0)
750 return r;
751 assert(path_fd >= 0);
753 if (!FLAGS_SET(chase_flags, CHASE_PARENT) &&
754 !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) {
755 r = chase_extract_filename(p, root, &fname);
756 if (r < 0)
757 return r;
760 r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, mode);
761 if (r < 0)
762 return r;
764 if (ret_path)
765 *ret_path = TAKE_PTR(p);
767 return r;
770 int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
771 _cleanup_close_ int path_fd = -EBADF;
772 _cleanup_free_ char *p = NULL;
773 DIR *d;
774 int r;
776 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
777 assert(ret_dir);
779 if (empty_or_root(root) && !ret_path &&
780 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
781 /* Shortcut this call if none of the special features of this call are requested */
782 d = opendir(path);
783 if (!d)
784 return -errno;
786 *ret_dir = d;
787 return 0;
790 r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
791 if (r < 0)
792 return r;
793 assert(path_fd >= 0);
795 d = xopendirat(path_fd, ".", O_NOFOLLOW);
796 if (!d)
797 return -errno;
799 if (ret_path)
800 *ret_path = TAKE_PTR(p);
802 *ret_dir = d;
803 return 0;
806 int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
807 _cleanup_close_ int path_fd = -EBADF;
808 _cleanup_free_ char *p = NULL;
809 int r;
811 assert(path);
812 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
813 assert(ret_stat);
815 if (empty_or_root(root) && !ret_path &&
816 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
817 /* Shortcut this call if none of the special features of this call are requested */
818 return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
819 FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
821 r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
822 if (r < 0)
823 return r;
824 assert(path_fd >= 0);
826 if (fstat(path_fd, ret_stat) < 0)
827 return -errno;
829 if (ret_path)
830 *ret_path = TAKE_PTR(p);
832 return 0;
835 int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
836 _cleanup_close_ int path_fd = -EBADF;
837 _cleanup_free_ char *p = NULL;
838 int r;
840 assert(path);
841 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
843 if (empty_or_root(root) && !ret_path &&
844 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
845 /* Shortcut this call if none of the special features of this call are requested */
846 return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
847 FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
849 r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
850 if (r < 0)
851 return r;
852 assert(path_fd >= 0);
854 r = access_fd(path_fd, access_mode);
855 if (r < 0)
856 return r;
858 if (ret_path)
859 *ret_path = TAKE_PTR(p);
861 return 0;
864 int chase_and_fopen_unlocked(
865 const char *path,
866 const char *root,
867 ChaseFlags chase_flags,
868 const char *open_flags,
869 char **ret_path,
870 FILE **ret_file) {
872 _cleanup_free_ char *final_path = NULL;
873 _cleanup_close_ int fd = -EBADF;
874 int mode_flags, r;
876 assert(path);
877 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
878 assert(open_flags);
879 assert(ret_file);
881 mode_flags = fopen_mode_to_flags(open_flags);
882 if (mode_flags < 0)
883 return mode_flags;
885 fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
886 if (fd < 0)
887 return fd;
889 r = take_fdopen_unlocked(&fd, open_flags, ret_file);
890 if (r < 0)
891 return r;
893 if (ret_path)
894 *ret_path = TAKE_PTR(final_path);
896 return 0;
899 int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
900 _cleanup_free_ char *p = NULL, *fname = NULL;
901 _cleanup_close_ int fd = -EBADF;
902 int r;
904 assert(path);
905 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
907 fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
908 if (fd < 0)
909 return fd;
911 r = path_extract_filename(p, &fname);
912 if (r < 0)
913 return r;
915 if (unlinkat(fd, fname, unlink_flags) < 0)
916 return -errno;
918 if (ret_path)
919 *ret_path = TAKE_PTR(p);
921 return 0;
924 int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
925 int pfd, r;
927 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
929 r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
930 if (r < 0)
931 return r;
933 return pfd;
936 int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path) {
937 _cleanup_close_ int path_fd = -EBADF;
938 _cleanup_free_ char *p = NULL, *fname = NULL;
939 mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
940 int r;
942 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
944 if (dir_fd == AT_FDCWD && !ret_path &&
945 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
946 /* Shortcut this call if none of the special features of this call are requested */
947 return xopenat(dir_fd, path,
948 open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
949 /* xopen_flags = */ 0,
950 mode);
952 r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
953 if (r < 0)
954 return r;
956 if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
957 r = path_extract_filename(p, &fname);
958 if (r < 0 && r != -EADDRNOTAVAIL)
959 return r;
962 r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, mode);
963 if (r < 0)
964 return r;
966 if (ret_path)
967 *ret_path = TAKE_PTR(p);
969 return r;
972 int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
973 _cleanup_close_ int path_fd = -EBADF;
974 _cleanup_free_ char *p = NULL;
975 DIR *d;
976 int r;
978 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
979 assert(ret_dir);
981 if (dir_fd == AT_FDCWD && !ret_path &&
982 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
983 /* Shortcut this call if none of the special features of this call are requested */
984 d = opendir(path);
985 if (!d)
986 return -errno;
988 *ret_dir = d;
989 return 0;
992 r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
993 if (r < 0)
994 return r;
995 assert(path_fd >= 0);
997 d = xopendirat(path_fd, ".", O_NOFOLLOW);
998 if (!d)
999 return -errno;
1001 if (ret_path)
1002 *ret_path = TAKE_PTR(p);
1004 *ret_dir = d;
1005 return 0;
1008 int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
1009 _cleanup_close_ int path_fd = -EBADF;
1010 _cleanup_free_ char *p = NULL;
1011 int r;
1013 assert(path);
1014 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1015 assert(ret_stat);
1017 if (dir_fd == AT_FDCWD && !ret_path &&
1018 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
1019 /* Shortcut this call if none of the special features of this call are requested */
1020 return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
1021 FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
1023 r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
1024 if (r < 0)
1025 return r;
1026 assert(path_fd >= 0);
1028 if (fstat(path_fd, ret_stat) < 0)
1029 return -errno;
1031 if (ret_path)
1032 *ret_path = TAKE_PTR(p);
1034 return 0;
1037 int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
1038 _cleanup_close_ int path_fd = -EBADF;
1039 _cleanup_free_ char *p = NULL;
1040 int r;
1042 assert(path);
1043 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1045 if (dir_fd == AT_FDCWD && !ret_path &&
1046 (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
1047 /* Shortcut this call if none of the special features of this call are requested */
1048 return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
1049 FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
1051 r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
1052 if (r < 0)
1053 return r;
1054 assert(path_fd >= 0);
1056 r = access_fd(path_fd, access_mode);
1057 if (r < 0)
1058 return r;
1060 if (ret_path)
1061 *ret_path = TAKE_PTR(p);
1063 return 0;
1066 int chase_and_fopenat_unlocked(
1067 int dir_fd,
1068 const char *path,
1069 ChaseFlags chase_flags,
1070 const char *open_flags,
1071 char **ret_path,
1072 FILE **ret_file) {
1074 _cleanup_free_ char *final_path = NULL;
1075 _cleanup_close_ int fd = -EBADF;
1076 int mode_flags, r;
1078 assert(path);
1079 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1080 assert(open_flags);
1081 assert(ret_file);
1083 mode_flags = fopen_mode_to_flags(open_flags);
1084 if (mode_flags < 0)
1085 return mode_flags;
1087 fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
1088 if (fd < 0)
1089 return fd;
1091 r = take_fdopen_unlocked(&fd, open_flags, ret_file);
1092 if (r < 0)
1093 return r;
1095 if (ret_path)
1096 *ret_path = TAKE_PTR(final_path);
1098 return 0;
1101 int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
1102 _cleanup_free_ char *p = NULL, *fname = NULL;
1103 _cleanup_close_ int fd = -EBADF;
1104 int r;
1106 assert(path);
1107 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
1109 fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
1110 if (fd < 0)
1111 return fd;
1113 r = path_extract_filename(p, &fname);
1114 if (r < 0)
1115 return r;
1117 if (unlinkat(fd, fname, unlink_flags) < 0)
1118 return -errno;
1120 if (ret_path)
1121 *ret_path = TAKE_PTR(p);
1123 return 0;
1126 int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
1127 int pfd, r;
1129 assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
1131 r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
1132 if (r < 0)
1133 return r;
1135 return pfd;