]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/chase.c
3a929498bf2cc1a1a832497bad244d162efd3f32
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include <linux/magic.h>
7 #include "alloc-util.h"
9 #include "errno-util.h"
13 #include "glyph-util.h"
15 #include "path-util.h"
16 #include "stat-util.h"
17 #include "string-util.h"
19 #include "user-util.h"
21 bool unsafe_transition(const struct stat
*a
, const struct stat
*b
) {
22 /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
23 * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files
24 * making us believe we read something safe even though it isn't safe in the specific context we open it in. */
26 if (a
->st_uid
== 0) /* Transitioning from privileged to unprivileged is always fine */
29 return a
->st_uid
!= b
->st_uid
; /* Otherwise we need to stay within the same UID */
32 static int log_unsafe_transition(int a
, int b
, const char *path
, ChaseFlags flags
) {
33 _cleanup_free_
char *n1
= NULL
, *n2
= NULL
, *user_a
= NULL
, *user_b
= NULL
;
36 if (!FLAGS_SET(flags
, CHASE_WARN
))
39 (void) fd_get_path(a
, &n1
);
40 (void) fd_get_path(b
, &n2
);
42 if (fstat(a
, &st
) == 0)
43 user_a
= uid_to_name(st
.st_uid
);
44 if (fstat(b
, &st
) == 0)
45 user_b
= uid_to_name(st
.st_uid
);
47 return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK
),
48 "Detected unsafe path transition %s (owned by %s) %s %s (owned by %s) during canonicalization of %s.",
49 strna(n1
), strna(user_a
), glyph(GLYPH_ARROW_RIGHT
), strna(n2
), strna(user_b
), path
);
52 static int log_autofs_mount_point(int fd
, const char *path
, ChaseFlags flags
) {
53 _cleanup_free_
char *n1
= NULL
;
55 if (!FLAGS_SET(flags
, CHASE_WARN
))
58 (void) fd_get_path(fd
, &n1
);
60 return log_warning_errno(SYNTHETIC_ERRNO(EREMOTE
),
61 "Detected autofs mount point %s during canonicalization of %s.",
65 static int log_prohibited_symlink(int fd
, ChaseFlags flags
) {
66 _cleanup_free_
char *n1
= NULL
;
70 if (!FLAGS_SET(flags
, CHASE_WARN
))
73 (void) fd_get_path(fd
, &n1
);
75 return log_warning_errno(SYNTHETIC_ERRNO(EREMCHG
),
76 "Detected symlink where not symlink is allowed at %s, refusing.",
80 static int openat_opath_with_automount(int dir_fd
, const char *path
, bool automount
) {
81 static bool can_open_tree
= true;
84 /* Pin an inode via O_PATH semantics. Sounds pretty obvious to do this, right? You just do open()
85 * with O_PATH, and there you go. But uh, it's not that easy. open() via O_PATH does not trigger
86 * automounts, but we usually want that (except if CHASE_NO_AUTOFS is used). But thankfully there's
87 * a way out: the newer open_tree() call, when specified without OPEN_TREE_CLONE actually is fully
88 * equivalent to open() with O_PATH – except for one thing: it triggers automounts.
90 * As it turns out some sandboxes prohibit open_tree(), and return EPERM or ENOSYS if we call it.
91 * But since autofs does not work inside of mount namespace anyway, let's simply handle this
92 * as gracefully as we can, and fall back to classic openat() if we see EPERM/ENOSYS. */
94 assert(dir_fd
>= 0 || dir_fd
== AT_FDCWD
);
97 if (automount
&& can_open_tree
) {
98 r
= RET_NERRNO(open_tree(dir_fd
, path
, AT_SYMLINK_NOFOLLOW
|OPEN_TREE_CLOEXEC
));
99 if (r
>= 0 || (r
!= -EPERM
&& !ERRNO_IS_NEG_NOT_SUPPORTED(r
)))
102 can_open_tree
= false;
105 return RET_NERRNO(openat(dir_fd
, path
, O_PATH
|O_NOFOLLOW
|O_CLOEXEC
));
108 static int chaseat_needs_absolute(int dir_fd
, const char *path
) {
110 return path_is_absolute(path
);
112 return dir_fd_is_root(dir_fd
);
115 int chaseat(int dir_fd
, const char *path
, ChaseFlags flags
, char **ret_path
, int *ret_fd
) {
116 _cleanup_free_
char *buffer
= NULL
, *done
= NULL
;
117 _cleanup_close_
int fd
= -EBADF
, root_fd
= -EBADF
;
118 unsigned max_follow
= CHASE_MAX
; /* how many symlinks to follow before giving up and returning ELOOP */
119 bool exists
= true, append_trail_slash
= false;
120 struct stat st
; /* stat obtained from fd */
124 assert(!FLAGS_SET(flags
, CHASE_PREFIX_ROOT
));
125 assert(!FLAGS_SET(flags
, CHASE_MUST_BE_DIRECTORY
|CHASE_MUST_BE_REGULAR
));
126 assert(!FLAGS_SET(flags
, CHASE_STEP
|CHASE_EXTRACT_FILENAME
));
127 assert(dir_fd
>= 0 || dir_fd
== AT_FDCWD
);
129 if (FLAGS_SET(flags
, CHASE_STEP
))
135 /* This function resolves symlinks of the path relative to the given directory file descriptor. If
136 * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
137 * are resolved relative to the given directory file descriptor. Otherwise, they are resolved
138 * relative to the root directory of the host.
140 * Note that when a positive directory file descriptor is provided and CHASE_AT_RESOLVE_IN_ROOT is
141 * specified and we find an absolute symlink, it is resolved relative to given directory file
142 * descriptor and not the root of the host. Also, when following relative symlinks, this functions
143 * ensures they cannot be used to "escape" the given directory file descriptor. If a positive
144 * directory file descriptor is provided, the "path" parameter is always interpreted relative to the
145 * given directory file descriptor, even if it is absolute. If the given directory file descriptor is
146 * AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host.
148 * When "dir_fd" points to a non-root directory and CHASE_AT_RESOLVE_IN_ROOT is set, this function
149 * always returns a relative path in "ret_path", even if "path" is an absolute path, because openat()
150 * like functions generally ignore the directory fd if they are provided with an absolute path. When
151 * CHASE_AT_RESOLVE_IN_ROOT is not set, then this returns relative path to the specified file
152 * descriptor if all resolved symlinks are relative, otherwise absolute path will be returned. When
153 * "dir_fd" is AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path"
154 * because otherwise, if the caller passes the returned relative path to another openat() like
155 * function, it would be resolved relative to the current working directory instead of to "/".
157 * Summary about the result path:
158 * - "dir_fd" points to the root directory
159 * → result will be absolute
160 * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is set
162 * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is not set
163 * → relative when all resolved symlinks are relative, otherwise absolute
164 * - "dir_fd" is AT_FDCWD, and "path" is absolute
166 * - "dir_fd" is AT_FDCWD, and "path" is relative
167 * → relative when all resolved symlinks are relative, otherwise absolute
169 * Algorithmically this operates on two path buffers: "done" are the components of the path we
170 * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we
171 * still need to process. On each iteration, we move one component from "todo" to "done", processing
172 * its special meaning each time. We always keep an O_PATH fd to the component we are currently
173 * processing, thus keeping lookup races to a minimum.
175 * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute
176 * path you got as-is: fully qualified and relative to your host's root. Optionally, specify the
177 * "dir_fd" parameter to tell this function what to do when encountering a symlink with an absolute
178 * path as directory: resolve it relative to the given directory file descriptor.
180 * There are five ways to invoke this function:
182 * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is
183 * returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0
184 * is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is
185 * returned if the destination was found, -ENOENT if it wasn't.
187 * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file
188 * descriptor is returned as return value. This is useful to open files relative to some root
189 * directory. Note that the returned O_PATH file descriptors must be converted into a regular one
190 * (using fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be
191 * combined with CHASE_NONEXISTENT.
193 * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only
194 * the first symlink or ".." component of the path is resolved, and the resulting path is
195 * returned. This is useful if a caller wants to trace the path through the file system verbosely.
196 * Returns < 0 on error, > 0 if the path is fully normalized, and == 0 for each normalization
197 * step. This may be combined with CHASE_NONEXISTENT, in which case 1 is returned when a component
200 * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions
201 * from unprivileged to privileged files or directories. In such cases the return value is
202 * -ENOLINK. If CHASE_WARN is also set, a warning describing the unsafe transition is emitted.
203 * CHASE_WARN cannot be used in PID 1.
205 * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization
206 * is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of
207 * the mount point is emitted. CHASE_WARN cannot be used in PID 1.
210 if (FLAGS_SET(flags
, CHASE_AT_RESOLVE_IN_ROOT
)) {
211 /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
212 * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
214 r
= dir_fd_is_root_or_cwd(dir_fd
);
218 flags
&= ~CHASE_AT_RESOLVE_IN_ROOT
;
222 (CHASE_AT_RESOLVE_IN_ROOT
|CHASE_NONEXISTENT
|CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_STEP
|
223 CHASE_PROHIBIT_SYMLINKS
|CHASE_MKDIR_0755
|CHASE_PARENT
)) &&
224 !ret_path
&& ret_fd
) {
226 /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
227 * set and doesn't care about any of the other special features we provide either. */
228 r
= openat(dir_fd
, path
, O_PATH
|O_CLOEXEC
|(FLAGS_SET(flags
, CHASE_NOFOLLOW
) ? O_NOFOLLOW
: 0));
236 buffer
= strdup(path
);
240 /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
241 * a relative path would be interpreted relative to the current working directory. Also, let's make
242 * the result absolute when the file descriptor of the root directory is specified. */
243 r
= chaseat_needs_absolute(dir_fd
, path
);
247 bool need_absolute
= r
;
254 /* If a positive directory file descriptor is provided, always resolve the given path relative to it,
255 * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat()
256 * semantics, if the path is relative, resolve against the current working directory. Otherwise,
257 * resolve against root. */
258 fd
= openat(dir_fd
, done
?: ".", O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
262 if (fstat(fd
, &st
) < 0)
265 /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
266 * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
267 * whether to resolve symlinks in it or not. */
268 if (dir_fd
>= 0 && FLAGS_SET(flags
, CHASE_AT_RESOLVE_IN_ROOT
))
269 root_fd
= openat(dir_fd
, ".", O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
271 root_fd
= open("/", O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
275 if (ENDSWITH_SET(buffer
, "/", "/.")) {
276 flags
|= CHASE_MUST_BE_DIRECTORY
;
277 if (FLAGS_SET(flags
, CHASE_TRAIL_SLASH
))
278 append_trail_slash
= true;
279 } else if (dot_or_dot_dot(buffer
) || endswith(buffer
, "/.."))
280 flags
|= CHASE_MUST_BE_DIRECTORY
;
282 if (FLAGS_SET(flags
, CHASE_PARENT
))
283 flags
|= CHASE_MUST_BE_DIRECTORY
;
285 for (todo
= buffer
;;) {
286 _cleanup_free_
char *first
= NULL
;
287 _cleanup_close_
int child
= -EBADF
;
288 struct stat st_child
;
291 r
= path_find_first_component(&todo
, /* accept_dot_dot= */ true, &e
);
294 if (r
== 0) /* We reached the end. */
297 first
= strndup(e
, r
);
301 /* Two dots? Then chop off the last bit of what we already found out. */
302 if (streq(first
, "..")) {
303 _cleanup_free_
char *parent
= NULL
;
304 _cleanup_close_
int fd_parent
= -EBADF
;
305 struct stat st_parent
;
307 /* If we already are at the top, then going up will not change anything. This is
308 * in-line with how the kernel handles this. */
309 if (empty_or_root(done
) && FLAGS_SET(flags
, CHASE_AT_RESOLVE_IN_ROOT
)) {
310 if (FLAGS_SET(flags
, CHASE_STEP
))
315 fd_parent
= openat(fd
, "..", O_CLOEXEC
|O_NOFOLLOW
|O_PATH
|O_DIRECTORY
);
319 if (fstat(fd_parent
, &st_parent
) < 0)
322 /* If we opened the same directory, that _may_ indicate that we're at the host root
323 * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
324 * going up won't change anything. */
325 if (stat_inode_same(&st_parent
, &st
)) {
326 r
= dir_fd_is_root(fd
);
330 if (FLAGS_SET(flags
, CHASE_STEP
))
336 r
= path_extract_directory(done
, &parent
);
338 assert(!need_absolute
|| path_is_absolute(parent
));
339 free_and_replace(done
, parent
);
340 } else if (r
== -EDESTADDRREQ
) {
341 /* 'done' contains filename only (i.e. no slash). */
342 assert(!need_absolute
);
344 } else if (r
== -EADDRNOTAVAIL
) {
345 /* 'done' is "/". This branch should be already handled in the above. */
346 assert(!FLAGS_SET(flags
, CHASE_AT_RESOLVE_IN_ROOT
));
347 assert_not_reached();
348 } else if (r
== -EINVAL
) {
349 /* 'done' is an empty string, ends with '..', or an invalid path. */
350 assert(!need_absolute
);
351 assert(!FLAGS_SET(flags
, CHASE_AT_RESOLVE_IN_ROOT
));
353 if (!path_is_valid(done
))
356 /* If we're at the top of "dir_fd", start appending ".." to "done". */
357 if (!path_extend(&done
, ".."))
362 if (FLAGS_SET(flags
, CHASE_STEP
))
365 if (FLAGS_SET(flags
, CHASE_SAFE
) &&
366 unsafe_transition(&st
, &st_parent
))
367 return log_unsafe_transition(fd
, fd_parent
, path
, flags
);
369 /* If the path ends on a "..", and CHASE_PARENT is specified then our current 'fd' is
370 * the child of the returned normalized path, not the parent as requested. To correct
371 * this we have to go *two* levels up. */
372 if (FLAGS_SET(flags
, CHASE_PARENT
) && isempty(todo
)) {
373 _cleanup_close_
int fd_grandparent
= -EBADF
;
374 struct stat st_grandparent
;
376 fd_grandparent
= openat(fd_parent
, "..", O_CLOEXEC
|O_NOFOLLOW
|O_PATH
|O_DIRECTORY
);
377 if (fd_grandparent
< 0)
380 if (fstat(fd_grandparent
, &st_grandparent
) < 0)
383 if (FLAGS_SET(flags
, CHASE_SAFE
) &&
384 unsafe_transition(&st_parent
, &st_grandparent
))
385 return log_unsafe_transition(fd_parent
, fd_grandparent
, path
, flags
);
388 close_and_replace(fd
, fd_grandparent
);
392 /* update fd and stat */
394 close_and_replace(fd
, fd_parent
);
398 /* Otherwise let's pin it by file descriptor, via O_PATH. */
399 child
= r
= openat_opath_with_automount(fd
, first
, /* automount = */ !FLAGS_SET(flags
, CHASE_NO_AUTOFS
));
404 if (!isempty(todo
) && !path_is_safe(todo
)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */
407 if (FLAGS_SET(flags
, CHASE_MKDIR_0755
) && (!isempty(todo
) || !(flags
& (CHASE_PARENT
|CHASE_NONEXISTENT
)))) {
408 child
= xopenat_full(fd
,
410 O_DIRECTORY
|O_CREAT
|O_EXCL
|O_NOFOLLOW
|O_PATH
|O_CLOEXEC
,
411 /* xopen_flags = */ 0,
415 } else if (FLAGS_SET(flags
, CHASE_PARENT
) && isempty(todo
)) {
416 if (!path_extend(&done
, first
))
420 } else if (FLAGS_SET(flags
, CHASE_NONEXISTENT
)) {
421 if (!path_extend(&done
, first
, todo
))
430 /* ... and then check what it actually is. */
431 if (fstat(child
, &st_child
) < 0)
434 if (FLAGS_SET(flags
, CHASE_SAFE
) &&
435 unsafe_transition(&st
, &st_child
))
436 return log_unsafe_transition(fd
, child
, path
, flags
);
438 if (FLAGS_SET(flags
, CHASE_NO_AUTOFS
) &&
439 fd_is_fs_type(child
, AUTOFS_SUPER_MAGIC
) > 0)
440 return log_autofs_mount_point(child
, path
, flags
);
442 if (S_ISLNK(st_child
.st_mode
) && !(FLAGS_SET(flags
, CHASE_NOFOLLOW
) && isempty(todo
))) {
443 _cleanup_free_
char *destination
= NULL
;
445 if (FLAGS_SET(flags
, CHASE_PROHIBIT_SYMLINKS
))
446 return log_prohibited_symlink(child
, flags
);
448 /* This is a symlink, in this case read the destination. But let's make sure we
449 * don't follow symlinks without bounds. */
450 if (--max_follow
<= 0)
453 r
= readlinkat_malloc(fd
, first
, &destination
);
456 if (isempty(destination
))
459 if (path_is_absolute(destination
)) {
461 /* An absolute destination. Start the loop from the beginning, but use the
462 * root file descriptor as base. */
465 fd
= fd_reopen(root_fd
, O_CLOEXEC
|O_PATH
|O_DIRECTORY
);
469 if (fstat(fd
, &st
) < 0)
472 if (FLAGS_SET(flags
, CHASE_SAFE
) &&
473 unsafe_transition(&st_child
, &st
))
474 return log_unsafe_transition(child
, fd
, path
, flags
);
476 /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be
477 * outside of the specified dir_fd. Let's make the result absolute. */
478 if (!FLAGS_SET(flags
, CHASE_AT_RESOLVE_IN_ROOT
))
479 need_absolute
= true;
481 r
= free_and_strdup(&done
, need_absolute
? "/" : NULL
);
486 /* Prefix what's left to do with what we just read, and start the loop again, but
487 * remain in the current directory. */
488 if (!path_extend(&destination
, todo
))
491 free_and_replace(buffer
, destination
);
494 if (FLAGS_SET(flags
, CHASE_STEP
))
500 /* If this is not a symlink, then let's just add the name we read to what we already verified. */
501 if (!path_extend(&done
, first
))
504 if (FLAGS_SET(flags
, CHASE_PARENT
) && isempty(todo
))
507 /* And iterate again, but go one directory further down. */
509 close_and_replace(fd
, child
);
512 if (FLAGS_SET(flags
, CHASE_MUST_BE_DIRECTORY
)) {
513 r
= stat_verify_directory(&st
);
518 if (FLAGS_SET(flags
, CHASE_MUST_BE_REGULAR
)) {
519 r
= stat_verify_regular(&st
);
525 if (FLAGS_SET(flags
, CHASE_EXTRACT_FILENAME
) && done
) {
526 _cleanup_free_
char *f
= NULL
;
528 r
= path_extract_filename(done
, &f
);
529 if (r
< 0 && r
!= -EADDRNOTAVAIL
)
532 /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
533 free_and_replace(done
, f
);
537 assert(!need_absolute
|| FLAGS_SET(flags
, CHASE_EXTRACT_FILENAME
));
543 if (append_trail_slash
)
544 if (!strextend(&done
, "/"))
547 *ret_path
= TAKE_PTR(done
);
552 /* Return the O_PATH fd we currently are looking to the caller. It can translate it
553 * to a proper fd by opening /proc/self/fd/xyz. */
555 *ret_fd
= TAKE_FD(fd
);
560 if (FLAGS_SET(flags
, CHASE_STEP
))
570 assert(!need_absolute
);
571 done
= strdup(append_trail_slash
? "./" : ".");
576 /* todo may contain slashes at the beginning. */
577 r
= path_find_first_component(&todo
, /* accept_dot_dot= */ true, &e
);
581 *ret_path
= TAKE_PTR(done
);
585 c
= path_join(done
, e
);
596 int chase(const char *path
, const char *root
, ChaseFlags flags
, char **ret_path
, int *ret_fd
) {
597 _cleanup_free_
char *root_abs
= NULL
, *absolute
= NULL
, *p
= NULL
;
598 _cleanup_close_
int fd
= -EBADF
, pfd
= -EBADF
;
606 r
= empty_or_root_harder_to_null(&root
);
610 /* A root directory of "/" or "" is identical to "/". */
611 if (empty_or_root(root
)) {
614 /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(),
615 * hence below is not necessary, but let's shortcut. */
616 flags
&= ~CHASE_AT_RESOLVE_IN_ROOT
;
619 r
= path_make_absolute_cwd(root
, &root_abs
);
623 /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
624 * end. While we won't resolve the root path we still simplify it. */
625 root
= path_simplify(root_abs
);
627 assert(path_is_absolute(root
));
628 assert(!empty_or_root(root
));
630 if (FLAGS_SET(flags
, CHASE_PREFIX_ROOT
)) {
631 absolute
= path_join(root
, path
);
636 flags
|= CHASE_AT_RESOLVE_IN_ROOT
;
640 r
= path_make_absolute_cwd(path
, &absolute
);
645 path
= path_startswith(absolute
, root
);
647 return log_full_errno(FLAGS_SET(flags
, CHASE_WARN
) ? LOG_WARNING
: LOG_DEBUG
,
648 SYNTHETIC_ERRNO(ECHRNG
),
649 "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
652 fd
= open(root
, O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
656 r
= chaseat(fd
, path
, flags
& ~CHASE_PREFIX_ROOT
, ret_path
? &p
: NULL
, ret_fd
? &pfd
: NULL
);
661 if (!FLAGS_SET(flags
, CHASE_EXTRACT_FILENAME
)) {
663 /* When "root" points to the root directory, the result of chaseat() is always
664 * absolute, hence it is not necessary to prefix with the root. When "root" points to
665 * a non-root directory, the result path is always normalized and relative, hence
666 * we can simply call path_join() and not necessary to call path_simplify().
667 * As a special case, chaseat() may return "." or "./", which are normalized too,
668 * but we need to drop "." before merging with root. */
670 if (empty_or_root(root
))
671 assert(path_is_absolute(p
));
675 assert(!path_is_absolute(p
));
677 q
= path_join(root
, p
+ STR_IN_SET(p
, ".", "./"));
681 free_and_replace(p
, q
);
685 *ret_path
= TAKE_PTR(p
);
689 *ret_fd
= TAKE_FD(pfd
);
694 int chaseat_prefix_root(const char *path
, const char *root
, char **ret
) {
701 /* This is mostly for prefixing the result of chaseat(). */
703 if (!path_is_absolute(path
)) {
704 _cleanup_free_
char *root_abs
= NULL
;
706 r
= empty_or_root_harder_to_null(&root
);
707 if (r
< 0 && r
!= -ENOENT
)
710 /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
711 if (empty_or_root(root
))
714 r
= path_make_absolute_cwd(root
, &root_abs
);
718 root
= path_simplify(root_abs
);
720 q
= path_join(root
, path
+ (path
[0] == '.' && IN_SET(path
[1], '/', '\0')));
730 int chase_extract_filename(const char *path
, const char *root
, char **ret
) {
733 /* This is similar to path_extract_filename(), but takes root directory.
734 * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */
742 if (!path_is_absolute(path
))
745 r
= empty_or_root_harder_to_null(&root
);
746 if (r
< 0 && r
!= -ENOENT
)
749 if (!empty_or_root(root
)) {
750 _cleanup_free_
char *root_abs
= NULL
;
752 r
= path_make_absolute_cwd(root
, &root_abs
);
756 path
= path_startswith(path
, root_abs
);
761 if (!isempty(path
)) {
762 r
= path_extract_filename(path
, ret
);
763 if (r
!= -EADDRNOTAVAIL
)
767 return strdup_to(ret
, ".");
773 ChaseFlags chase_flags
,
777 _cleanup_close_
int path_fd
= -EBADF
;
778 _cleanup_free_
char *p
= NULL
, *fname
= NULL
;
781 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
783 if (empty_or_root(root
) && !ret_path
&&
784 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
785 /* Shortcut this call if none of the special features of this call are requested */
786 return xopenat_full(AT_FDCWD
, path
,
787 open_flags
| (FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? O_NOFOLLOW
: 0),
788 /* xopen_flags = */ 0,
791 r
= chase(path
, root
, CHASE_PARENT
|chase_flags
, &p
, &path_fd
);
794 assert(path_fd
>= 0);
796 if (!FLAGS_SET(chase_flags
, CHASE_PARENT
) &&
797 !FLAGS_SET(chase_flags
, CHASE_EXTRACT_FILENAME
)) {
798 r
= chase_extract_filename(p
, root
, &fname
);
803 r
= xopenat_full(path_fd
, strempty(fname
), open_flags
|O_NOFOLLOW
, /* xopen_flags = */ 0, MODE_INVALID
);
808 *ret_path
= TAKE_PTR(p
);
813 int chase_and_opendir(const char *path
, const char *root
, ChaseFlags chase_flags
, char **ret_path
, DIR **ret_dir
) {
814 _cleanup_close_
int path_fd
= -EBADF
;
815 _cleanup_free_
char *p
= NULL
;
819 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
822 if (empty_or_root(root
) && !ret_path
&&
823 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0) {
824 /* Shortcut this call if none of the special features of this call are requested */
833 r
= chase(path
, root
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
836 assert(path_fd
>= 0);
838 d
= xopendirat(path_fd
, ".", O_NOFOLLOW
);
843 *ret_path
= TAKE_PTR(p
);
849 int chase_and_stat(const char *path
, const char *root
, ChaseFlags chase_flags
, char **ret_path
, struct stat
*ret_stat
) {
850 _cleanup_close_
int path_fd
= -EBADF
;
851 _cleanup_free_
char *p
= NULL
;
855 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
858 if (empty_or_root(root
) && !ret_path
&&
859 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
860 /* Shortcut this call if none of the special features of this call are requested */
861 return RET_NERRNO(fstatat(AT_FDCWD
, path
, ret_stat
,
862 FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? AT_SYMLINK_NOFOLLOW
: 0));
864 r
= chase(path
, root
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
867 assert(path_fd
>= 0);
869 if (fstat(path_fd
, ret_stat
) < 0)
873 *ret_path
= TAKE_PTR(p
);
878 int chase_and_access(const char *path
, const char *root
, ChaseFlags chase_flags
, int access_mode
, char **ret_path
) {
879 _cleanup_close_
int path_fd
= -EBADF
;
880 _cleanup_free_
char *p
= NULL
;
884 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
886 if (empty_or_root(root
) && !ret_path
&&
887 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
888 /* Shortcut this call if none of the special features of this call are requested */
889 return RET_NERRNO(faccessat(AT_FDCWD
, path
, access_mode
,
890 FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? AT_SYMLINK_NOFOLLOW
: 0));
892 r
= chase(path
, root
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
895 assert(path_fd
>= 0);
897 r
= access_fd(path_fd
, access_mode
);
902 *ret_path
= TAKE_PTR(p
);
907 int chase_and_fopen_unlocked(
910 ChaseFlags chase_flags
,
911 const char *open_flags
,
915 _cleanup_free_
char *final_path
= NULL
;
916 _cleanup_close_
int fd
= -EBADF
;
920 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
|CHASE_PARENT
)));
924 mode_flags
= fopen_mode_to_flags(open_flags
);
928 fd
= chase_and_open(path
, root
, chase_flags
, mode_flags
, ret_path
? &final_path
: NULL
);
932 r
= take_fdopen_unlocked(&fd
, open_flags
, ret_file
);
937 *ret_path
= TAKE_PTR(final_path
);
942 int chase_and_unlink(const char *path
, const char *root
, ChaseFlags chase_flags
, int unlink_flags
, char **ret_path
) {
943 _cleanup_free_
char *p
= NULL
, *fname
= NULL
;
944 _cleanup_close_
int fd
= -EBADF
;
948 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
|CHASE_PARENT
)));
950 fd
= chase_and_open(path
, root
, chase_flags
|CHASE_PARENT
|CHASE_NOFOLLOW
, O_PATH
|O_DIRECTORY
|O_CLOEXEC
, &p
);
954 r
= path_extract_filename(p
, &fname
);
958 if (unlinkat(fd
, fname
, unlink_flags
) < 0)
962 *ret_path
= TAKE_PTR(p
);
967 int chase_and_open_parent(const char *path
, const char *root
, ChaseFlags chase_flags
, char **ret_filename
) {
970 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
972 r
= chase(path
, root
, CHASE_PARENT
|CHASE_EXTRACT_FILENAME
|chase_flags
, ret_filename
, &pfd
);
979 int chase_and_openat(
982 ChaseFlags chase_flags
,
986 _cleanup_close_
int path_fd
= -EBADF
;
987 _cleanup_free_
char *p
= NULL
, *fname
= NULL
;
990 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
992 if (dir_fd
== AT_FDCWD
&& !ret_path
&&
993 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
994 /* Shortcut this call if none of the special features of this call are requested */
995 return xopenat_full(dir_fd
, path
,
996 open_flags
| (FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? O_NOFOLLOW
: 0),
997 /* xopen_flags = */ 0,
1000 r
= chaseat(dir_fd
, path
, chase_flags
|CHASE_PARENT
, &p
, &path_fd
);
1004 if (!FLAGS_SET(chase_flags
, CHASE_PARENT
)) {
1005 r
= path_extract_filename(p
, &fname
);
1006 if (r
< 0 && r
!= -EADDRNOTAVAIL
)
1010 r
= xopenat_full(path_fd
, strempty(fname
), open_flags
|O_NOFOLLOW
, /* xopen_flags= */ 0, MODE_INVALID
);
1015 *ret_path
= TAKE_PTR(p
);
1020 int chase_and_opendirat(int dir_fd
, const char *path
, ChaseFlags chase_flags
, char **ret_path
, DIR **ret_dir
) {
1021 _cleanup_close_
int path_fd
= -EBADF
;
1022 _cleanup_free_
char *p
= NULL
;
1026 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
1029 if (dir_fd
== AT_FDCWD
&& !ret_path
&&
1030 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0) {
1031 /* Shortcut this call if none of the special features of this call are requested */
1040 r
= chaseat(dir_fd
, path
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
1043 assert(path_fd
>= 0);
1045 d
= xopendirat(path_fd
, ".", O_NOFOLLOW
);
1050 *ret_path
= TAKE_PTR(p
);
1056 int chase_and_statat(int dir_fd
, const char *path
, ChaseFlags chase_flags
, char **ret_path
, struct stat
*ret_stat
) {
1057 _cleanup_close_
int path_fd
= -EBADF
;
1058 _cleanup_free_
char *p
= NULL
;
1062 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
1065 if (dir_fd
== AT_FDCWD
&& !ret_path
&&
1066 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
1067 /* Shortcut this call if none of the special features of this call are requested */
1068 return RET_NERRNO(fstatat(AT_FDCWD
, path
, ret_stat
,
1069 FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? AT_SYMLINK_NOFOLLOW
: 0));
1071 r
= chaseat(dir_fd
, path
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
1074 assert(path_fd
>= 0);
1076 if (fstat(path_fd
, ret_stat
) < 0)
1080 *ret_path
= TAKE_PTR(p
);
1085 int chase_and_accessat(int dir_fd
, const char *path
, ChaseFlags chase_flags
, int access_mode
, char **ret_path
) {
1086 _cleanup_close_
int path_fd
= -EBADF
;
1087 _cleanup_free_
char *p
= NULL
;
1091 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
1093 if (dir_fd
== AT_FDCWD
&& !ret_path
&&
1094 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
1095 /* Shortcut this call if none of the special features of this call are requested */
1096 return RET_NERRNO(faccessat(AT_FDCWD
, path
, access_mode
,
1097 FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? AT_SYMLINK_NOFOLLOW
: 0));
1099 r
= chaseat(dir_fd
, path
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
1102 assert(path_fd
>= 0);
1104 r
= access_fd(path_fd
, access_mode
);
1109 *ret_path
= TAKE_PTR(p
);
1114 int chase_and_fopenat_unlocked(
1117 ChaseFlags chase_flags
,
1118 const char *open_flags
,
1122 _cleanup_free_
char *final_path
= NULL
;
1123 _cleanup_close_
int fd
= -EBADF
;
1127 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
|CHASE_PARENT
)));
1131 mode_flags
= fopen_mode_to_flags(open_flags
);
1135 fd
= chase_and_openat(dir_fd
, path
, chase_flags
, mode_flags
, ret_path
? &final_path
: NULL
);
1139 r
= take_fdopen_unlocked(&fd
, open_flags
, ret_file
);
1144 *ret_path
= TAKE_PTR(final_path
);
1149 int chase_and_unlinkat(int dir_fd
, const char *path
, ChaseFlags chase_flags
, int unlink_flags
, char **ret_path
) {
1150 _cleanup_free_
char *p
= NULL
, *fname
= NULL
;
1151 _cleanup_close_
int fd
= -EBADF
;
1155 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
|CHASE_PARENT
)));
1157 fd
= chase_and_openat(dir_fd
, path
, chase_flags
|CHASE_PARENT
|CHASE_NOFOLLOW
, O_PATH
|O_DIRECTORY
|O_CLOEXEC
, &p
);
1161 r
= path_extract_filename(p
, &fname
);
1165 if (unlinkat(fd
, fname
, unlink_flags
) < 0)
1169 *ret_path
= TAKE_PTR(p
);
1174 int chase_and_open_parent_at(int dir_fd
, const char *path
, ChaseFlags chase_flags
, char **ret_filename
) {
1177 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
1179 r
= chaseat(dir_fd
, path
, CHASE_PARENT
|CHASE_EXTRACT_FILENAME
|chase_flags
, ret_filename
, &pfd
);