]>
Commit | Line | Data |
---|---|---|
f4351959 LP |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include <linux/magic.h> | |
c5de7b14 | 4 | #include <sys/mount.h> |
0c15577a | 5 | #include <unistd.h> |
f4351959 LP |
6 | |
7 | #include "alloc-util.h" | |
f461a28d | 8 | #include "chase.h" |
fda65211 | 9 | #include "errno-util.h" |
f4351959 | 10 | #include "fd-util.h" |
01bebba3 | 11 | #include "fileio.h" |
f4351959 LP |
12 | #include "fs-util.h" |
13 | #include "glyph-util.h" | |
14 | #include "log.h" | |
15 | #include "path-util.h" | |
0c15577a | 16 | #include "stat-util.h" |
f4351959 | 17 | #include "string-util.h" |
d9ccf6b3 | 18 | #include "strv.h" |
f4351959 LP |
19 | #include "user-util.h" |
20 | ||
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. */ | |
25 | ||
26 | if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */ | |
27 | return false; | |
28 | ||
29 | return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */ | |
30 | } | |
31 | ||
f461a28d | 32 | static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) { |
f4351959 LP |
33 | _cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL; |
34 | struct stat st; | |
35 | ||
36 | if (!FLAGS_SET(flags, CHASE_WARN)) | |
37 | return -ENOLINK; | |
38 | ||
39 | (void) fd_get_path(a, &n1); | |
40 | (void) fd_get_path(b, &n2); | |
41 | ||
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); | |
46 | ||
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.", | |
1ae9b0cf | 49 | strna(n1), strna(user_a), glyph(GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path); |
f4351959 LP |
50 | } |
51 | ||
f461a28d | 52 | static int log_autofs_mount_point(int fd, const char *path, ChaseFlags flags) { |
f4351959 LP |
53 | _cleanup_free_ char *n1 = NULL; |
54 | ||
55 | if (!FLAGS_SET(flags, CHASE_WARN)) | |
56 | return -EREMOTE; | |
57 | ||
58 | (void) fd_get_path(fd, &n1); | |
59 | ||
60 | return log_warning_errno(SYNTHETIC_ERRNO(EREMOTE), | |
61 | "Detected autofs mount point %s during canonicalization of %s.", | |
62 | strna(n1), path); | |
63 | } | |
64 | ||
f461a28d | 65 | static int log_prohibited_symlink(int fd, ChaseFlags flags) { |
d43e78b6 LP |
66 | _cleanup_free_ char *n1 = NULL; |
67 | ||
68 | assert(fd >= 0); | |
69 | ||
70 | if (!FLAGS_SET(flags, CHASE_WARN)) | |
71 | return -EREMCHG; | |
72 | ||
73 | (void) fd_get_path(fd, &n1); | |
74 | ||
75 | return log_warning_errno(SYNTHETIC_ERRNO(EREMCHG), | |
76 | "Detected symlink where not symlink is allowed at %s, refusing.", | |
77 | strna(n1)); | |
78 | } | |
79 | ||
1320e562 MY |
80 | static int openat_opath_with_automount(int dir_fd, const char *path, bool automount) { |
81 | static bool can_open_tree = true; | |
82 | int r; | |
83 | ||
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. | |
89 | * | |
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. */ | |
93 | ||
94 | assert(dir_fd >= 0 || dir_fd == AT_FDCWD); | |
95 | assert(path); | |
96 | ||
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))) | |
100 | return r; | |
101 | ||
102 | can_open_tree = false; | |
103 | } | |
104 | ||
105 | return RET_NERRNO(openat(dir_fd, path, O_PATH|O_NOFOLLOW|O_CLOEXEC)); | |
106 | } | |
107 | ||
4445242a YW |
108 | static int chaseat_needs_absolute(int dir_fd, const char *path) { |
109 | if (dir_fd < 0) | |
110 | return path_is_absolute(path); | |
111 | ||
112 | return dir_fd_is_root(dir_fd); | |
113 | } | |
114 | ||
11659e48 | 115 | int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) { |
5bc244aa | 116 | _cleanup_free_ char *buffer = NULL, *done = NULL; |
254d1313 | 117 | _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; |
f461a28d | 118 | unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */ |
f4351959 | 119 | bool exists = true, append_trail_slash = false; |
ad66c7f1 | 120 | struct stat st; /* stat obtained from fd */ |
f4351959 LP |
121 | const char *todo; |
122 | int r; | |
123 | ||
5bc244aa | 124 | assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); |
90b9f7a0 | 125 | assert(!FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR)); |
63bfd52f | 126 | assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME)); |
5bc244aa | 127 | assert(dir_fd >= 0 || dir_fd == AT_FDCWD); |
f4351959 | 128 | |
5a2f674a | 129 | if (FLAGS_SET(flags, CHASE_STEP)) |
ea8282b6 | 130 | assert(!ret_fd); |
f4351959 LP |
131 | |
132 | if (isempty(path)) | |
5bc244aa | 133 | path = "."; |
f4351959 | 134 | |
5bc244aa | 135 | /* This function resolves symlinks of the path relative to the given directory file descriptor. If |
f461a28d | 136 | * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks |
c677e13c DDM |
137 | * are resolved relative to the given directory file descriptor. Otherwise, they are resolved |
138 | * relative to the root directory of the host. | |
5bc244aa | 139 | * |
c677e13c DDM |
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. | |
f4351959 | 147 | * |
b3ef56bc YW |
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 "/". | |
156 | * | |
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 | |
161 | * → relative | |
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 | |
165 | * → absolute | |
166 | * - "dir_fd" is AT_FDCWD, and "path" is relative | |
167 | * → relative when all resolved symlinks are relative, otherwise absolute | |
f4351959 | 168 | * |
5bc244aa DDM |
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 | |
18fe76eb | 172 | * its special meaning each time. We always keep an O_PATH fd to the component we are currently |
5bc244aa | 173 | * processing, thus keeping lookup races to a minimum. |
f4351959 | 174 | * |
5bc244aa DDM |
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. | |
f4351959 LP |
179 | * |
180 | * There are five ways to invoke this function: | |
181 | * | |
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. | |
186 | * | |
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 | |
5bc244aa DDM |
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. | |
f4351959 | 192 | * |
5bc244aa DDM |
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 | |
198 | * is not found. | |
f4351959 | 199 | * |
5bc244aa DDM |
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. | |
f4351959 LP |
204 | * |
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 | |
9154bd57 | 207 | * the mount point is emitted. CHASE_WARN cannot be used in PID 1. |
f4351959 LP |
208 | */ |
209 | ||
e115daa6 YW |
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. */ | |
213 | ||
e212f422 | 214 | r = dir_fd_is_root_or_cwd(dir_fd); |
e115daa6 YW |
215 | if (r < 0) |
216 | return r; | |
217 | if (r > 0) | |
218 | flags &= ~CHASE_AT_RESOLVE_IN_ROOT; | |
219 | } | |
220 | ||
577efd82 DDM |
221 | if (!(flags & |
222 | (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP| | |
87333bd1 | 223 | CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_PARENT)) && |
577efd82 | 224 | !ret_path && ret_fd) { |
f4351959 | 225 | |
5bc244aa DDM |
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. */ | |
5a2f674a | 228 | r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0)); |
f4351959 LP |
229 | if (r < 0) |
230 | return -errno; | |
231 | ||
232 | *ret_fd = r; | |
233 | return 0; | |
234 | } | |
235 | ||
db0096f2 YW |
236 | buffer = strdup(path); |
237 | if (!buffer) | |
238 | return -ENOMEM; | |
f4351959 | 239 | |
c677e13c | 240 | /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because |
c0552b35 YW |
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. */ | |
4445242a YW |
243 | r = chaseat_needs_absolute(dir_fd, path); |
244 | if (r < 0) | |
245 | return r; | |
246 | ||
247 | bool need_absolute = r; | |
5bc244aa DDM |
248 | if (need_absolute) { |
249 | done = strdup("/"); | |
250 | if (!done) | |
251 | return -ENOMEM; | |
f4351959 LP |
252 | } |
253 | ||
c677e13c DDM |
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. */ | |
00a050b3 | 258 | fd = openat(dir_fd, done ?: ".", O_CLOEXEC|O_DIRECTORY|O_PATH); |
f4351959 LP |
259 | if (fd < 0) |
260 | return -errno; | |
261 | ||
ad66c7f1 | 262 | if (fstat(fd, &st) < 0) |
5bc244aa | 263 | return -errno; |
f4351959 | 264 | |
00a050b3 YW |
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); | |
270 | else | |
271 | root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH); | |
272 | if (root_fd < 0) | |
273 | return -errno; | |
274 | ||
90b9f7a0 LP |
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; | |
281 | ||
282 | if (FLAGS_SET(flags, CHASE_PARENT)) | |
283 | flags |= CHASE_MUST_BE_DIRECTORY; | |
f4351959 | 284 | |
5bc244aa | 285 | for (todo = buffer;;) { |
f4351959 | 286 | _cleanup_free_ char *first = NULL; |
5bb1d7fb | 287 | _cleanup_close_ int child = -EBADF; |
ad66c7f1 | 288 | struct stat st_child; |
f4351959 LP |
289 | const char *e; |
290 | ||
860f4c6a | 291 | r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e); |
f4351959 LP |
292 | if (r < 0) |
293 | return r; | |
3351c0a5 | 294 | if (r == 0) /* We reached the end. */ |
f4351959 | 295 | break; |
f4351959 LP |
296 | |
297 | first = strndup(e, r); | |
298 | if (!first) | |
299 | return -ENOMEM; | |
300 | ||
301 | /* Two dots? Then chop off the last bit of what we already found out. */ | |
b0afd29f | 302 | if (streq(first, "..")) { |
f4351959 | 303 | _cleanup_free_ char *parent = NULL; |
254d1313 | 304 | _cleanup_close_ int fd_parent = -EBADF; |
ad66c7f1 | 305 | struct stat st_parent; |
f4351959 | 306 | |
5bc244aa DDM |
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. */ | |
5f0bae7b YW |
309 | if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { |
310 | if (FLAGS_SET(flags, CHASE_STEP)) | |
311 | goto chased_one; | |
f4351959 | 312 | continue; |
5f0bae7b | 313 | } |
f4351959 | 314 | |
5bc244aa DDM |
315 | fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY); |
316 | if (fd_parent < 0) | |
317 | return -errno; | |
f4351959 | 318 | |
ad66c7f1 | 319 | if (fstat(fd_parent, &st_parent) < 0) |
5bc244aa DDM |
320 | return -errno; |
321 | ||
a6ef5ef7 YW |
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, | |
5bc244aa | 324 | * going up won't change anything. */ |
a6ef5ef7 YW |
325 | if (stat_inode_same(&st_parent, &st)) { |
326 | r = dir_fd_is_root(fd); | |
327 | if (r < 0) | |
328 | return r; | |
5f0bae7b YW |
329 | if (r > 0) { |
330 | if (FLAGS_SET(flags, CHASE_STEP)) | |
331 | goto chased_one; | |
a6ef5ef7 | 332 | continue; |
5f0bae7b | 333 | } |
a6ef5ef7 | 334 | } |
f4351959 | 335 | |
5bc244aa | 336 | r = path_extract_directory(done, &parent); |
4de5b4e3 YW |
337 | if (r >= 0) { |
338 | assert(!need_absolute || path_is_absolute(parent)); | |
5bc244aa | 339 | free_and_replace(done, parent); |
4de5b4e3 YW |
340 | } else if (r == -EDESTADDRREQ) { |
341 | /* 'done' contains filename only (i.e. no slash). */ | |
342 | assert(!need_absolute); | |
343 | done = mfree(done); | |
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)); | |
352 | ||
353 | if (!path_is_valid(done)) | |
354 | return -EINVAL; | |
355 | ||
5bc244aa DDM |
356 | /* If we're at the top of "dir_fd", start appending ".." to "done". */ |
357 | if (!path_extend(&done, "..")) | |
358 | return -ENOMEM; | |
359 | } else | |
360 | return r; | |
f4351959 | 361 | |
5a2f674a | 362 | if (FLAGS_SET(flags, CHASE_STEP)) |
f4351959 LP |
363 | goto chased_one; |
364 | ||
5a2f674a | 365 | if (FLAGS_SET(flags, CHASE_SAFE) && |
ad66c7f1 YW |
366 | unsafe_transition(&st, &st_parent)) |
367 | return log_unsafe_transition(fd, fd_parent, path, flags); | |
f4351959 | 368 | |
9c21cfdd LP |
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; | |
375 | ||
376 | fd_grandparent = openat(fd_parent, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY); | |
377 | if (fd_grandparent < 0) | |
378 | return -errno; | |
379 | ||
380 | if (fstat(fd_grandparent, &st_grandparent) < 0) | |
381 | return -errno; | |
382 | ||
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); | |
386 | ||
387 | st = st_grandparent; | |
388 | close_and_replace(fd, fd_grandparent); | |
47f0e1b5 | 389 | break; |
9c21cfdd | 390 | } |
47f0e1b5 | 391 | |
ad66c7f1 YW |
392 | /* update fd and stat */ |
393 | st = st_parent; | |
ee3455cf | 394 | close_and_replace(fd, fd_parent); |
f4351959 LP |
395 | continue; |
396 | } | |
397 | ||
1320e562 MY |
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)); | |
e864dfa6 DDM |
400 | if (r < 0) { |
401 | if (r != -ENOENT) | |
402 | return r; | |
403 | ||
e49e76d6 | 404 | if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */ |
e864dfa6 | 405 | return r; |
f4351959 | 406 | |
e49e76d6 | 407 | if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) { |
e40b11be YW |
408 | child = xopenat_full(fd, |
409 | first, | |
e49e76d6 | 410 | O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC, |
e40b11be YW |
411 | /* xopen_flags = */ 0, |
412 | 0755); | |
47f0e1b5 DDM |
413 | if (child < 0) |
414 | return child; | |
415 | } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) { | |
416 | if (!path_extend(&done, first)) | |
417 | return -ENOMEM; | |
418 | ||
419 | break; | |
5a2f674a | 420 | } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) { |
f4351959 LP |
421 | if (!path_extend(&done, first, todo)) |
422 | return -ENOMEM; | |
423 | ||
424 | exists = false; | |
425 | break; | |
47f0e1b5 | 426 | } else |
e864dfa6 | 427 | return r; |
f4351959 LP |
428 | } |
429 | ||
1320e562 | 430 | /* ... and then check what it actually is. */ |
ad66c7f1 | 431 | if (fstat(child, &st_child) < 0) |
f4351959 | 432 | return -errno; |
ad66c7f1 | 433 | |
5a2f674a | 434 | if (FLAGS_SET(flags, CHASE_SAFE) && |
ad66c7f1 | 435 | unsafe_transition(&st, &st_child)) |
f4351959 LP |
436 | return log_unsafe_transition(fd, child, path, flags); |
437 | ||
5a2f674a | 438 | if (FLAGS_SET(flags, CHASE_NO_AUTOFS) && |
f4351959 LP |
439 | fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0) |
440 | return log_autofs_mount_point(child, path, flags); | |
441 | ||
5a2f674a | 442 | if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) { |
f4351959 LP |
443 | _cleanup_free_ char *destination = NULL; |
444 | ||
5a2f674a | 445 | if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS)) |
d43e78b6 LP |
446 | return log_prohibited_symlink(child, flags); |
447 | ||
f4351959 LP |
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) | |
451 | return -ELOOP; | |
452 | ||
453 | r = readlinkat_malloc(fd, first, &destination); | |
454 | if (r < 0) | |
455 | return r; | |
456 | if (isempty(destination)) | |
457 | return -EINVAL; | |
458 | ||
459 | if (path_is_absolute(destination)) { | |
460 | ||
5bc244aa DDM |
461 | /* An absolute destination. Start the loop from the beginning, but use the |
462 | * root file descriptor as base. */ | |
f4351959 LP |
463 | |
464 | safe_close(fd); | |
5bc244aa | 465 | fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY); |
f4351959 | 466 | if (fd < 0) |
5bc244aa | 467 | return fd; |
f4351959 | 468 | |
ad66c7f1 YW |
469 | if (fstat(fd, &st) < 0) |
470 | return -errno; | |
f4351959 | 471 | |
5a2f674a | 472 | if (FLAGS_SET(flags, CHASE_SAFE) && |
ad66c7f1 YW |
473 | unsafe_transition(&st_child, &st)) |
474 | return log_unsafe_transition(child, fd, path, flags); | |
f4351959 | 475 | |
24be89eb YW |
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; | |
480 | ||
5bc244aa | 481 | r = free_and_strdup(&done, need_absolute ? "/" : NULL); |
f4351959 LP |
482 | if (r < 0) |
483 | return r; | |
484 | } | |
485 | ||
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)) | |
489 | return -ENOMEM; | |
490 | ||
491 | free_and_replace(buffer, destination); | |
492 | todo = buffer; | |
493 | ||
5a2f674a | 494 | if (FLAGS_SET(flags, CHASE_STEP)) |
f4351959 LP |
495 | goto chased_one; |
496 | ||
497 | continue; | |
498 | } | |
499 | ||
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)) | |
502 | return -ENOMEM; | |
503 | ||
47f0e1b5 DDM |
504 | if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) |
505 | break; | |
506 | ||
f4351959 | 507 | /* And iterate again, but go one directory further down. */ |
ad66c7f1 | 508 | st = st_child; |
ee3455cf | 509 | close_and_replace(fd, child); |
f4351959 LP |
510 | } |
511 | ||
90b9f7a0 | 512 | if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) { |
75adfc3b | 513 | r = stat_verify_directory(&st); |
28aa650e DDM |
514 | if (r < 0) |
515 | return r; | |
516 | } | |
517 | ||
90b9f7a0 LP |
518 | if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) { |
519 | r = stat_verify_regular(&st); | |
520 | if (r < 0) | |
521 | return r; | |
522 | } | |
523 | ||
7bf4057d | 524 | if (ret_path) { |
63bfd52f DDM |
525 | if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) { |
526 | _cleanup_free_ char *f = NULL; | |
527 | ||
528 | r = path_extract_filename(done, &f); | |
21eac258 | 529 | if (r < 0 && r != -EADDRNOTAVAIL) |
63bfd52f DDM |
530 | return r; |
531 | ||
21eac258 | 532 | /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */ |
63bfd52f DDM |
533 | free_and_replace(done, f); |
534 | } | |
535 | ||
7bf4057d | 536 | if (!done) { |
1c13bdf3 | 537 | assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)); |
3351c0a5 | 538 | done = strdup("."); |
7bf4057d DDM |
539 | if (!done) |
540 | return -ENOMEM; | |
541 | } | |
542 | ||
3351c0a5 LP |
543 | if (append_trail_slash) |
544 | if (!strextend(&done, "/")) | |
545 | return -ENOMEM; | |
546 | ||
f4351959 | 547 | *ret_path = TAKE_PTR(done); |
7bf4057d | 548 | } |
f4351959 LP |
549 | |
550 | if (ret_fd) { | |
628f45f4 YW |
551 | if (exists) { |
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. */ | |
554 | assert(fd >= 0); | |
555 | *ret_fd = TAKE_FD(fd); | |
556 | } else | |
557 | *ret_fd = -EBADF; | |
f4351959 LP |
558 | } |
559 | ||
5a2f674a | 560 | if (FLAGS_SET(flags, CHASE_STEP)) |
f4351959 LP |
561 | return 1; |
562 | ||
563 | return exists; | |
564 | ||
565 | chased_one: | |
566 | if (ret_path) { | |
567 | const char *e; | |
568 | ||
7bf4057d | 569 | if (!done) { |
1c13bdf3 | 570 | assert(!need_absolute); |
7bf4057d DDM |
571 | done = strdup(append_trail_slash ? "./" : "."); |
572 | if (!done) | |
573 | return -ENOMEM; | |
574 | } | |
575 | ||
f4351959 | 576 | /* todo may contain slashes at the beginning. */ |
860f4c6a | 577 | r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e); |
f4351959 LP |
578 | if (r < 0) |
579 | return r; | |
580 | if (r == 0) | |
581 | *ret_path = TAKE_PTR(done); | |
582 | else { | |
583 | char *c; | |
584 | ||
585 | c = path_join(done, e); | |
586 | if (!c) | |
587 | return -ENOMEM; | |
588 | ||
589 | *ret_path = c; | |
590 | } | |
591 | } | |
592 | ||
593 | return 0; | |
594 | } | |
595 | ||
237bf933 YW |
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; | |
254d1313 | 598 | _cleanup_close_ int fd = -EBADF, pfd = -EBADF; |
5bc244aa DDM |
599 | int r; |
600 | ||
601 | assert(path); | |
602 | ||
603 | if (isempty(path)) | |
604 | return -EINVAL; | |
605 | ||
8ce46363 | 606 | r = empty_or_root_harder_to_null(&root); |
83c57d8c YW |
607 | if (r < 0) |
608 | return r; | |
609 | ||
237bf933 | 610 | /* A root directory of "/" or "" is identical to "/". */ |
b7e957d3 | 611 | if (empty_or_root(root)) { |
237bf933 | 612 | root = "/"; |
b7e957d3 YW |
613 | |
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; | |
617 | ||
618 | } else { | |
237bf933 | 619 | r = path_make_absolute_cwd(root, &root_abs); |
5bc244aa DDM |
620 | if (r < 0) |
621 | return r; | |
622 | ||
623 | /* Simplify the root directory, so that it has no duplicate slashes and nothing at the | |
0d68cd72 | 624 | * end. While we won't resolve the root path we still simplify it. */ |
237bf933 | 625 | root = path_simplify(root_abs); |
5bc244aa | 626 | |
0d68cd72 YW |
627 | assert(path_is_absolute(root)); |
628 | assert(!empty_or_root(root)); | |
629 | ||
5a2f674a | 630 | if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) { |
5bc244aa DDM |
631 | absolute = path_join(root, path); |
632 | if (!absolute) | |
633 | return -ENOMEM; | |
634 | } | |
0d68cd72 YW |
635 | |
636 | flags |= CHASE_AT_RESOLVE_IN_ROOT; | |
5bc244aa DDM |
637 | } |
638 | ||
639 | if (!absolute) { | |
640 | r = path_make_absolute_cwd(path, &absolute); | |
641 | if (r < 0) | |
642 | return r; | |
643 | } | |
644 | ||
237bf933 | 645 | path = path_startswith(absolute, root); |
accc26a0 | 646 | if (!path) |
5a2f674a | 647 | return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG, |
6d5d3e20 YW |
648 | SYNTHETIC_ERRNO(ECHRNG), |
649 | "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.", | |
237bf933 | 650 | absolute, root); |
accc26a0 | 651 | |
237bf933 | 652 | fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH); |
accc26a0 DDM |
653 | if (fd < 0) |
654 | return -errno; | |
5bc244aa | 655 | |
8bf26bfe | 656 | r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL); |
5bc244aa DDM |
657 | if (r < 0) |
658 | return r; | |
659 | ||
660 | if (ret_path) { | |
63bfd52f | 661 | if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) { |
0a9b8878 | 662 | |
9a0dcf03 YW |
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(). | |
7efaab48 DDM |
667 | * As a special case, chaseat() may return "." or "./", which are normalized too, |
668 | * but we need to drop "." before merging with root. */ | |
9a0dcf03 YW |
669 | |
670 | if (empty_or_root(root)) | |
671 | assert(path_is_absolute(p)); | |
672 | else { | |
673 | char *q; | |
5bc244aa | 674 | |
9a0dcf03 | 675 | assert(!path_is_absolute(p)); |
7bf4057d | 676 | |
7efaab48 | 677 | q = path_join(root, p + STR_IN_SET(p, ".", "./")); |
9a0dcf03 | 678 | if (!q) |
63bfd52f DDM |
679 | return -ENOMEM; |
680 | ||
9a0dcf03 YW |
681 | free_and_replace(p, q); |
682 | } | |
63bfd52f | 683 | } |
7bf4057d | 684 | |
63bfd52f | 685 | *ret_path = TAKE_PTR(p); |
5bc244aa DDM |
686 | } |
687 | ||
688 | if (ret_fd) | |
689 | *ret_fd = TAKE_FD(pfd); | |
690 | ||
691 | return r; | |
692 | } | |
693 | ||
60e761d8 YW |
694 | int chaseat_prefix_root(const char *path, const char *root, char **ret) { |
695 | char *q; | |
696 | int r; | |
697 | ||
698 | assert(path); | |
699 | assert(ret); | |
700 | ||
701 | /* This is mostly for prefixing the result of chaseat(). */ | |
702 | ||
703 | if (!path_is_absolute(path)) { | |
704 | _cleanup_free_ char *root_abs = NULL; | |
705 | ||
8ce46363 | 706 | r = empty_or_root_harder_to_null(&root); |
83c57d8c YW |
707 | if (r < 0 && r != -ENOENT) |
708 | return r; | |
709 | ||
60e761d8 | 710 | /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */ |
83c57d8c YW |
711 | if (empty_or_root(root)) |
712 | return -EINVAL; | |
60e761d8 YW |
713 | |
714 | r = path_make_absolute_cwd(root, &root_abs); | |
715 | if (r < 0) | |
716 | return r; | |
717 | ||
718 | root = path_simplify(root_abs); | |
719 | ||
720 | q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0'))); | |
721 | } else | |
722 | q = strdup(path); | |
723 | if (!q) | |
724 | return -ENOMEM; | |
725 | ||
726 | *ret = q; | |
727 | return 0; | |
728 | } | |
729 | ||
3991f35f YW |
730 | int chase_extract_filename(const char *path, const char *root, char **ret) { |
731 | int r; | |
732 | ||
733 | /* This is similar to path_extract_filename(), but takes root directory. | |
734 | * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */ | |
735 | ||
736 | assert(path); | |
737 | assert(ret); | |
738 | ||
739 | if (isempty(path)) | |
740 | return -EINVAL; | |
741 | ||
742 | if (!path_is_absolute(path)) | |
743 | return -EINVAL; | |
744 | ||
8ce46363 | 745 | r = empty_or_root_harder_to_null(&root); |
83c57d8c YW |
746 | if (r < 0 && r != -ENOENT) |
747 | return r; | |
748 | ||
3991f35f YW |
749 | if (!empty_or_root(root)) { |
750 | _cleanup_free_ char *root_abs = NULL; | |
751 | ||
752 | r = path_make_absolute_cwd(root, &root_abs); | |
753 | if (r < 0) | |
754 | return r; | |
755 | ||
756 | path = path_startswith(path, root_abs); | |
757 | if (!path) | |
758 | return -EINVAL; | |
759 | } | |
760 | ||
761 | if (!isempty(path)) { | |
762 | r = path_extract_filename(path, ret); | |
763 | if (r != -EADDRNOTAVAIL) | |
764 | return r; | |
765 | } | |
766 | ||
454318d3 | 767 | return strdup_to(ret, "."); |
3991f35f YW |
768 | } |
769 | ||
8beb8c3e LP |
770 | int chase_and_open( |
771 | const char *path, | |
772 | const char *root, | |
773 | ChaseFlags chase_flags, | |
774 | int open_flags, | |
775 | char **ret_path) { | |
776 | ||
254d1313 | 777 | _cleanup_close_ int path_fd = -EBADF; |
47f0e1b5 | 778 | _cleanup_free_ char *p = NULL, *fname = NULL; |
f4351959 LP |
779 | int r; |
780 | ||
ea8282b6 | 781 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); |
f4351959 | 782 | |
577efd82 | 783 | if (empty_or_root(root) && !ret_path && |
47f0e1b5 | 784 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) |
f4351959 | 785 | /* Shortcut this call if none of the special features of this call are requested */ |
e40b11be YW |
786 | return xopenat_full(AT_FDCWD, path, |
787 | open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), | |
788 | /* xopen_flags = */ 0, | |
8beb8c3e | 789 | MODE_INVALID); |
f4351959 | 790 | |
f461a28d | 791 | r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd); |
f4351959 LP |
792 | if (r < 0) |
793 | return r; | |
794 | assert(path_fd >= 0); | |
795 | ||
3991f35f YW |
796 | if (!FLAGS_SET(chase_flags, CHASE_PARENT) && |
797 | !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) { | |
798 | r = chase_extract_filename(p, root, &fname); | |
799 | if (r < 0) | |
708e8870 DDM |
800 | return r; |
801 | } | |
47f0e1b5 | 802 | |
8beb8c3e | 803 | r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, MODE_INVALID); |
f4351959 LP |
804 | if (r < 0) |
805 | return r; | |
806 | ||
807 | if (ret_path) | |
808 | *ret_path = TAKE_PTR(p); | |
809 | ||
810 | return r; | |
811 | } | |
812 | ||
11659e48 | 813 | int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { |
254d1313 | 814 | _cleanup_close_ int path_fd = -EBADF; |
f4351959 LP |
815 | _cleanup_free_ char *p = NULL; |
816 | DIR *d; | |
817 | int r; | |
818 | ||
ea8282b6 DDM |
819 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); |
820 | assert(ret_dir); | |
f4351959 | 821 | |
577efd82 | 822 | if (empty_or_root(root) && !ret_path && |
e864dfa6 | 823 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { |
f4351959 LP |
824 | /* Shortcut this call if none of the special features of this call are requested */ |
825 | d = opendir(path); | |
826 | if (!d) | |
827 | return -errno; | |
828 | ||
829 | *ret_dir = d; | |
830 | return 0; | |
831 | } | |
832 | ||
f461a28d | 833 | r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); |
f4351959 LP |
834 | if (r < 0) |
835 | return r; | |
836 | assert(path_fd >= 0); | |
837 | ||
2075b6dd | 838 | d = xopendirat(path_fd, ".", O_NOFOLLOW); |
1fe6e5c1 LP |
839 | if (!d) |
840 | return -errno; | |
f4351959 LP |
841 | |
842 | if (ret_path) | |
843 | *ret_path = TAKE_PTR(p); | |
844 | ||
845 | *ret_dir = d; | |
846 | return 0; | |
847 | } | |
848 | ||
11659e48 | 849 | int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { |
254d1313 | 850 | _cleanup_close_ int path_fd = -EBADF; |
f4351959 LP |
851 | _cleanup_free_ char *p = NULL; |
852 | int r; | |
853 | ||
854 | assert(path); | |
ea8282b6 DDM |
855 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); |
856 | assert(ret_stat); | |
f4351959 | 857 | |
577efd82 | 858 | if (empty_or_root(root) && !ret_path && |
88f2ee86 | 859 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) |
f4351959 | 860 | /* Shortcut this call if none of the special features of this call are requested */ |
88f2ee86 DDM |
861 | return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, |
862 | FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); | |
f4351959 | 863 | |
f461a28d | 864 | r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); |
f4351959 LP |
865 | if (r < 0) |
866 | return r; | |
867 | assert(path_fd >= 0); | |
868 | ||
869 | if (fstat(path_fd, ret_stat) < 0) | |
870 | return -errno; | |
871 | ||
872 | if (ret_path) | |
873 | *ret_path = TAKE_PTR(p); | |
2b2caea2 | 874 | |
88f2ee86 | 875 | return 0; |
2b2caea2 LP |
876 | } |
877 | ||
11659e48 | 878 | int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) { |
254d1313 | 879 | _cleanup_close_ int path_fd = -EBADF; |
2b2caea2 LP |
880 | _cleanup_free_ char *p = NULL; |
881 | int r; | |
882 | ||
883 | assert(path); | |
ea8282b6 | 884 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); |
2b2caea2 | 885 | |
577efd82 | 886 | if (empty_or_root(root) && !ret_path && |
88f2ee86 | 887 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) |
2b2caea2 | 888 | /* Shortcut this call if none of the special features of this call are requested */ |
88f2ee86 DDM |
889 | return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, |
890 | FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); | |
2b2caea2 | 891 | |
f461a28d | 892 | r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); |
2b2caea2 LP |
893 | if (r < 0) |
894 | return r; | |
895 | assert(path_fd >= 0); | |
896 | ||
897 | r = access_fd(path_fd, access_mode); | |
898 | if (r < 0) | |
899 | return r; | |
900 | ||
901 | if (ret_path) | |
902 | *ret_path = TAKE_PTR(p); | |
f4351959 | 903 | |
88f2ee86 | 904 | return 0; |
f4351959 | 905 | } |
01bebba3 | 906 | |
f461a28d | 907 | int chase_and_fopen_unlocked( |
01bebba3 LP |
908 | const char *path, |
909 | const char *root, | |
f461a28d | 910 | ChaseFlags chase_flags, |
01bebba3 LP |
911 | const char *open_flags, |
912 | char **ret_path, | |
913 | FILE **ret_file) { | |
914 | ||
915 | _cleanup_free_ char *final_path = NULL; | |
254d1313 | 916 | _cleanup_close_ int fd = -EBADF; |
01bebba3 LP |
917 | int mode_flags, r; |
918 | ||
919 | assert(path); | |
ea8282b6 | 920 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); |
01bebba3 LP |
921 | assert(open_flags); |
922 | assert(ret_file); | |
923 | ||
924 | mode_flags = fopen_mode_to_flags(open_flags); | |
925 | if (mode_flags < 0) | |
926 | return mode_flags; | |
927 | ||
f461a28d | 928 | fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL); |
01bebba3 LP |
929 | if (fd < 0) |
930 | return fd; | |
931 | ||
932 | r = take_fdopen_unlocked(&fd, open_flags, ret_file); | |
933 | if (r < 0) | |
934 | return r; | |
935 | ||
936 | if (ret_path) | |
937 | *ret_path = TAKE_PTR(final_path); | |
938 | ||
939 | return 0; | |
940 | } | |
1132fd73 | 941 | |
11659e48 | 942 | int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { |
47f0e1b5 | 943 | _cleanup_free_ char *p = NULL, *fname = NULL; |
92651a7a | 944 | _cleanup_close_ int fd = -EBADF; |
1132fd73 LN |
945 | int r; |
946 | ||
947 | assert(path); | |
ea8282b6 | 948 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); |
1132fd73 | 949 | |
f461a28d | 950 | fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); |
1132fd73 LN |
951 | if (fd < 0) |
952 | return fd; | |
953 | ||
47f0e1b5 DDM |
954 | r = path_extract_filename(p, &fname); |
955 | if (r < 0) | |
956 | return r; | |
1132fd73 LN |
957 | |
958 | if (unlinkat(fd, fname, unlink_flags) < 0) | |
959 | return -errno; | |
960 | ||
961 | if (ret_path) | |
47f0e1b5 | 962 | *ret_path = TAKE_PTR(p); |
1132fd73 LN |
963 | |
964 | return 0; | |
965 | } | |
9a98c0f2 | 966 | |
670afc18 DDM |
967 | int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) { |
968 | int pfd, r; | |
969 | ||
970 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
971 | ||
972 | r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd); | |
973 | if (r < 0) | |
974 | return r; | |
975 | ||
976 | return pfd; | |
977 | } | |
978 | ||
8beb8c3e LP |
979 | int chase_and_openat( |
980 | int dir_fd, | |
981 | const char *path, | |
982 | ChaseFlags chase_flags, | |
983 | int open_flags, | |
984 | char **ret_path) { | |
985 | ||
9a98c0f2 | 986 | _cleanup_close_ int path_fd = -EBADF; |
47f0e1b5 | 987 | _cleanup_free_ char *p = NULL, *fname = NULL; |
9a98c0f2 DDM |
988 | int r; |
989 | ||
ea8282b6 | 990 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); |
9a98c0f2 DDM |
991 | |
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 */ | |
e40b11be YW |
995 | return xopenat_full(dir_fd, path, |
996 | open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), | |
997 | /* xopen_flags = */ 0, | |
8beb8c3e | 998 | MODE_INVALID); |
9a98c0f2 | 999 | |
f461a28d | 1000 | r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd); |
9a98c0f2 DDM |
1001 | if (r < 0) |
1002 | return r; | |
9a98c0f2 | 1003 | |
708e8870 DDM |
1004 | if (!FLAGS_SET(chase_flags, CHASE_PARENT)) { |
1005 | r = path_extract_filename(p, &fname); | |
1006 | if (r < 0 && r != -EADDRNOTAVAIL) | |
1007 | return r; | |
1008 | } | |
47f0e1b5 | 1009 | |
8beb8c3e | 1010 | r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags= */ 0, MODE_INVALID); |
9a98c0f2 DDM |
1011 | if (r < 0) |
1012 | return r; | |
1013 | ||
1014 | if (ret_path) | |
1015 | *ret_path = TAKE_PTR(p); | |
1016 | ||
1017 | return r; | |
1018 | } | |
1019 | ||
11659e48 | 1020 | int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { |
12ef2617 DDM |
1021 | _cleanup_close_ int path_fd = -EBADF; |
1022 | _cleanup_free_ char *p = NULL; | |
1023 | DIR *d; | |
1024 | int r; | |
1025 | ||
1026 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
1027 | assert(ret_dir); | |
1028 | ||
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 */ | |
1032 | d = opendir(path); | |
1033 | if (!d) | |
1034 | return -errno; | |
1035 | ||
1036 | *ret_dir = d; | |
1037 | return 0; | |
1038 | } | |
1039 | ||
f461a28d | 1040 | r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); |
12ef2617 DDM |
1041 | if (r < 0) |
1042 | return r; | |
1043 | assert(path_fd >= 0); | |
1044 | ||
1045 | d = xopendirat(path_fd, ".", O_NOFOLLOW); | |
1046 | if (!d) | |
1047 | return -errno; | |
1048 | ||
1049 | if (ret_path) | |
1050 | *ret_path = TAKE_PTR(p); | |
1051 | ||
1052 | *ret_dir = d; | |
1053 | return 0; | |
1054 | } | |
1055 | ||
11659e48 | 1056 | int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { |
12ef2617 DDM |
1057 | _cleanup_close_ int path_fd = -EBADF; |
1058 | _cleanup_free_ char *p = NULL; | |
1059 | int r; | |
1060 | ||
1061 | assert(path); | |
1062 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
1063 | assert(ret_stat); | |
1064 | ||
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)); | |
1070 | ||
f461a28d | 1071 | r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); |
12ef2617 DDM |
1072 | if (r < 0) |
1073 | return r; | |
1074 | assert(path_fd >= 0); | |
1075 | ||
1076 | if (fstat(path_fd, ret_stat) < 0) | |
1077 | return -errno; | |
1078 | ||
1079 | if (ret_path) | |
1080 | *ret_path = TAKE_PTR(p); | |
1081 | ||
1082 | return 0; | |
1083 | } | |
1084 | ||
11659e48 | 1085 | int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) { |
12ef2617 DDM |
1086 | _cleanup_close_ int path_fd = -EBADF; |
1087 | _cleanup_free_ char *p = NULL; | |
1088 | int r; | |
1089 | ||
1090 | assert(path); | |
1091 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
1092 | ||
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)); | |
1098 | ||
f461a28d | 1099 | r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); |
12ef2617 DDM |
1100 | if (r < 0) |
1101 | return r; | |
1102 | assert(path_fd >= 0); | |
1103 | ||
1104 | r = access_fd(path_fd, access_mode); | |
1105 | if (r < 0) | |
1106 | return r; | |
1107 | ||
1108 | if (ret_path) | |
1109 | *ret_path = TAKE_PTR(p); | |
1110 | ||
1111 | return 0; | |
1112 | } | |
1113 | ||
f461a28d | 1114 | int chase_and_fopenat_unlocked( |
12ef2617 DDM |
1115 | int dir_fd, |
1116 | const char *path, | |
f461a28d | 1117 | ChaseFlags chase_flags, |
12ef2617 DDM |
1118 | const char *open_flags, |
1119 | char **ret_path, | |
1120 | FILE **ret_file) { | |
1121 | ||
1122 | _cleanup_free_ char *final_path = NULL; | |
1123 | _cleanup_close_ int fd = -EBADF; | |
1124 | int mode_flags, r; | |
1125 | ||
1126 | assert(path); | |
1127 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); | |
1128 | assert(open_flags); | |
1129 | assert(ret_file); | |
1130 | ||
1131 | mode_flags = fopen_mode_to_flags(open_flags); | |
1132 | if (mode_flags < 0) | |
1133 | return mode_flags; | |
1134 | ||
f461a28d | 1135 | fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL); |
12ef2617 DDM |
1136 | if (fd < 0) |
1137 | return fd; | |
1138 | ||
1139 | r = take_fdopen_unlocked(&fd, open_flags, ret_file); | |
1140 | if (r < 0) | |
1141 | return r; | |
1142 | ||
1143 | if (ret_path) | |
1144 | *ret_path = TAKE_PTR(final_path); | |
1145 | ||
1146 | return 0; | |
1147 | } | |
1148 | ||
11659e48 | 1149 | int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { |
12ef2617 DDM |
1150 | _cleanup_free_ char *p = NULL, *fname = NULL; |
1151 | _cleanup_close_ int fd = -EBADF; | |
1152 | int r; | |
1153 | ||
1154 | assert(path); | |
1155 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); | |
1156 | ||
f461a28d | 1157 | fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); |
12ef2617 DDM |
1158 | if (fd < 0) |
1159 | return fd; | |
1160 | ||
1161 | r = path_extract_filename(p, &fname); | |
1162 | if (r < 0) | |
1163 | return r; | |
1164 | ||
1165 | if (unlinkat(fd, fname, unlink_flags) < 0) | |
1166 | return -errno; | |
1167 | ||
1168 | if (ret_path) | |
1169 | *ret_path = TAKE_PTR(p); | |
1170 | ||
1171 | return 0; | |
1172 | } | |
670afc18 | 1173 | |
11659e48 | 1174 | int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) { |
670afc18 DDM |
1175 | int pfd, r; |
1176 | ||
1177 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
1178 | ||
1179 | r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd); | |
1180 | if (r < 0) | |
1181 | return r; | |
1182 | ||
1183 | return pfd; | |
1184 | } |