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.",
75 static int chaseat_needs_absolute(int dir_fd
, const char *path
) {
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 */
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
))
101 if (FLAGS_SET(flags
, CHASE_STEP
))
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
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
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
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
);
190 flags
&= ~CHASE_AT_RESOLVE_IN_ROOT
;
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));
208 buffer
= strdup(path
);
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
);
219 bool need_absolute
= r
;
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
);
234 if (fstat(fd
, &st
) < 0)
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
);
243 root_fd
= open("/", O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
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
;
256 r
= path_find_first_component(&todo
, /* accept_dot_dot= */ true, &e
);
259 if (r
== 0) { /* We reached the end. */
260 if (append_trail_slash
)
261 if (!strextend(&done
, "/"))
266 first
= strndup(e
, r
);
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
))
284 fd_parent
= openat(fd
, "..", O_CLOEXEC
|O_NOFOLLOW
|O_PATH
|O_DIRECTORY
);
288 if (fstat(fd_parent
, &st_parent
) < 0)
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
);
299 if (FLAGS_SET(flags
, CHASE_STEP
))
305 r
= path_extract_directory(done
, &parent
);
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
);
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
))
325 /* If we're at the top of "dir_fd", start appending ".." to "done". */
326 if (!path_extend(&done
, ".."))
331 if (FLAGS_SET(flags
, CHASE_STEP
))
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
))
341 /* update fd and stat */
343 close_and_replace(fd
, fd_parent
);
347 /* Otherwise let's see what this is. */
348 child
= r
= RET_NERRNO(openat(fd
, first
, O_CLOEXEC
|O_NOFOLLOW
|O_PATH
));
353 if (!isempty(todo
) && !path_is_safe(todo
))
356 if (FLAGS_SET(flags
, CHASE_MKDIR_0755
) && !isempty(todo
)) {
359 O_DIRECTORY
|O_CREAT
|O_EXCL
|O_NOFOLLOW
|O_CLOEXEC
,
360 /* xopen_flags = */ 0,
364 } else if (FLAGS_SET(flags
, CHASE_PARENT
) && isempty(todo
)) {
365 if (!path_extend(&done
, first
))
369 } else if (FLAGS_SET(flags
, CHASE_NONEXISTENT
)) {
370 if (!path_extend(&done
, first
, todo
))
379 if (fstat(child
, &st_child
) < 0)
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)
401 r
= readlinkat_malloc(fd
, first
, &destination
);
404 if (isempty(destination
))
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. */
413 fd
= fd_reopen(root_fd
, O_CLOEXEC
|O_PATH
|O_DIRECTORY
);
417 if (fstat(fd
, &st
) < 0)
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
);
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
))
439 free_and_replace(buffer
, destination
);
442 if (FLAGS_SET(flags
, CHASE_STEP
))
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
))
452 if (FLAGS_SET(flags
, CHASE_PARENT
) && isempty(todo
))
455 /* And iterate again, but go one directory further down. */
457 close_and_replace(fd
, child
);
460 if (FLAGS_SET(flags
, CHASE_PARENT
)) {
461 r
= stat_verify_directory(&st
);
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
)
474 /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
475 free_and_replace(done
, f
);
479 assert(!need_absolute
|| FLAGS_SET(flags
, CHASE_EXTRACT_FILENAME
));
480 done
= strdup(append_trail_slash
? "./" : ".");
485 *ret_path
= TAKE_PTR(done
);
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. */
493 *ret_fd
= TAKE_FD(fd
);
496 if (FLAGS_SET(flags
, CHASE_STEP
))
506 assert(!need_absolute
);
507 done
= strdup(append_trail_slash
? "./" : ".");
512 /* todo may contain slashes at the beginning. */
513 r
= path_find_first_component(&todo
, /* accept_dot_dot= */ true, &e
);
517 *ret_path
= TAKE_PTR(done
);
521 c
= path_join(done
, e
);
532 static int empty_or_root_to_null(const char **path
) {
537 /* This nullifies the input path when the path is empty or points to "/". */
539 if (empty_or_root(*path
)) {
544 r
= path_is_root(*path
);
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
;
563 r
= empty_or_root_to_null(&root
);
567 /* A root directory of "/" or "" is identical to "/". */
568 if (empty_or_root(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
;
576 r
= path_make_absolute_cwd(root
, &root_abs
);
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
);
593 flags
|= CHASE_AT_RESOLVE_IN_ROOT
;
597 r
= path_make_absolute_cwd(path
, &absolute
);
602 path
= path_startswith(absolute
, root
);
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.",
609 fd
= open(root
, O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
613 r
= chaseat(fd
, path
, flags
& ~CHASE_PREFIX_ROOT
, ret_path
? &p
: NULL
, ret_fd
? &pfd
: NULL
);
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
));
632 assert(!path_is_absolute(p
));
634 q
= path_join(root
, p
+ (*p
== '.'));
638 free_and_replace(p
, q
);
642 *ret_path
= TAKE_PTR(p
);
646 *ret_fd
= TAKE_FD(pfd
);
651 int chaseat_prefix_root(const char *path
, const char *root
, char **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
)
667 /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
668 if (empty_or_root(root
))
671 r
= path_make_absolute_cwd(root
, &root_abs
);
675 root
= path_simplify(root_abs
);
677 q
= path_join(root
, path
+ (path
[0] == '.' && IN_SET(path
[1], '/', '\0')));
687 int chase_extract_filename(const char *path
, const char *root
, char **ret
) {
690 /* This is similar to path_extract_filename(), but takes root directory.
691 * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */
699 if (!path_is_absolute(path
))
702 r
= empty_or_root_to_null(&root
);
703 if (r
< 0 && r
!= -ENOENT
)
706 if (!empty_or_root(root
)) {
707 _cleanup_free_
char *root_abs
= NULL
;
709 r
= path_make_absolute_cwd(root
, &root_abs
);
713 path
= path_startswith(path
, root_abs
);
718 if (!isempty(path
)) {
719 r
= path_extract_filename(path
, ret
);
720 if (r
!= -EADDRNOTAVAIL
)
724 char *fname
= strdup(".");
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;
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,
748 r
= chase(path
, root
, CHASE_PARENT
|chase_flags
, &p
, &path_fd
);
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
);
760 r
= xopenat(path_fd
, strempty(fname
), open_flags
|O_NOFOLLOW
, /* xopen_flags = */ 0, mode
);
765 *ret_path
= TAKE_PTR(p
);
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
;
776 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
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 */
790 r
= chase(path
, root
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
793 assert(path_fd
>= 0);
795 d
= xopendirat(path_fd
, ".", O_NOFOLLOW
);
800 *ret_path
= TAKE_PTR(p
);
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
;
812 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
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
);
824 assert(path_fd
>= 0);
826 if (fstat(path_fd
, ret_stat
) < 0)
830 *ret_path
= TAKE_PTR(p
);
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
;
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
);
852 assert(path_fd
>= 0);
854 r
= access_fd(path_fd
, access_mode
);
859 *ret_path
= TAKE_PTR(p
);
864 int chase_and_fopen_unlocked(
867 ChaseFlags chase_flags
,
868 const char *open_flags
,
872 _cleanup_free_
char *final_path
= NULL
;
873 _cleanup_close_
int fd
= -EBADF
;
877 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
|CHASE_PARENT
)));
881 mode_flags
= fopen_mode_to_flags(open_flags
);
885 fd
= chase_and_open(path
, root
, chase_flags
, mode_flags
, ret_path
? &final_path
: NULL
);
889 r
= take_fdopen_unlocked(&fd
, open_flags
, ret_file
);
894 *ret_path
= TAKE_PTR(final_path
);
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
;
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
);
911 r
= path_extract_filename(p
, &fname
);
915 if (unlinkat(fd
, fname
, unlink_flags
) < 0)
919 *ret_path
= TAKE_PTR(p
);
924 int chase_and_open_parent(const char *path
, const char *root
, ChaseFlags chase_flags
, char **ret_filename
) {
927 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
929 r
= chase(path
, root
, CHASE_PARENT
|CHASE_EXTRACT_FILENAME
|chase_flags
, ret_filename
, &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;
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,
952 r
= chaseat(dir_fd
, path
, chase_flags
|CHASE_PARENT
, &p
, &path_fd
);
956 if (!FLAGS_SET(chase_flags
, CHASE_PARENT
)) {
957 r
= path_extract_filename(p
, &fname
);
958 if (r
< 0 && r
!= -EADDRNOTAVAIL
)
962 r
= xopenat(path_fd
, strempty(fname
), open_flags
|O_NOFOLLOW
, /* xopen_flags = */ 0, mode
);
967 *ret_path
= TAKE_PTR(p
);
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
;
978 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
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 */
992 r
= chaseat(dir_fd
, path
, chase_flags
, ret_path
? &p
: NULL
, &path_fd
);
995 assert(path_fd
>= 0);
997 d
= xopendirat(path_fd
, ".", O_NOFOLLOW
);
1002 *ret_path
= TAKE_PTR(p
);
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
;
1014 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
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
);
1026 assert(path_fd
>= 0);
1028 if (fstat(path_fd
, ret_stat
) < 0)
1032 *ret_path
= TAKE_PTR(p
);
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
;
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
);
1054 assert(path_fd
>= 0);
1056 r
= access_fd(path_fd
, access_mode
);
1061 *ret_path
= TAKE_PTR(p
);
1066 int chase_and_fopenat_unlocked(
1069 ChaseFlags chase_flags
,
1070 const char *open_flags
,
1074 _cleanup_free_
char *final_path
= NULL
;
1075 _cleanup_close_
int fd
= -EBADF
;
1079 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
|CHASE_PARENT
)));
1083 mode_flags
= fopen_mode_to_flags(open_flags
);
1087 fd
= chase_and_openat(dir_fd
, path
, chase_flags
, mode_flags
, ret_path
? &final_path
: NULL
);
1091 r
= take_fdopen_unlocked(&fd
, open_flags
, ret_file
);
1096 *ret_path
= TAKE_PTR(final_path
);
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
;
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
);
1113 r
= path_extract_filename(p
, &fname
);
1117 if (unlinkat(fd
, fname
, unlink_flags
) < 0)
1121 *ret_path
= TAKE_PTR(p
);
1126 int chase_and_open_parent_at(int dir_fd
, const char *path
, ChaseFlags chase_flags
, char **ret_filename
) {
1129 assert(!(chase_flags
& (CHASE_NONEXISTENT
|CHASE_STEP
)));
1131 r
= chaseat(dir_fd
, path
, CHASE_PARENT
|CHASE_EXTRACT_FILENAME
|chase_flags
, ret_filename
, &pfd
);