]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <linux/magic.h> | |
4 | #include <sys/mount.h> | |
5 | #include <unistd.h> | |
6 | ||
7 | #include "alloc-util.h" | |
8 | #include "chase.h" | |
9 | #include "errno-util.h" | |
10 | #include "fd-util.h" | |
11 | #include "fileio.h" | |
12 | #include "fs-util.h" | |
13 | #include "glyph-util.h" | |
14 | #include "log.h" | |
15 | #include "path-util.h" | |
16 | #include "stat-util.h" | |
17 | #include "string-util.h" | |
18 | #include "strv.h" | |
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 | ||
32 | static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) { | |
33 | _cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL; | |
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.", | |
49 | strna(n1), strna(user_a), glyph(GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path); | |
50 | } | |
51 | ||
52 | static int log_autofs_mount_point(int fd, const char *path, ChaseFlags flags) { | |
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 | ||
65 | static int log_prohibited_symlink(int fd, ChaseFlags flags) { | |
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 | ||
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 | ||
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 | ||
115 | int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) { | |
116 | _cleanup_free_ char *buffer = NULL, *done = NULL; | |
117 | _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; | |
118 | unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */ | |
119 | bool exists = true, append_trail_slash = false; | |
120 | struct stat st; /* stat obtained from fd */ | |
121 | const char *todo; | |
122 | int r; | |
123 | ||
124 | assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); | |
125 | assert(!FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR)); | |
126 | assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME)); | |
127 | assert(dir_fd >= 0 || dir_fd == AT_FDCWD); | |
128 | ||
129 | if (FLAGS_SET(flags, CHASE_STEP)) | |
130 | assert(!ret_fd); | |
131 | ||
132 | if (isempty(path)) | |
133 | path = "."; | |
134 | ||
135 | /* This function resolves symlinks of the path relative to the given directory file descriptor. If | |
136 | * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks | |
137 | * are resolved relative to the given directory file descriptor. Otherwise, they are resolved | |
138 | * relative to the root directory of the host. | |
139 | * | |
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. | |
147 | * | |
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 | |
168 | * | |
169 | * Algorithmically this operates on two path buffers: "done" are the components of the path we | |
170 | * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we | |
171 | * still need to process. On each iteration, we move one component from "todo" to "done", processing | |
172 | * its special meaning each time. We always keep an O_PATH fd to the component we are currently | |
173 | * processing, thus keeping lookup races to a minimum. | |
174 | * | |
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. | |
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 | |
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. | |
192 | * | |
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. | |
199 | * | |
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. | |
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 | |
207 | * the mount point is emitted. CHASE_WARN cannot be used in PID 1. | |
208 | */ | |
209 | ||
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 | ||
214 | r = dir_fd_is_root_or_cwd(dir_fd); | |
215 | if (r < 0) | |
216 | return r; | |
217 | if (r > 0) | |
218 | flags &= ~CHASE_AT_RESOLVE_IN_ROOT; | |
219 | } | |
220 | ||
221 | if (!(flags & | |
222 | (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP| | |
223 | CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_PARENT)) && | |
224 | !ret_path && ret_fd) { | |
225 | ||
226 | /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root | |
227 | * set and doesn't care about any of the other special features we provide either. */ | |
228 | r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0)); | |
229 | if (r < 0) | |
230 | return -errno; | |
231 | ||
232 | *ret_fd = r; | |
233 | return 0; | |
234 | } | |
235 | ||
236 | buffer = strdup(path); | |
237 | if (!buffer) | |
238 | return -ENOMEM; | |
239 | ||
240 | /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because | |
241 | * a relative path would be interpreted relative to the current working directory. Also, let's make | |
242 | * the result absolute when the file descriptor of the root directory is specified. */ | |
243 | r = chaseat_needs_absolute(dir_fd, path); | |
244 | if (r < 0) | |
245 | return r; | |
246 | ||
247 | bool need_absolute = r; | |
248 | if (need_absolute) { | |
249 | done = strdup("/"); | |
250 | if (!done) | |
251 | return -ENOMEM; | |
252 | } | |
253 | ||
254 | /* If a positive directory file descriptor is provided, always resolve the given path relative to it, | |
255 | * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat() | |
256 | * semantics, if the path is relative, resolve against the current working directory. Otherwise, | |
257 | * resolve against root. */ | |
258 | fd = openat(dir_fd, done ?: ".", O_CLOEXEC|O_DIRECTORY|O_PATH); | |
259 | if (fd < 0) | |
260 | return -errno; | |
261 | ||
262 | if (fstat(fd, &st) < 0) | |
263 | return -errno; | |
264 | ||
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 | ||
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; | |
284 | ||
285 | for (todo = buffer;;) { | |
286 | _cleanup_free_ char *first = NULL; | |
287 | _cleanup_close_ int child = -EBADF; | |
288 | struct stat st_child; | |
289 | const char *e; | |
290 | ||
291 | r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e); | |
292 | if (r < 0) | |
293 | return r; | |
294 | if (r == 0) /* We reached the end. */ | |
295 | break; | |
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. */ | |
302 | if (streq(first, "..")) { | |
303 | _cleanup_free_ char *parent = NULL; | |
304 | _cleanup_close_ int fd_parent = -EBADF; | |
305 | struct stat st_parent; | |
306 | ||
307 | /* If we already are at the top, then going up will not change anything. This is | |
308 | * in-line with how the kernel handles this. */ | |
309 | if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { | |
310 | if (FLAGS_SET(flags, CHASE_STEP)) | |
311 | goto chased_one; | |
312 | continue; | |
313 | } | |
314 | ||
315 | fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY); | |
316 | if (fd_parent < 0) | |
317 | return -errno; | |
318 | ||
319 | if (fstat(fd_parent, &st_parent) < 0) | |
320 | return -errno; | |
321 | ||
322 | /* If we opened the same directory, that _may_ indicate that we're at the host root | |
323 | * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so, | |
324 | * going up won't change anything. */ | |
325 | if (stat_inode_same(&st_parent, &st)) { | |
326 | r = dir_fd_is_root(fd); | |
327 | if (r < 0) | |
328 | return r; | |
329 | if (r > 0) { | |
330 | if (FLAGS_SET(flags, CHASE_STEP)) | |
331 | goto chased_one; | |
332 | continue; | |
333 | } | |
334 | } | |
335 | ||
336 | r = path_extract_directory(done, &parent); | |
337 | if (r >= 0) { | |
338 | assert(!need_absolute || path_is_absolute(parent)); | |
339 | free_and_replace(done, parent); | |
340 | } else if (r == -EDESTADDRREQ) { | |
341 | /* 'done' contains filename only (i.e. no slash). */ | |
342 | assert(!need_absolute); | |
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 | ||
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; | |
361 | ||
362 | if (FLAGS_SET(flags, CHASE_STEP)) | |
363 | goto chased_one; | |
364 | ||
365 | if (FLAGS_SET(flags, CHASE_SAFE) && | |
366 | unsafe_transition(&st, &st_parent)) | |
367 | return log_unsafe_transition(fd, fd_parent, path, flags); | |
368 | ||
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); | |
389 | break; | |
390 | } | |
391 | ||
392 | /* update fd and stat */ | |
393 | st = st_parent; | |
394 | close_and_replace(fd, fd_parent); | |
395 | continue; | |
396 | } | |
397 | ||
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)); | |
400 | if (r < 0) { | |
401 | if (r != -ENOENT) | |
402 | return r; | |
403 | ||
404 | if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */ | |
405 | return r; | |
406 | ||
407 | if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) { | |
408 | child = xopenat_full(fd, | |
409 | first, | |
410 | O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC, | |
411 | /* xopen_flags = */ 0, | |
412 | 0755); | |
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; | |
420 | } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) { | |
421 | if (!path_extend(&done, first, todo)) | |
422 | return -ENOMEM; | |
423 | ||
424 | exists = false; | |
425 | break; | |
426 | } else | |
427 | return r; | |
428 | } | |
429 | ||
430 | /* ... and then check what it actually is. */ | |
431 | if (fstat(child, &st_child) < 0) | |
432 | return -errno; | |
433 | ||
434 | if (FLAGS_SET(flags, CHASE_SAFE) && | |
435 | unsafe_transition(&st, &st_child)) | |
436 | return log_unsafe_transition(fd, child, path, flags); | |
437 | ||
438 | if (FLAGS_SET(flags, CHASE_NO_AUTOFS) && | |
439 | fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0) | |
440 | return log_autofs_mount_point(child, path, flags); | |
441 | ||
442 | if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) { | |
443 | _cleanup_free_ char *destination = NULL; | |
444 | ||
445 | if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS)) | |
446 | return log_prohibited_symlink(child, flags); | |
447 | ||
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 | ||
461 | /* An absolute destination. Start the loop from the beginning, but use the | |
462 | * root file descriptor as base. */ | |
463 | ||
464 | safe_close(fd); | |
465 | fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY); | |
466 | if (fd < 0) | |
467 | return fd; | |
468 | ||
469 | if (fstat(fd, &st) < 0) | |
470 | return -errno; | |
471 | ||
472 | if (FLAGS_SET(flags, CHASE_SAFE) && | |
473 | unsafe_transition(&st_child, &st)) | |
474 | return log_unsafe_transition(child, fd, path, flags); | |
475 | ||
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 | ||
481 | r = free_and_strdup(&done, need_absolute ? "/" : NULL); | |
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 | ||
494 | if (FLAGS_SET(flags, CHASE_STEP)) | |
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 | ||
504 | if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) | |
505 | break; | |
506 | ||
507 | /* And iterate again, but go one directory further down. */ | |
508 | st = st_child; | |
509 | close_and_replace(fd, child); | |
510 | } | |
511 | ||
512 | if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) { | |
513 | r = stat_verify_directory(&st); | |
514 | if (r < 0) | |
515 | return r; | |
516 | } | |
517 | ||
518 | if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) { | |
519 | r = stat_verify_regular(&st); | |
520 | if (r < 0) | |
521 | return r; | |
522 | } | |
523 | ||
524 | if (ret_path) { | |
525 | if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) { | |
526 | _cleanup_free_ char *f = NULL; | |
527 | ||
528 | r = path_extract_filename(done, &f); | |
529 | if (r < 0 && r != -EADDRNOTAVAIL) | |
530 | return r; | |
531 | ||
532 | /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */ | |
533 | free_and_replace(done, f); | |
534 | } | |
535 | ||
536 | if (!done) { | |
537 | assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)); | |
538 | done = strdup("."); | |
539 | if (!done) | |
540 | return -ENOMEM; | |
541 | } | |
542 | ||
543 | if (append_trail_slash) | |
544 | if (!strextend(&done, "/")) | |
545 | return -ENOMEM; | |
546 | ||
547 | *ret_path = TAKE_PTR(done); | |
548 | } | |
549 | ||
550 | if (ret_fd) { | |
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; | |
558 | } | |
559 | ||
560 | if (FLAGS_SET(flags, CHASE_STEP)) | |
561 | return 1; | |
562 | ||
563 | return exists; | |
564 | ||
565 | chased_one: | |
566 | if (ret_path) { | |
567 | const char *e; | |
568 | ||
569 | if (!done) { | |
570 | assert(!need_absolute); | |
571 | done = strdup(append_trail_slash ? "./" : "."); | |
572 | if (!done) | |
573 | return -ENOMEM; | |
574 | } | |
575 | ||
576 | /* todo may contain slashes at the beginning. */ | |
577 | r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e); | |
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 | ||
596 | int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) { | |
597 | _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL; | |
598 | _cleanup_close_ int fd = -EBADF, pfd = -EBADF; | |
599 | int r; | |
600 | ||
601 | assert(path); | |
602 | ||
603 | if (isempty(path)) | |
604 | return -EINVAL; | |
605 | ||
606 | r = empty_or_root_harder_to_null(&root); | |
607 | if (r < 0) | |
608 | return r; | |
609 | ||
610 | /* A root directory of "/" or "" is identical to "/". */ | |
611 | if (empty_or_root(root)) { | |
612 | root = "/"; | |
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 { | |
619 | r = path_make_absolute_cwd(root, &root_abs); | |
620 | if (r < 0) | |
621 | return r; | |
622 | ||
623 | /* Simplify the root directory, so that it has no duplicate slashes and nothing at the | |
624 | * end. While we won't resolve the root path we still simplify it. */ | |
625 | root = path_simplify(root_abs); | |
626 | ||
627 | assert(path_is_absolute(root)); | |
628 | assert(!empty_or_root(root)); | |
629 | ||
630 | if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) { | |
631 | absolute = path_join(root, path); | |
632 | if (!absolute) | |
633 | return -ENOMEM; | |
634 | } | |
635 | ||
636 | flags |= CHASE_AT_RESOLVE_IN_ROOT; | |
637 | } | |
638 | ||
639 | if (!absolute) { | |
640 | r = path_make_absolute_cwd(path, &absolute); | |
641 | if (r < 0) | |
642 | return r; | |
643 | } | |
644 | ||
645 | path = path_startswith(absolute, root); | |
646 | if (!path) | |
647 | return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG, | |
648 | SYNTHETIC_ERRNO(ECHRNG), | |
649 | "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.", | |
650 | absolute, root); | |
651 | ||
652 | fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH); | |
653 | if (fd < 0) | |
654 | return -errno; | |
655 | ||
656 | r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL); | |
657 | if (r < 0) | |
658 | return r; | |
659 | ||
660 | if (ret_path) { | |
661 | if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) { | |
662 | ||
663 | /* When "root" points to the root directory, the result of chaseat() is always | |
664 | * absolute, hence it is not necessary to prefix with the root. When "root" points to | |
665 | * a non-root directory, the result path is always normalized and relative, hence | |
666 | * we can simply call path_join() and not necessary to call path_simplify(). | |
667 | * As a special case, chaseat() may return "." or "./", which are normalized too, | |
668 | * but we need to drop "." before merging with root. */ | |
669 | ||
670 | if (empty_or_root(root)) | |
671 | assert(path_is_absolute(p)); | |
672 | else { | |
673 | char *q; | |
674 | ||
675 | assert(!path_is_absolute(p)); | |
676 | ||
677 | q = path_join(root, p + STR_IN_SET(p, ".", "./")); | |
678 | if (!q) | |
679 | return -ENOMEM; | |
680 | ||
681 | free_and_replace(p, q); | |
682 | } | |
683 | } | |
684 | ||
685 | *ret_path = TAKE_PTR(p); | |
686 | } | |
687 | ||
688 | if (ret_fd) | |
689 | *ret_fd = TAKE_FD(pfd); | |
690 | ||
691 | return r; | |
692 | } | |
693 | ||
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 | ||
706 | r = empty_or_root_harder_to_null(&root); | |
707 | if (r < 0 && r != -ENOENT) | |
708 | return r; | |
709 | ||
710 | /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */ | |
711 | if (empty_or_root(root)) | |
712 | return -EINVAL; | |
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 | ||
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 | ||
745 | r = empty_or_root_harder_to_null(&root); | |
746 | if (r < 0 && r != -ENOENT) | |
747 | return r; | |
748 | ||
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 | ||
767 | return strdup_to(ret, "."); | |
768 | } | |
769 | ||
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 | ||
777 | _cleanup_close_ int path_fd = -EBADF; | |
778 | _cleanup_free_ char *p = NULL, *fname = NULL; | |
779 | int r; | |
780 | ||
781 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
782 | ||
783 | if (empty_or_root(root) && !ret_path && | |
784 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) | |
785 | /* Shortcut this call if none of the special features of this call are requested */ | |
786 | return xopenat_full(AT_FDCWD, path, | |
787 | open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), | |
788 | /* xopen_flags = */ 0, | |
789 | MODE_INVALID); | |
790 | ||
791 | r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd); | |
792 | if (r < 0) | |
793 | return r; | |
794 | assert(path_fd >= 0); | |
795 | ||
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) | |
800 | return r; | |
801 | } | |
802 | ||
803 | r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, MODE_INVALID); | |
804 | if (r < 0) | |
805 | return r; | |
806 | ||
807 | if (ret_path) | |
808 | *ret_path = TAKE_PTR(p); | |
809 | ||
810 | return r; | |
811 | } | |
812 | ||
813 | int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { | |
814 | _cleanup_close_ int path_fd = -EBADF; | |
815 | _cleanup_free_ char *p = NULL; | |
816 | DIR *d; | |
817 | int r; | |
818 | ||
819 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
820 | assert(ret_dir); | |
821 | ||
822 | if (empty_or_root(root) && !ret_path && | |
823 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { | |
824 | /* Shortcut this call if none of the special features of this call are requested */ | |
825 | d = opendir(path); | |
826 | if (!d) | |
827 | return -errno; | |
828 | ||
829 | *ret_dir = d; | |
830 | return 0; | |
831 | } | |
832 | ||
833 | r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); | |
834 | if (r < 0) | |
835 | return r; | |
836 | assert(path_fd >= 0); | |
837 | ||
838 | d = xopendirat(path_fd, ".", O_NOFOLLOW); | |
839 | if (!d) | |
840 | return -errno; | |
841 | ||
842 | if (ret_path) | |
843 | *ret_path = TAKE_PTR(p); | |
844 | ||
845 | *ret_dir = d; | |
846 | return 0; | |
847 | } | |
848 | ||
849 | int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { | |
850 | _cleanup_close_ int path_fd = -EBADF; | |
851 | _cleanup_free_ char *p = NULL; | |
852 | int r; | |
853 | ||
854 | assert(path); | |
855 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
856 | assert(ret_stat); | |
857 | ||
858 | if (empty_or_root(root) && !ret_path && | |
859 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) | |
860 | /* Shortcut this call if none of the special features of this call are requested */ | |
861 | return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, | |
862 | FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); | |
863 | ||
864 | r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); | |
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); | |
874 | ||
875 | return 0; | |
876 | } | |
877 | ||
878 | int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) { | |
879 | _cleanup_close_ int path_fd = -EBADF; | |
880 | _cleanup_free_ char *p = NULL; | |
881 | int r; | |
882 | ||
883 | assert(path); | |
884 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
885 | ||
886 | if (empty_or_root(root) && !ret_path && | |
887 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) | |
888 | /* Shortcut this call if none of the special features of this call are requested */ | |
889 | return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, | |
890 | FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); | |
891 | ||
892 | r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); | |
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); | |
903 | ||
904 | return 0; | |
905 | } | |
906 | ||
907 | int chase_and_fopen_unlocked( | |
908 | const char *path, | |
909 | const char *root, | |
910 | ChaseFlags chase_flags, | |
911 | const char *open_flags, | |
912 | char **ret_path, | |
913 | FILE **ret_file) { | |
914 | ||
915 | _cleanup_free_ char *final_path = NULL; | |
916 | _cleanup_close_ int fd = -EBADF; | |
917 | int mode_flags, r; | |
918 | ||
919 | assert(path); | |
920 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); | |
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 | ||
928 | fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL); | |
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 | } | |
941 | ||
942 | int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { | |
943 | _cleanup_free_ char *p = NULL, *fname = NULL; | |
944 | _cleanup_close_ int fd = -EBADF; | |
945 | int r; | |
946 | ||
947 | assert(path); | |
948 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); | |
949 | ||
950 | fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); | |
951 | if (fd < 0) | |
952 | return fd; | |
953 | ||
954 | r = path_extract_filename(p, &fname); | |
955 | if (r < 0) | |
956 | return r; | |
957 | ||
958 | if (unlinkat(fd, fname, unlink_flags) < 0) | |
959 | return -errno; | |
960 | ||
961 | if (ret_path) | |
962 | *ret_path = TAKE_PTR(p); | |
963 | ||
964 | return 0; | |
965 | } | |
966 | ||
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 | ||
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 | ||
986 | _cleanup_close_ int path_fd = -EBADF; | |
987 | _cleanup_free_ char *p = NULL, *fname = NULL; | |
988 | int r; | |
989 | ||
990 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
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 */ | |
995 | return xopenat_full(dir_fd, path, | |
996 | open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), | |
997 | /* xopen_flags = */ 0, | |
998 | MODE_INVALID); | |
999 | ||
1000 | r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd); | |
1001 | if (r < 0) | |
1002 | return r; | |
1003 | ||
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 | } | |
1009 | ||
1010 | r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags= */ 0, MODE_INVALID); | |
1011 | if (r < 0) | |
1012 | return r; | |
1013 | ||
1014 | if (ret_path) | |
1015 | *ret_path = TAKE_PTR(p); | |
1016 | ||
1017 | return r; | |
1018 | } | |
1019 | ||
1020 | int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { | |
1021 | _cleanup_close_ int path_fd = -EBADF; | |
1022 | _cleanup_free_ char *p = NULL; | |
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 | ||
1040 | r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); | |
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 | ||
1056 | int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { | |
1057 | _cleanup_close_ int path_fd = -EBADF; | |
1058 | _cleanup_free_ char *p = NULL; | |
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 | ||
1071 | r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); | |
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 | ||
1085 | int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) { | |
1086 | _cleanup_close_ int path_fd = -EBADF; | |
1087 | _cleanup_free_ char *p = NULL; | |
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 | ||
1099 | r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); | |
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 | ||
1114 | int chase_and_fopenat_unlocked( | |
1115 | int dir_fd, | |
1116 | const char *path, | |
1117 | ChaseFlags chase_flags, | |
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 | ||
1135 | fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL); | |
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 | ||
1149 | int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { | |
1150 | _cleanup_free_ char *p = NULL, *fname = NULL; | |
1151 | _cleanup_close_ int fd = -EBADF; | |
1152 | int r; | |
1153 | ||
1154 | assert(path); | |
1155 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); | |
1156 | ||
1157 | fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); | |
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 | } | |
1173 | ||
1174 | int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) { | |
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 | } |