1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include <linux/magic.h>
5 #include "alloc-util.h"
10 #include "glyph-util.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 */
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
;
31 if (!FLAGS_SET(flags
, CHASE_WARN
))
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
))
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.",
60 static int log_prohibited_symlink(int fd
, ChaseFlags flags
) {
61 _cleanup_free_
char *n1
= NULL
;
65 if (!FLAGS_SET(flags
, CHASE_WARN
))
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.",
82 _cleanup_free_
char *buffer
= NULL
, *done
= NULL
;
83 _cleanup_close_
int fd
= -EBADF
, root_fd
= -EBADF
;
84 unsigned max_follow
= CHASE_MAX
; /* how many symlinks to follow before giving up and returning ELOOP */
85 bool exists
= true, append_trail_slash
= false;
86 struct stat previous_stat
;
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(dir_fd
>= 0 || dir_fd
== AT_FDCWD
);
96 /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
97 if ((flags
& CHASE_NONEXISTENT
))
100 if ((flags
& CHASE_STEP
))
106 /* This function resolves symlinks of the path relative to the given directory file descriptor. If
107 * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
108 * are resolved relative to the given directory file descriptor. Otherwise, they are resolved
109 * relative to the root directory of the host.
111 * Note that when a positive directory file descriptor is provided and CHASE_AT_RESOLVE_IN_ROOT is
112 * specified and we find an absolute symlink, it is resolved relative to given directory file
113 * descriptor and not the root of the host. Also, when following relative symlinks, this functions
114 * ensures they cannot be used to "escape" the given directory file descriptor. If a positive
115 * directory file descriptor is provided, the "path" parameter is always interpreted relative to the
116 * given directory file descriptor, even if it is absolute. If the given directory file descriptor is
117 * AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host.
119 * If "dir_fd" is a valid directory fd, "path" is an absolute path and "ret_path" is not NULL, this
120 * functions returns a relative path in "ret_path" because openat() like functions generally ignore
121 * the directory fd if they are provided with an absolute path. On the other hand, if "dir_fd" is
122 * AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path" because
123 * otherwise, if the caller passes the returned relative path to another openat() like function, it
124 * would be resolved relative to the current working directory instead of to "/".
126 * Algorithmically this operates on two path buffers: "done" are the components of the path we
127 * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we
128 * still need to process. On each iteration, we move one component from "todo" to "done", processing
129 * its special meaning each time. We always keep an O_PATH fd to the component we are currently
130 * processing, thus keeping lookup races to a minimum.
132 * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute
133 * path you got as-is: fully qualified and relative to your host's root. Optionally, specify the
134 * "dir_fd" parameter to tell this function what to do when encountering a symlink with an absolute
135 * path as directory: resolve it relative to the given directory file descriptor.
137 * There are five ways to invoke this function:
139 * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is
140 * returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0
141 * is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is
142 * returned if the destination was found, -ENOENT if it wasn't.
144 * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file
145 * descriptor is returned as return value. This is useful to open files relative to some root
146 * directory. Note that the returned O_PATH file descriptors must be converted into a regular one
147 * (using fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be
148 * combined with CHASE_NONEXISTENT.
150 * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only
151 * the first symlink or ".." component of the path is resolved, and the resulting path is
152 * returned. This is useful if a caller wants to trace the path through the file system verbosely.
153 * Returns < 0 on error, > 0 if the path is fully normalized, and == 0 for each normalization
154 * step. This may be combined with CHASE_NONEXISTENT, in which case 1 is returned when a component
157 * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions
158 * from unprivileged to privileged files or directories. In such cases the return value is
159 * -ENOLINK. If CHASE_WARN is also set, a warning describing the unsafe transition is emitted.
160 * CHASE_WARN cannot be used in PID 1.
162 * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization
163 * is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of
164 * the mount point is emitted. CHASE_WARN cannot be used in PID 1.
167 if (FLAGS_SET(flags
, CHASE_AT_RESOLVE_IN_ROOT
)) {
168 /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
169 * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
172 r
= dir_fd_is_root(dir_fd
);
178 flags
&= ~CHASE_AT_RESOLVE_IN_ROOT
;
182 (CHASE_AT_RESOLVE_IN_ROOT
|CHASE_NONEXISTENT
|CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_STEP
|
183 CHASE_PROHIBIT_SYMLINKS
|CHASE_MKDIR_0755
)) &&
184 !ret_path
&& ret_fd
) {
186 /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
187 * set and doesn't care about any of the other special features we provide either. */
188 r
= openat(dir_fd
, buffer
?: path
, O_PATH
|O_CLOEXEC
|((flags
& CHASE_NOFOLLOW
) ? O_NOFOLLOW
: 0));
197 buffer
= strdup(path
);
202 /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
203 * a relative path would be interpreted relative to the current working directory. */
204 bool need_absolute
= dir_fd
== AT_FDCWD
&& path_is_absolute(path
);
211 /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
212 * directory file descriptor is provided will we look at CHASE_AT_RESOLVE_IN_ROOT to determine
213 * whether to resolve symlinks in it or not. */
214 if (dir_fd
>= 0 && FLAGS_SET(flags
, CHASE_AT_RESOLVE_IN_ROOT
))
215 root_fd
= openat(dir_fd
, ".", O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
217 root_fd
= open("/", O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
221 /* If a positive directory file descriptor is provided, always resolve the given path relative to it,
222 * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat()
223 * semantics, if the path is relative, resolve against the current working directory. Otherwise,
224 * resolve against root. */
225 if (dir_fd
>= 0 || !path_is_absolute(path
))
226 fd
= openat(dir_fd
, ".", O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
228 fd
= open("/", O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
232 if (fstat(fd
, &previous_stat
) < 0)
235 if (flags
& CHASE_TRAIL_SLASH
)
236 append_trail_slash
= ENDSWITH_SET(buffer
, "/", "/.");
238 for (todo
= buffer
;;) {
239 _cleanup_free_
char *first
= NULL
;
240 _cleanup_close_
int child
= -EBADF
;
244 r
= path_find_first_component(&todo
, /* accept_dot_dot= */ true, &e
);
247 if (r
== 0) { /* We reached the end. */
248 if (append_trail_slash
)
249 if (!strextend(&done
, "/"))
254 first
= strndup(e
, r
);
258 /* Two dots? Then chop off the last bit of what we already found out. */
259 if (path_equal(first
, "..")) {
260 _cleanup_free_
char *parent
= NULL
;
261 _cleanup_close_
int fd_parent
= -EBADF
;
263 /* If we already are at the top, then going up will not change anything. This is
264 * in-line with how the kernel handles this. */
265 if (empty_or_root(done
) && FLAGS_SET(flags
, CHASE_AT_RESOLVE_IN_ROOT
))
268 fd_parent
= openat(fd
, "..", O_CLOEXEC
|O_NOFOLLOW
|O_PATH
|O_DIRECTORY
);
272 if (fstat(fd_parent
, &st
) < 0)
275 /* If we opened the same directory, that means we're at the host root directory, so
276 * going up won't change anything. */
277 if (st
.st_dev
== previous_stat
.st_dev
&& st
.st_ino
== previous_stat
.st_ino
)
280 r
= path_extract_directory(done
, &parent
);
281 if (r
>= 0 || r
== -EDESTADDRREQ
)
282 free_and_replace(done
, parent
);
283 else if (IN_SET(r
, -EINVAL
, -EADDRNOTAVAIL
)) {
284 /* If we're at the top of "dir_fd", start appending ".." to "done". */
285 if (!path_extend(&done
, ".."))
290 if (flags
& CHASE_STEP
)
293 if (flags
& CHASE_SAFE
) {
294 if (unsafe_transition(&previous_stat
, &st
))
295 return log_unsafe_transition(fd
, fd_parent
, path
, flags
);
300 if (FLAGS_SET(flags
, CHASE_PARENT
) && isempty(todo
))
303 close_and_replace(fd
, fd_parent
);
308 /* Otherwise let's see what this is. */
309 child
= r
= RET_NERRNO(openat(fd
, first
, O_CLOEXEC
|O_NOFOLLOW
|O_PATH
));
314 if (!isempty(todo
) && !path_is_safe(todo
))
317 if (FLAGS_SET(flags
, CHASE_MKDIR_0755
) && !isempty(todo
)) {
318 child
= xopenat(fd
, first
, O_DIRECTORY
|O_CREAT
|O_EXCL
|O_NOFOLLOW
|O_CLOEXEC
, 0755);
321 } else if (FLAGS_SET(flags
, CHASE_PARENT
) && isempty(todo
)) {
322 if (!path_extend(&done
, first
))
326 } else if (flags
& CHASE_NONEXISTENT
) {
327 if (!path_extend(&done
, first
, todo
))
336 if (fstat(child
, &st
) < 0)
338 if ((flags
& CHASE_SAFE
) &&
339 unsafe_transition(&previous_stat
, &st
))
340 return log_unsafe_transition(fd
, child
, path
, flags
);
344 if ((flags
& CHASE_NO_AUTOFS
) &&
345 fd_is_fs_type(child
, AUTOFS_SUPER_MAGIC
) > 0)
346 return log_autofs_mount_point(child
, path
, flags
);
348 if (S_ISLNK(st
.st_mode
) && !((flags
& CHASE_NOFOLLOW
) && isempty(todo
))) {
349 _cleanup_free_
char *destination
= NULL
;
351 if (flags
& CHASE_PROHIBIT_SYMLINKS
)
352 return log_prohibited_symlink(child
, flags
);
354 /* This is a symlink, in this case read the destination. But let's make sure we
355 * don't follow symlinks without bounds. */
356 if (--max_follow
<= 0)
359 r
= readlinkat_malloc(fd
, first
, &destination
);
362 if (isempty(destination
))
365 if (path_is_absolute(destination
)) {
367 /* An absolute destination. Start the loop from the beginning, but use the
368 * root file descriptor as base. */
371 fd
= fd_reopen(root_fd
, O_CLOEXEC
|O_PATH
|O_DIRECTORY
);
375 if (flags
& CHASE_SAFE
) {
376 if (fstat(fd
, &st
) < 0)
379 if (unsafe_transition(&previous_stat
, &st
))
380 return log_unsafe_transition(child
, fd
, path
, flags
);
385 r
= free_and_strdup(&done
, need_absolute
? "/" : NULL
);
390 /* Prefix what's left to do with what we just read, and start the loop again, but
391 * remain in the current directory. */
392 if (!path_extend(&destination
, todo
))
395 free_and_replace(buffer
, destination
);
398 if (flags
& CHASE_STEP
)
404 /* If this is not a symlink, then let's just add the name we read to what we already verified. */
405 if (!path_extend(&done
, first
))
408 if (FLAGS_SET(flags
, CHASE_PARENT
) && isempty(todo
))
411 /* And iterate again, but go one directory further down. */
412 close_and_replace(fd
, child
);
415 if (flags
& CHASE_PARENT
) {
416 r
= fd_verify_directory(fd
);
422 if (FLAGS_SET(flags
, CHASE_EXTRACT_FILENAME
) && done
) {
423 _cleanup_free_
char *f
= NULL
;
425 r
= path_extract_filename(done
, &f
);
426 if (r
< 0 && r
!= -EDESTADDRREQ
)
429 /* If we get EDESTADDRREQ we clear done and it will get reinitialized by the next block. */
430 free_and_replace(done
, f
);
434 done
= strdup(append_trail_slash
? "./" : ".");
439 *ret_path
= TAKE_PTR(done
);
443 /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
444 * proper fd by opening /proc/self/fd/xyz. */
447 *ret_fd
= TAKE_FD(fd
);
450 if (flags
& CHASE_STEP
)
460 done
= strdup(append_trail_slash
? "./" : ".");
465 /* todo may contain slashes at the beginning. */
466 r
= path_find_first_component(&todo
, /* accept_dot_dot= */ true, &e
);
470 *ret_path
= TAKE_PTR(done
);
474 c
= path_join(done
, e
);
487 const char *original_root
,
492 _cleanup_free_
char *root
= NULL
, *absolute
= NULL
, *p
= NULL
;
493 _cleanup_close_
int fd
= -EBADF
, pfd
= -EBADF
;
501 /* A root directory of "/" or "" is identical to none */
502 if (empty_or_root(original_root
))
503 original_root
= NULL
;
506 r
= path_make_absolute_cwd(original_root
, &root
);
510 /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
511 * end. While we won't resolve the root path we still simplify it. Note that dropping the
512 * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
513 * anyway. Moreover at the end of this function after processing everything we'll always turn
514 * the empty string back to "/". */
515 delete_trailing_chars(root
, "/");
518 if (flags
& CHASE_PREFIX_ROOT
) {
519 absolute
= path_join(root
, path
);
526 r
= path_make_absolute_cwd(path
, &absolute
);
531 path
= path_startswith(absolute
, empty_to_root(root
));
533 return log_full_errno(flags
& CHASE_WARN
? LOG_WARNING
: LOG_DEBUG
,
534 SYNTHETIC_ERRNO(ECHRNG
),
535 "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
536 absolute
, empty_to_root(root
));
538 fd
= open(empty_to_root(root
), O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
542 if (!empty_or_root(root
))
543 flags
|= CHASE_AT_RESOLVE_IN_ROOT
;
545 r
= chaseat(fd
, path
, flags
& ~CHASE_PREFIX_ROOT
, ret_path
? &p
: NULL
, ret_fd
? &pfd
: NULL
);
550 if (!FLAGS_SET(flags
, CHASE_EXTRACT_FILENAME
)) {
551 _cleanup_free_
char *q
= NULL
;
553 q
= path_join(empty_to_root(root
), p
);
559 if (FLAGS_SET(flags
, CHASE_TRAIL_SLASH
) && ENDSWITH_SET(path
, "/", "/."))
560 if (!strextend(&q
, "/"))
563 free_and_replace(p
, q
);
566 *ret_path
= TAKE_PTR(p
);
570 *ret_fd
= TAKE_FD(pfd
);
578 ChaseFlags chase_flags
,
582 _cleanup_close_
int path_fd
= -EBADF
;
583 _cleanup_free_
char *p
= NULL
, *fname
= NULL
;
584 mode_t mode
= open_flags
& O_DIRECTORY
? 0755 : 0644;
588 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
590 if (empty_or_root(root
) && !ret_path
&&
591 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
592 /* Shortcut this call if none of the special features of this call are requested */
593 return RET_NERRNO(xopenat(AT_FDCWD
, path
,
594 open_flags
| (FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? O_NOFOLLOW
: 0),
597 r
= chase(path
, root
, CHASE_PARENT
|chase_flags
, &p
, &path_fd
);
600 assert(path_fd
>= 0);
602 assert_se(q
= path_startswith(p
, empty_to_root(root
)));
606 r
= path_extract_filename(q
, &fname
);
607 if (r
< 0 && r
!= -EADDRNOTAVAIL
)
610 if (FLAGS_SET(chase_flags
, CHASE_PARENT
) || r
== -EADDRNOTAVAIL
)
611 r
= fd_reopen(path_fd
, open_flags
);
613 r
= xopenat(path_fd
, fname
, open_flags
|O_NOFOLLOW
, mode
);
618 *ret_path
= TAKE_PTR(p
);
623 int chase_and_opendir(
626 ChaseFlags chase_flags
,
630 _cleanup_close_
int path_fd
= -EBADF
;
631 _cleanup_free_
char *p
= NULL
;
635 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
638 if (empty_or_root(root
) && !ret_path
&&
639 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0) {
640 /* Shortcut this call if none of the special features of this call are requested */
649 r
= chase(path
, root
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
652 assert(path_fd
>= 0);
654 d
= xopendirat(path_fd
, ".", O_NOFOLLOW
);
659 *ret_path
= TAKE_PTR(p
);
668 ChaseFlags chase_flags
,
670 struct stat
*ret_stat
) {
672 _cleanup_close_
int path_fd
= -EBADF
;
673 _cleanup_free_
char *p
= NULL
;
677 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
680 if (empty_or_root(root
) && !ret_path
&&
681 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
682 /* Shortcut this call if none of the special features of this call are requested */
683 return RET_NERRNO(fstatat(AT_FDCWD
, path
, ret_stat
,
684 FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? AT_SYMLINK_NOFOLLOW
: 0));
686 r
= chase(path
, root
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
689 assert(path_fd
>= 0);
691 if (fstat(path_fd
, ret_stat
) < 0)
695 *ret_path
= TAKE_PTR(p
);
700 int chase_and_access(
703 ChaseFlags chase_flags
,
707 _cleanup_close_
int path_fd
= -EBADF
;
708 _cleanup_free_
char *p
= NULL
;
712 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
714 if (empty_or_root(root
) && !ret_path
&&
715 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
716 /* Shortcut this call if none of the special features of this call are requested */
717 return RET_NERRNO(faccessat(AT_FDCWD
, path
, access_mode
,
718 FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? AT_SYMLINK_NOFOLLOW
: 0));
720 r
= chase(path
, root
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
723 assert(path_fd
>= 0);
725 r
= access_fd(path_fd
, access_mode
);
730 *ret_path
= TAKE_PTR(p
);
735 int chase_and_fopen_unlocked(
738 ChaseFlags chase_flags
,
739 const char *open_flags
,
743 _cleanup_free_
char *final_path
= NULL
;
744 _cleanup_close_
int fd
= -EBADF
;
748 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
|CHASE_PARENT
)));
752 mode_flags
= fopen_mode_to_flags(open_flags
);
756 fd
= chase_and_open(path
, root
, chase_flags
, mode_flags
, ret_path
? &final_path
: NULL
);
760 r
= take_fdopen_unlocked(&fd
, open_flags
, ret_file
);
765 *ret_path
= TAKE_PTR(final_path
);
770 int chase_and_unlink(
773 ChaseFlags chase_flags
,
777 _cleanup_free_
char *p
= NULL
, *fname
= NULL
;
778 _cleanup_close_
int fd
= -EBADF
;
782 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
|CHASE_PARENT
)));
784 fd
= chase_and_open(path
, root
, chase_flags
|CHASE_PARENT
|CHASE_NOFOLLOW
, O_PATH
|O_DIRECTORY
|O_CLOEXEC
, &p
);
788 r
= path_extract_filename(p
, &fname
);
792 if (unlinkat(fd
, fname
, unlink_flags
) < 0)
796 *ret_path
= TAKE_PTR(p
);
801 int chase_and_open_parent(const char *path
, const char *root
, ChaseFlags chase_flags
, char **ret_filename
) {
804 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
806 r
= chase(path
, root
, CHASE_PARENT
|CHASE_EXTRACT_FILENAME
|chase_flags
, ret_filename
, &pfd
);
813 int chase_and_openat(
816 ChaseFlags chase_flags
,
820 _cleanup_close_
int path_fd
= -EBADF
;
821 _cleanup_free_
char *p
= NULL
, *fname
= NULL
;
822 mode_t mode
= open_flags
& O_DIRECTORY
? 0755 : 0644;
825 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
827 if (dir_fd
== AT_FDCWD
&& !ret_path
&&
828 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
829 /* Shortcut this call if none of the special features of this call are requested */
830 return RET_NERRNO(xopenat(dir_fd
, path
,
831 open_flags
| (FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? O_NOFOLLOW
: 0),
834 r
= chaseat(dir_fd
, path
, chase_flags
|CHASE_PARENT
, &p
, &path_fd
);
838 r
= path_extract_filename(p
, &fname
);
839 if (r
< 0 && r
!= -EDESTADDRREQ
)
842 if (FLAGS_SET(chase_flags
, CHASE_PARENT
) || r
== -EDESTADDRREQ
)
843 r
= fd_reopen(path_fd
, open_flags
);
845 r
= xopenat(path_fd
, fname
, open_flags
|O_NOFOLLOW
, mode
);
850 *ret_path
= TAKE_PTR(p
);
855 int chase_and_opendirat(
858 ChaseFlags chase_flags
,
862 _cleanup_close_
int path_fd
= -EBADF
;
863 _cleanup_free_
char *p
= NULL
;
867 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
870 if (dir_fd
== AT_FDCWD
&& !ret_path
&&
871 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0) {
872 /* Shortcut this call if none of the special features of this call are requested */
881 r
= chaseat(dir_fd
, path
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
884 assert(path_fd
>= 0);
886 d
= xopendirat(path_fd
, ".", O_NOFOLLOW
);
891 *ret_path
= TAKE_PTR(p
);
897 int chase_and_statat(
900 ChaseFlags chase_flags
,
902 struct stat
*ret_stat
) {
904 _cleanup_close_
int path_fd
= -EBADF
;
905 _cleanup_free_
char *p
= NULL
;
909 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
912 if (dir_fd
== AT_FDCWD
&& !ret_path
&&
913 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
914 /* Shortcut this call if none of the special features of this call are requested */
915 return RET_NERRNO(fstatat(AT_FDCWD
, path
, ret_stat
,
916 FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? AT_SYMLINK_NOFOLLOW
: 0));
918 r
= chaseat(dir_fd
, path
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
921 assert(path_fd
>= 0);
923 if (fstat(path_fd
, ret_stat
) < 0)
927 *ret_path
= TAKE_PTR(p
);
932 int chase_and_accessat(
935 ChaseFlags chase_flags
,
939 _cleanup_close_
int path_fd
= -EBADF
;
940 _cleanup_free_
char *p
= NULL
;
944 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
946 if (dir_fd
== AT_FDCWD
&& !ret_path
&&
947 (chase_flags
& (CHASE_NO_AUTOFS
|CHASE_SAFE
|CHASE_PROHIBIT_SYMLINKS
|CHASE_PARENT
|CHASE_MKDIR_0755
)) == 0)
948 /* Shortcut this call if none of the special features of this call are requested */
949 return RET_NERRNO(faccessat(AT_FDCWD
, path
, access_mode
,
950 FLAGS_SET(chase_flags
, CHASE_NOFOLLOW
) ? AT_SYMLINK_NOFOLLOW
: 0));
952 r
= chaseat(dir_fd
, path
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
955 assert(path_fd
>= 0);
957 r
= access_fd(path_fd
, access_mode
);
962 *ret_path
= TAKE_PTR(p
);
967 int chase_and_fopenat_unlocked(
970 ChaseFlags chase_flags
,
971 const char *open_flags
,
975 _cleanup_free_
char *final_path
= NULL
;
976 _cleanup_close_
int fd
= -EBADF
;
980 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
|CHASE_PARENT
)));
984 mode_flags
= fopen_mode_to_flags(open_flags
);
988 fd
= chase_and_openat(dir_fd
, path
, chase_flags
, mode_flags
, ret_path
? &final_path
: NULL
);
992 r
= take_fdopen_unlocked(&fd
, open_flags
, ret_file
);
997 *ret_path
= TAKE_PTR(final_path
);
1002 int chase_and_unlinkat(
1005 ChaseFlags chase_flags
,
1009 _cleanup_free_
char *p
= NULL
, *fname
= NULL
;
1010 _cleanup_close_
int fd
= -EBADF
;
1014 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
|CHASE_PARENT
)));
1016 fd
= chase_and_openat(dir_fd
, path
, chase_flags
|CHASE_PARENT
|CHASE_NOFOLLOW
, O_PATH
|O_DIRECTORY
|O_CLOEXEC
, &p
);
1020 r
= path_extract_filename(p
, &fname
);
1024 if (unlinkat(fd
, fname
, unlink_flags
) < 0)
1028 *ret_path
= TAKE_PTR(p
);
1033 int chase_and_open_parent_at(
1036 ChaseFlags chase_flags
,
1037 char **ret_filename
) {
1041 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
1043 r
= chaseat(dir_fd
, path
, CHASE_PARENT
|CHASE_EXTRACT_FILENAME
|chase_flags
, ret_filename
, &pfd
);