]>
Commit | Line | Data |
---|---|---|
f4351959 LP |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include <linux/magic.h> | |
4 | ||
5 | #include "alloc-util.h" | |
f461a28d | 6 | #include "chase.h" |
f4351959 | 7 | #include "fd-util.h" |
01bebba3 | 8 | #include "fileio.h" |
f4351959 LP |
9 | #include "fs-util.h" |
10 | #include "glyph-util.h" | |
11 | #include "log.h" | |
12 | #include "path-util.h" | |
13 | #include "string-util.h" | |
14 | #include "user-util.h" | |
15 | ||
16 | bool unsafe_transition(const struct stat *a, const struct stat *b) { | |
17 | /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to | |
18 | * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files | |
19 | * making us believe we read something safe even though it isn't safe in the specific context we open it in. */ | |
20 | ||
21 | if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */ | |
22 | return false; | |
23 | ||
24 | return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */ | |
25 | } | |
26 | ||
f461a28d | 27 | static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) { |
f4351959 LP |
28 | _cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL; |
29 | struct stat st; | |
30 | ||
31 | if (!FLAGS_SET(flags, CHASE_WARN)) | |
32 | return -ENOLINK; | |
33 | ||
34 | (void) fd_get_path(a, &n1); | |
35 | (void) fd_get_path(b, &n2); | |
36 | ||
37 | if (fstat(a, &st) == 0) | |
38 | user_a = uid_to_name(st.st_uid); | |
39 | if (fstat(b, &st) == 0) | |
40 | user_b = uid_to_name(st.st_uid); | |
41 | ||
42 | return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK), | |
43 | "Detected unsafe path transition %s (owned by %s) %s %s (owned by %s) during canonicalization of %s.", | |
fc03e80c | 44 | strna(n1), strna(user_a), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path); |
f4351959 LP |
45 | } |
46 | ||
f461a28d | 47 | static int log_autofs_mount_point(int fd, const char *path, ChaseFlags flags) { |
f4351959 LP |
48 | _cleanup_free_ char *n1 = NULL; |
49 | ||
50 | if (!FLAGS_SET(flags, CHASE_WARN)) | |
51 | return -EREMOTE; | |
52 | ||
53 | (void) fd_get_path(fd, &n1); | |
54 | ||
55 | return log_warning_errno(SYNTHETIC_ERRNO(EREMOTE), | |
56 | "Detected autofs mount point %s during canonicalization of %s.", | |
57 | strna(n1), path); | |
58 | } | |
59 | ||
f461a28d | 60 | static int log_prohibited_symlink(int fd, ChaseFlags flags) { |
d43e78b6 LP |
61 | _cleanup_free_ char *n1 = NULL; |
62 | ||
63 | assert(fd >= 0); | |
64 | ||
65 | if (!FLAGS_SET(flags, CHASE_WARN)) | |
66 | return -EREMCHG; | |
67 | ||
68 | (void) fd_get_path(fd, &n1); | |
69 | ||
70 | return log_warning_errno(SYNTHETIC_ERRNO(EREMCHG), | |
71 | "Detected symlink where not symlink is allowed at %s, refusing.", | |
72 | strna(n1)); | |
73 | } | |
74 | ||
11659e48 | 75 | int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) { |
5bc244aa | 76 | _cleanup_free_ char *buffer = NULL, *done = NULL; |
254d1313 | 77 | _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; |
f461a28d | 78 | unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */ |
f4351959 | 79 | bool exists = true, append_trail_slash = false; |
ad66c7f1 | 80 | struct stat st; /* stat obtained from fd */ |
f4351959 LP |
81 | const char *todo; |
82 | int r; | |
83 | ||
84 | assert(path); | |
5bc244aa | 85 | assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); |
63bfd52f DDM |
86 | assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME)); |
87 | assert(!FLAGS_SET(flags, CHASE_TRAIL_SLASH|CHASE_EXTRACT_FILENAME)); | |
5bc244aa | 88 | assert(dir_fd >= 0 || dir_fd == AT_FDCWD); |
f4351959 LP |
89 | |
90 | /* Either the file may be missing, or we return an fd to the final object, but both make no sense */ | |
ea8282b6 DDM |
91 | if ((flags & CHASE_NONEXISTENT)) |
92 | assert(!ret_fd); | |
f4351959 | 93 | |
ea8282b6 DDM |
94 | if ((flags & CHASE_STEP)) |
95 | assert(!ret_fd); | |
f4351959 LP |
96 | |
97 | if (isempty(path)) | |
5bc244aa | 98 | path = "."; |
f4351959 | 99 | |
5bc244aa | 100 | /* This function resolves symlinks of the path relative to the given directory file descriptor. If |
f461a28d | 101 | * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks |
c677e13c DDM |
102 | * are resolved relative to the given directory file descriptor. Otherwise, they are resolved |
103 | * relative to the root directory of the host. | |
5bc244aa | 104 | * |
c677e13c DDM |
105 | * Note that when a positive directory file descriptor is provided and CHASE_AT_RESOLVE_IN_ROOT is |
106 | * specified and we find an absolute symlink, it is resolved relative to given directory file | |
107 | * descriptor and not the root of the host. Also, when following relative symlinks, this functions | |
108 | * ensures they cannot be used to "escape" the given directory file descriptor. If a positive | |
109 | * directory file descriptor is provided, the "path" parameter is always interpreted relative to the | |
110 | * given directory file descriptor, even if it is absolute. If the given directory file descriptor is | |
111 | * AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host. | |
f4351959 | 112 | * |
5bc244aa DDM |
113 | * If "dir_fd" is a valid directory fd, "path" is an absolute path and "ret_path" is not NULL, this |
114 | * functions returns a relative path in "ret_path" because openat() like functions generally ignore | |
115 | * the directory fd if they are provided with an absolute path. On the other hand, if "dir_fd" is | |
c677e13c | 116 | * AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path" because |
5bc244aa DDM |
117 | * otherwise, if the caller passes the returned relative path to another openat() like function, it |
118 | * would be resolved relative to the current working directory instead of to "/". | |
f4351959 | 119 | * |
5bc244aa DDM |
120 | * Algorithmically this operates on two path buffers: "done" are the components of the path we |
121 | * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we | |
122 | * still need to process. On each iteration, we move one component from "todo" to "done", processing | |
18fe76eb | 123 | * its special meaning each time. We always keep an O_PATH fd to the component we are currently |
5bc244aa | 124 | * processing, thus keeping lookup races to a minimum. |
f4351959 | 125 | * |
5bc244aa DDM |
126 | * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute |
127 | * path you got as-is: fully qualified and relative to your host's root. Optionally, specify the | |
128 | * "dir_fd" parameter to tell this function what to do when encountering a symlink with an absolute | |
129 | * path as directory: resolve it relative to the given directory file descriptor. | |
f4351959 LP |
130 | * |
131 | * There are five ways to invoke this function: | |
132 | * | |
133 | * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is | |
134 | * returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0 | |
135 | * is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is | |
136 | * returned if the destination was found, -ENOENT if it wasn't. | |
137 | * | |
138 | * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file | |
139 | * descriptor is returned as return value. This is useful to open files relative to some root | |
5bc244aa DDM |
140 | * directory. Note that the returned O_PATH file descriptors must be converted into a regular one |
141 | * (using fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be | |
142 | * combined with CHASE_NONEXISTENT. | |
f4351959 | 143 | * |
5bc244aa DDM |
144 | * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only |
145 | * the first symlink or ".." component of the path is resolved, and the resulting path is | |
146 | * returned. This is useful if a caller wants to trace the path through the file system verbosely. | |
147 | * Returns < 0 on error, > 0 if the path is fully normalized, and == 0 for each normalization | |
148 | * step. This may be combined with CHASE_NONEXISTENT, in which case 1 is returned when a component | |
149 | * is not found. | |
f4351959 | 150 | * |
5bc244aa DDM |
151 | * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions |
152 | * from unprivileged to privileged files or directories. In such cases the return value is | |
153 | * -ENOLINK. If CHASE_WARN is also set, a warning describing the unsafe transition is emitted. | |
154 | * CHASE_WARN cannot be used in PID 1. | |
f4351959 LP |
155 | * |
156 | * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization | |
157 | * is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of | |
9154bd57 | 158 | * the mount point is emitted. CHASE_WARN cannot be used in PID 1. |
f4351959 LP |
159 | */ |
160 | ||
e115daa6 YW |
161 | if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { |
162 | /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to | |
163 | * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */ | |
164 | ||
e212f422 | 165 | r = dir_fd_is_root_or_cwd(dir_fd); |
e115daa6 YW |
166 | if (r < 0) |
167 | return r; | |
168 | if (r > 0) | |
169 | flags &= ~CHASE_AT_RESOLVE_IN_ROOT; | |
170 | } | |
171 | ||
577efd82 DDM |
172 | if (!(flags & |
173 | (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP| | |
e864dfa6 | 174 | CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755)) && |
577efd82 | 175 | !ret_path && ret_fd) { |
f4351959 | 176 | |
5bc244aa DDM |
177 | /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root |
178 | * set and doesn't care about any of the other special features we provide either. */ | |
db0096f2 | 179 | r = openat(dir_fd, path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0)); |
f4351959 LP |
180 | if (r < 0) |
181 | return -errno; | |
182 | ||
183 | *ret_fd = r; | |
184 | return 0; | |
185 | } | |
186 | ||
db0096f2 YW |
187 | buffer = strdup(path); |
188 | if (!buffer) | |
189 | return -ENOMEM; | |
f4351959 | 190 | |
c677e13c DDM |
191 | /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because |
192 | * a relative path would be interpreted relative to the current working directory. */ | |
193 | bool need_absolute = dir_fd == AT_FDCWD && path_is_absolute(path); | |
5bc244aa DDM |
194 | if (need_absolute) { |
195 | done = strdup("/"); | |
196 | if (!done) | |
197 | return -ENOMEM; | |
f4351959 LP |
198 | } |
199 | ||
c677e13c | 200 | /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive |
52576a75 | 201 | * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine |
c677e13c DDM |
202 | * whether to resolve symlinks in it or not. */ |
203 | if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) | |
5bc244aa DDM |
204 | root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH); |
205 | else | |
206 | root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH); | |
207 | if (root_fd < 0) | |
208 | return -errno; | |
f4351959 | 209 | |
c677e13c DDM |
210 | /* If a positive directory file descriptor is provided, always resolve the given path relative to it, |
211 | * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat() | |
212 | * semantics, if the path is relative, resolve against the current working directory. Otherwise, | |
213 | * resolve against root. */ | |
214 | if (dir_fd >= 0 || !path_is_absolute(path)) | |
5bc244aa DDM |
215 | fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH); |
216 | else | |
217 | fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH); | |
f4351959 LP |
218 | if (fd < 0) |
219 | return -errno; | |
220 | ||
ad66c7f1 | 221 | if (fstat(fd, &st) < 0) |
5bc244aa | 222 | return -errno; |
f4351959 LP |
223 | |
224 | if (flags & CHASE_TRAIL_SLASH) | |
7bf4057d | 225 | append_trail_slash = ENDSWITH_SET(buffer, "/", "/."); |
f4351959 | 226 | |
5bc244aa | 227 | for (todo = buffer;;) { |
f4351959 | 228 | _cleanup_free_ char *first = NULL; |
5bb1d7fb | 229 | _cleanup_close_ int child = -EBADF; |
ad66c7f1 | 230 | struct stat st_child; |
f4351959 LP |
231 | const char *e; |
232 | ||
860f4c6a | 233 | r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e); |
f4351959 LP |
234 | if (r < 0) |
235 | return r; | |
236 | if (r == 0) { /* We reached the end. */ | |
237 | if (append_trail_slash) | |
238 | if (!strextend(&done, "/")) | |
239 | return -ENOMEM; | |
240 | break; | |
241 | } | |
242 | ||
243 | first = strndup(e, r); | |
244 | if (!first) | |
245 | return -ENOMEM; | |
246 | ||
247 | /* Two dots? Then chop off the last bit of what we already found out. */ | |
248 | if (path_equal(first, "..")) { | |
249 | _cleanup_free_ char *parent = NULL; | |
254d1313 | 250 | _cleanup_close_ int fd_parent = -EBADF; |
ad66c7f1 | 251 | struct stat st_parent; |
f4351959 | 252 | |
5bc244aa DDM |
253 | /* If we already are at the top, then going up will not change anything. This is |
254 | * in-line with how the kernel handles this. */ | |
255 | if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) | |
f4351959 LP |
256 | continue; |
257 | ||
5bc244aa DDM |
258 | fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY); |
259 | if (fd_parent < 0) | |
260 | return -errno; | |
f4351959 | 261 | |
ad66c7f1 | 262 | if (fstat(fd_parent, &st_parent) < 0) |
5bc244aa DDM |
263 | return -errno; |
264 | ||
a6ef5ef7 YW |
265 | /* If we opened the same directory, that _may_ indicate that we're at the host root |
266 | * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so, | |
5bc244aa | 267 | * going up won't change anything. */ |
a6ef5ef7 YW |
268 | if (stat_inode_same(&st_parent, &st)) { |
269 | r = dir_fd_is_root(fd); | |
270 | if (r < 0) | |
271 | return r; | |
272 | if (r > 0) | |
273 | continue; | |
274 | } | |
f4351959 | 275 | |
5bc244aa DDM |
276 | r = path_extract_directory(done, &parent); |
277 | if (r >= 0 || r == -EDESTADDRREQ) | |
278 | free_and_replace(done, parent); | |
279 | else if (IN_SET(r, -EINVAL, -EADDRNOTAVAIL)) { | |
280 | /* If we're at the top of "dir_fd", start appending ".." to "done". */ | |
281 | if (!path_extend(&done, "..")) | |
282 | return -ENOMEM; | |
283 | } else | |
284 | return r; | |
f4351959 LP |
285 | |
286 | if (flags & CHASE_STEP) | |
287 | goto chased_one; | |
288 | ||
ad66c7f1 YW |
289 | if (flags & CHASE_SAFE && |
290 | unsafe_transition(&st, &st_parent)) | |
291 | return log_unsafe_transition(fd, fd_parent, path, flags); | |
f4351959 | 292 | |
47f0e1b5 DDM |
293 | if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) |
294 | break; | |
295 | ||
ad66c7f1 YW |
296 | /* update fd and stat */ |
297 | st = st_parent; | |
ee3455cf | 298 | close_and_replace(fd, fd_parent); |
f4351959 LP |
299 | continue; |
300 | } | |
301 | ||
302 | /* Otherwise let's see what this is. */ | |
e864dfa6 DDM |
303 | child = r = RET_NERRNO(openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH)); |
304 | if (r < 0) { | |
305 | if (r != -ENOENT) | |
306 | return r; | |
307 | ||
308 | if (!isempty(todo) && !path_is_safe(todo)) | |
309 | return r; | |
f4351959 | 310 | |
47f0e1b5 | 311 | if (FLAGS_SET(flags, CHASE_MKDIR_0755) && !isempty(todo)) { |
ba54d730 | 312 | child = xopenat(fd, first, O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, 0755); |
47f0e1b5 DDM |
313 | if (child < 0) |
314 | return child; | |
315 | } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) { | |
316 | if (!path_extend(&done, first)) | |
317 | return -ENOMEM; | |
318 | ||
319 | break; | |
320 | } else if (flags & CHASE_NONEXISTENT) { | |
f4351959 LP |
321 | if (!path_extend(&done, first, todo)) |
322 | return -ENOMEM; | |
323 | ||
324 | exists = false; | |
325 | break; | |
47f0e1b5 | 326 | } else |
e864dfa6 | 327 | return r; |
f4351959 LP |
328 | } |
329 | ||
ad66c7f1 | 330 | if (fstat(child, &st_child) < 0) |
f4351959 | 331 | return -errno; |
ad66c7f1 | 332 | |
f4351959 | 333 | if ((flags & CHASE_SAFE) && |
ad66c7f1 | 334 | unsafe_transition(&st, &st_child)) |
f4351959 LP |
335 | return log_unsafe_transition(fd, child, path, flags); |
336 | ||
f4351959 LP |
337 | if ((flags & CHASE_NO_AUTOFS) && |
338 | fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0) | |
339 | return log_autofs_mount_point(child, path, flags); | |
340 | ||
ad66c7f1 | 341 | if (S_ISLNK(st_child.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) { |
f4351959 LP |
342 | _cleanup_free_ char *destination = NULL; |
343 | ||
d43e78b6 LP |
344 | if (flags & CHASE_PROHIBIT_SYMLINKS) |
345 | return log_prohibited_symlink(child, flags); | |
346 | ||
f4351959 LP |
347 | /* This is a symlink, in this case read the destination. But let's make sure we |
348 | * don't follow symlinks without bounds. */ | |
349 | if (--max_follow <= 0) | |
350 | return -ELOOP; | |
351 | ||
352 | r = readlinkat_malloc(fd, first, &destination); | |
353 | if (r < 0) | |
354 | return r; | |
355 | if (isempty(destination)) | |
356 | return -EINVAL; | |
357 | ||
358 | if (path_is_absolute(destination)) { | |
359 | ||
5bc244aa DDM |
360 | /* An absolute destination. Start the loop from the beginning, but use the |
361 | * root file descriptor as base. */ | |
f4351959 LP |
362 | |
363 | safe_close(fd); | |
5bc244aa | 364 | fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY); |
f4351959 | 365 | if (fd < 0) |
5bc244aa | 366 | return fd; |
f4351959 | 367 | |
ad66c7f1 YW |
368 | if (fstat(fd, &st) < 0) |
369 | return -errno; | |
f4351959 | 370 | |
ad66c7f1 YW |
371 | if (flags & CHASE_SAFE && |
372 | unsafe_transition(&st_child, &st)) | |
373 | return log_unsafe_transition(child, fd, path, flags); | |
f4351959 | 374 | |
5bc244aa | 375 | r = free_and_strdup(&done, need_absolute ? "/" : NULL); |
f4351959 LP |
376 | if (r < 0) |
377 | return r; | |
378 | } | |
379 | ||
380 | /* Prefix what's left to do with what we just read, and start the loop again, but | |
381 | * remain in the current directory. */ | |
382 | if (!path_extend(&destination, todo)) | |
383 | return -ENOMEM; | |
384 | ||
385 | free_and_replace(buffer, destination); | |
386 | todo = buffer; | |
387 | ||
388 | if (flags & CHASE_STEP) | |
389 | goto chased_one; | |
390 | ||
391 | continue; | |
392 | } | |
393 | ||
394 | /* If this is not a symlink, then let's just add the name we read to what we already verified. */ | |
395 | if (!path_extend(&done, first)) | |
396 | return -ENOMEM; | |
397 | ||
47f0e1b5 DDM |
398 | if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) |
399 | break; | |
400 | ||
f4351959 | 401 | /* And iterate again, but go one directory further down. */ |
ad66c7f1 | 402 | st = st_child; |
ee3455cf | 403 | close_and_replace(fd, child); |
f4351959 LP |
404 | } |
405 | ||
47f0e1b5 | 406 | if (flags & CHASE_PARENT) { |
75adfc3b | 407 | r = stat_verify_directory(&st); |
28aa650e DDM |
408 | if (r < 0) |
409 | return r; | |
410 | } | |
411 | ||
7bf4057d | 412 | if (ret_path) { |
63bfd52f DDM |
413 | if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) { |
414 | _cleanup_free_ char *f = NULL; | |
415 | ||
416 | r = path_extract_filename(done, &f); | |
21eac258 | 417 | if (r < 0 && r != -EADDRNOTAVAIL) |
63bfd52f DDM |
418 | return r; |
419 | ||
21eac258 | 420 | /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */ |
63bfd52f DDM |
421 | free_and_replace(done, f); |
422 | } | |
423 | ||
7bf4057d DDM |
424 | if (!done) { |
425 | done = strdup(append_trail_slash ? "./" : "."); | |
426 | if (!done) | |
427 | return -ENOMEM; | |
428 | } | |
429 | ||
f4351959 | 430 | *ret_path = TAKE_PTR(done); |
7bf4057d | 431 | } |
f4351959 LP |
432 | |
433 | if (ret_fd) { | |
434 | /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a | |
435 | * proper fd by opening /proc/self/fd/xyz. */ | |
436 | ||
437 | assert(fd >= 0); | |
438 | *ret_fd = TAKE_FD(fd); | |
439 | } | |
440 | ||
441 | if (flags & CHASE_STEP) | |
442 | return 1; | |
443 | ||
444 | return exists; | |
445 | ||
446 | chased_one: | |
447 | if (ret_path) { | |
448 | const char *e; | |
449 | ||
7bf4057d DDM |
450 | if (!done) { |
451 | done = strdup(append_trail_slash ? "./" : "."); | |
452 | if (!done) | |
453 | return -ENOMEM; | |
454 | } | |
455 | ||
f4351959 | 456 | /* todo may contain slashes at the beginning. */ |
860f4c6a | 457 | r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e); |
f4351959 LP |
458 | if (r < 0) |
459 | return r; | |
460 | if (r == 0) | |
461 | *ret_path = TAKE_PTR(done); | |
462 | else { | |
463 | char *c; | |
464 | ||
465 | c = path_join(done, e); | |
466 | if (!c) | |
467 | return -ENOMEM; | |
468 | ||
469 | *ret_path = c; | |
470 | } | |
471 | } | |
472 | ||
473 | return 0; | |
474 | } | |
475 | ||
11659e48 | 476 | int chase(const char *path, const char *original_root, ChaseFlags flags, char **ret_path, int *ret_fd) { |
5bc244aa | 477 | _cleanup_free_ char *root = NULL, *absolute = NULL, *p = NULL; |
254d1313 | 478 | _cleanup_close_ int fd = -EBADF, pfd = -EBADF; |
5bc244aa DDM |
479 | int r; |
480 | ||
481 | assert(path); | |
482 | ||
483 | if (isempty(path)) | |
484 | return -EINVAL; | |
485 | ||
486 | /* A root directory of "/" or "" is identical to none */ | |
487 | if (empty_or_root(original_root)) | |
488 | original_root = NULL; | |
489 | ||
490 | if (original_root) { | |
491 | r = path_make_absolute_cwd(original_root, &root); | |
492 | if (r < 0) | |
493 | return r; | |
494 | ||
495 | /* Simplify the root directory, so that it has no duplicate slashes and nothing at the | |
0d68cd72 | 496 | * end. While we won't resolve the root path we still simplify it. */ |
5bc244aa DDM |
497 | path_simplify(root); |
498 | ||
0d68cd72 YW |
499 | assert(path_is_absolute(root)); |
500 | assert(!empty_or_root(root)); | |
501 | ||
5bc244aa DDM |
502 | if (flags & CHASE_PREFIX_ROOT) { |
503 | absolute = path_join(root, path); | |
504 | if (!absolute) | |
505 | return -ENOMEM; | |
506 | } | |
0d68cd72 YW |
507 | |
508 | flags |= CHASE_AT_RESOLVE_IN_ROOT; | |
5bc244aa DDM |
509 | } |
510 | ||
511 | if (!absolute) { | |
512 | r = path_make_absolute_cwd(path, &absolute); | |
513 | if (r < 0) | |
514 | return r; | |
515 | } | |
516 | ||
accc26a0 DDM |
517 | path = path_startswith(absolute, empty_to_root(root)); |
518 | if (!path) | |
519 | return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG, | |
6d5d3e20 YW |
520 | SYNTHETIC_ERRNO(ECHRNG), |
521 | "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.", | |
522 | absolute, empty_to_root(root)); | |
accc26a0 DDM |
523 | |
524 | fd = open(empty_to_root(root), O_CLOEXEC|O_DIRECTORY|O_PATH); | |
525 | if (fd < 0) | |
526 | return -errno; | |
5bc244aa | 527 | |
8bf26bfe | 528 | r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL); |
5bc244aa DDM |
529 | if (r < 0) |
530 | return r; | |
531 | ||
532 | if (ret_path) { | |
63bfd52f DDM |
533 | if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) { |
534 | _cleanup_free_ char *q = NULL; | |
0a9b8878 | 535 | |
63bfd52f DDM |
536 | q = path_join(empty_to_root(root), p); |
537 | if (!q) | |
538 | return -ENOMEM; | |
5bc244aa | 539 | |
63bfd52f | 540 | path_simplify(q); |
7bf4057d | 541 | |
63bfd52f DDM |
542 | if (FLAGS_SET(flags, CHASE_TRAIL_SLASH) && ENDSWITH_SET(path, "/", "/.")) |
543 | if (!strextend(&q, "/")) | |
544 | return -ENOMEM; | |
545 | ||
546 | free_and_replace(p, q); | |
547 | } | |
7bf4057d | 548 | |
63bfd52f | 549 | *ret_path = TAKE_PTR(p); |
5bc244aa DDM |
550 | } |
551 | ||
552 | if (ret_fd) | |
553 | *ret_fd = TAKE_FD(pfd); | |
554 | ||
555 | return r; | |
556 | } | |
557 | ||
11659e48 | 558 | int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path) { |
254d1313 | 559 | _cleanup_close_ int path_fd = -EBADF; |
47f0e1b5 DDM |
560 | _cleanup_free_ char *p = NULL, *fname = NULL; |
561 | mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644; | |
562 | const char *q; | |
f4351959 LP |
563 | int r; |
564 | ||
ea8282b6 | 565 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); |
f4351959 | 566 | |
577efd82 | 567 | if (empty_or_root(root) && !ret_path && |
47f0e1b5 | 568 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) |
f4351959 | 569 | /* Shortcut this call if none of the special features of this call are requested */ |
47f0e1b5 DDM |
570 | return RET_NERRNO(xopenat(AT_FDCWD, path, |
571 | open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), | |
572 | mode)); | |
f4351959 | 573 | |
f461a28d | 574 | r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd); |
f4351959 LP |
575 | if (r < 0) |
576 | return r; | |
577 | assert(path_fd >= 0); | |
578 | ||
47f0e1b5 DDM |
579 | assert_se(q = path_startswith(p, empty_to_root(root))); |
580 | if (isempty(q)) | |
581 | q = "."; | |
582 | ||
708e8870 DDM |
583 | if (!FLAGS_SET(chase_flags, CHASE_PARENT)) { |
584 | r = path_extract_filename(q, &fname); | |
585 | if (r < 0 && r != -EADDRNOTAVAIL) | |
586 | return r; | |
587 | } | |
47f0e1b5 | 588 | |
708e8870 | 589 | r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, mode); |
f4351959 LP |
590 | if (r < 0) |
591 | return r; | |
592 | ||
593 | if (ret_path) | |
594 | *ret_path = TAKE_PTR(p); | |
595 | ||
596 | return r; | |
597 | } | |
598 | ||
11659e48 | 599 | int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { |
254d1313 | 600 | _cleanup_close_ int path_fd = -EBADF; |
f4351959 LP |
601 | _cleanup_free_ char *p = NULL; |
602 | DIR *d; | |
603 | int r; | |
604 | ||
ea8282b6 DDM |
605 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); |
606 | assert(ret_dir); | |
f4351959 | 607 | |
577efd82 | 608 | if (empty_or_root(root) && !ret_path && |
e864dfa6 | 609 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { |
f4351959 LP |
610 | /* Shortcut this call if none of the special features of this call are requested */ |
611 | d = opendir(path); | |
612 | if (!d) | |
613 | return -errno; | |
614 | ||
615 | *ret_dir = d; | |
616 | return 0; | |
617 | } | |
618 | ||
f461a28d | 619 | r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); |
f4351959 LP |
620 | if (r < 0) |
621 | return r; | |
622 | assert(path_fd >= 0); | |
623 | ||
2075b6dd | 624 | d = xopendirat(path_fd, ".", O_NOFOLLOW); |
1fe6e5c1 LP |
625 | if (!d) |
626 | return -errno; | |
f4351959 LP |
627 | |
628 | if (ret_path) | |
629 | *ret_path = TAKE_PTR(p); | |
630 | ||
631 | *ret_dir = d; | |
632 | return 0; | |
633 | } | |
634 | ||
11659e48 | 635 | int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { |
254d1313 | 636 | _cleanup_close_ int path_fd = -EBADF; |
f4351959 LP |
637 | _cleanup_free_ char *p = NULL; |
638 | int r; | |
639 | ||
640 | assert(path); | |
ea8282b6 DDM |
641 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); |
642 | assert(ret_stat); | |
f4351959 | 643 | |
577efd82 | 644 | if (empty_or_root(root) && !ret_path && |
88f2ee86 | 645 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) |
f4351959 | 646 | /* Shortcut this call if none of the special features of this call are requested */ |
88f2ee86 DDM |
647 | return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, |
648 | FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); | |
f4351959 | 649 | |
f461a28d | 650 | r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); |
f4351959 LP |
651 | if (r < 0) |
652 | return r; | |
653 | assert(path_fd >= 0); | |
654 | ||
655 | if (fstat(path_fd, ret_stat) < 0) | |
656 | return -errno; | |
657 | ||
658 | if (ret_path) | |
659 | *ret_path = TAKE_PTR(p); | |
2b2caea2 | 660 | |
88f2ee86 | 661 | return 0; |
2b2caea2 LP |
662 | } |
663 | ||
11659e48 | 664 | int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) { |
254d1313 | 665 | _cleanup_close_ int path_fd = -EBADF; |
2b2caea2 LP |
666 | _cleanup_free_ char *p = NULL; |
667 | int r; | |
668 | ||
669 | assert(path); | |
ea8282b6 | 670 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); |
2b2caea2 | 671 | |
577efd82 | 672 | if (empty_or_root(root) && !ret_path && |
88f2ee86 | 673 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) |
2b2caea2 | 674 | /* Shortcut this call if none of the special features of this call are requested */ |
88f2ee86 DDM |
675 | return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, |
676 | FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); | |
2b2caea2 | 677 | |
f461a28d | 678 | r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); |
2b2caea2 LP |
679 | if (r < 0) |
680 | return r; | |
681 | assert(path_fd >= 0); | |
682 | ||
683 | r = access_fd(path_fd, access_mode); | |
684 | if (r < 0) | |
685 | return r; | |
686 | ||
687 | if (ret_path) | |
688 | *ret_path = TAKE_PTR(p); | |
f4351959 | 689 | |
88f2ee86 | 690 | return 0; |
f4351959 | 691 | } |
01bebba3 | 692 | |
f461a28d | 693 | int chase_and_fopen_unlocked( |
01bebba3 LP |
694 | const char *path, |
695 | const char *root, | |
f461a28d | 696 | ChaseFlags chase_flags, |
01bebba3 LP |
697 | const char *open_flags, |
698 | char **ret_path, | |
699 | FILE **ret_file) { | |
700 | ||
701 | _cleanup_free_ char *final_path = NULL; | |
254d1313 | 702 | _cleanup_close_ int fd = -EBADF; |
01bebba3 LP |
703 | int mode_flags, r; |
704 | ||
705 | assert(path); | |
ea8282b6 | 706 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); |
01bebba3 LP |
707 | assert(open_flags); |
708 | assert(ret_file); | |
709 | ||
710 | mode_flags = fopen_mode_to_flags(open_flags); | |
711 | if (mode_flags < 0) | |
712 | return mode_flags; | |
713 | ||
f461a28d | 714 | fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL); |
01bebba3 LP |
715 | if (fd < 0) |
716 | return fd; | |
717 | ||
718 | r = take_fdopen_unlocked(&fd, open_flags, ret_file); | |
719 | if (r < 0) | |
720 | return r; | |
721 | ||
722 | if (ret_path) | |
723 | *ret_path = TAKE_PTR(final_path); | |
724 | ||
725 | return 0; | |
726 | } | |
1132fd73 | 727 | |
11659e48 | 728 | int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { |
47f0e1b5 | 729 | _cleanup_free_ char *p = NULL, *fname = NULL; |
92651a7a | 730 | _cleanup_close_ int fd = -EBADF; |
1132fd73 LN |
731 | int r; |
732 | ||
733 | assert(path); | |
ea8282b6 | 734 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); |
1132fd73 | 735 | |
f461a28d | 736 | fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); |
1132fd73 LN |
737 | if (fd < 0) |
738 | return fd; | |
739 | ||
47f0e1b5 DDM |
740 | r = path_extract_filename(p, &fname); |
741 | if (r < 0) | |
742 | return r; | |
1132fd73 LN |
743 | |
744 | if (unlinkat(fd, fname, unlink_flags) < 0) | |
745 | return -errno; | |
746 | ||
747 | if (ret_path) | |
47f0e1b5 | 748 | *ret_path = TAKE_PTR(p); |
1132fd73 LN |
749 | |
750 | return 0; | |
751 | } | |
9a98c0f2 | 752 | |
670afc18 DDM |
753 | int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) { |
754 | int pfd, r; | |
755 | ||
756 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
757 | ||
758 | r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd); | |
759 | if (r < 0) | |
760 | return r; | |
761 | ||
762 | return pfd; | |
763 | } | |
764 | ||
11659e48 | 765 | int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path) { |
9a98c0f2 | 766 | _cleanup_close_ int path_fd = -EBADF; |
47f0e1b5 DDM |
767 | _cleanup_free_ char *p = NULL, *fname = NULL; |
768 | mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644; | |
9a98c0f2 DDM |
769 | int r; |
770 | ||
ea8282b6 | 771 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); |
9a98c0f2 DDM |
772 | |
773 | if (dir_fd == AT_FDCWD && !ret_path && | |
774 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) | |
775 | /* Shortcut this call if none of the special features of this call are requested */ | |
47f0e1b5 DDM |
776 | return RET_NERRNO(xopenat(dir_fd, path, |
777 | open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), | |
778 | mode)); | |
9a98c0f2 | 779 | |
f461a28d | 780 | r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd); |
9a98c0f2 DDM |
781 | if (r < 0) |
782 | return r; | |
9a98c0f2 | 783 | |
708e8870 DDM |
784 | if (!FLAGS_SET(chase_flags, CHASE_PARENT)) { |
785 | r = path_extract_filename(p, &fname); | |
786 | if (r < 0 && r != -EADDRNOTAVAIL) | |
787 | return r; | |
788 | } | |
47f0e1b5 | 789 | |
708e8870 | 790 | r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, mode); |
9a98c0f2 DDM |
791 | if (r < 0) |
792 | return r; | |
793 | ||
794 | if (ret_path) | |
795 | *ret_path = TAKE_PTR(p); | |
796 | ||
797 | return r; | |
798 | } | |
799 | ||
11659e48 | 800 | int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { |
12ef2617 DDM |
801 | _cleanup_close_ int path_fd = -EBADF; |
802 | _cleanup_free_ char *p = NULL; | |
803 | DIR *d; | |
804 | int r; | |
805 | ||
806 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
807 | assert(ret_dir); | |
808 | ||
809 | if (dir_fd == AT_FDCWD && !ret_path && | |
810 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { | |
811 | /* Shortcut this call if none of the special features of this call are requested */ | |
812 | d = opendir(path); | |
813 | if (!d) | |
814 | return -errno; | |
815 | ||
816 | *ret_dir = d; | |
817 | return 0; | |
818 | } | |
819 | ||
f461a28d | 820 | r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); |
12ef2617 DDM |
821 | if (r < 0) |
822 | return r; | |
823 | assert(path_fd >= 0); | |
824 | ||
825 | d = xopendirat(path_fd, ".", O_NOFOLLOW); | |
826 | if (!d) | |
827 | return -errno; | |
828 | ||
829 | if (ret_path) | |
830 | *ret_path = TAKE_PTR(p); | |
831 | ||
832 | *ret_dir = d; | |
833 | return 0; | |
834 | } | |
835 | ||
11659e48 | 836 | int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { |
12ef2617 DDM |
837 | _cleanup_close_ int path_fd = -EBADF; |
838 | _cleanup_free_ char *p = NULL; | |
839 | int r; | |
840 | ||
841 | assert(path); | |
842 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
843 | assert(ret_stat); | |
844 | ||
845 | if (dir_fd == AT_FDCWD && !ret_path && | |
846 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) | |
847 | /* Shortcut this call if none of the special features of this call are requested */ | |
848 | return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, | |
849 | FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); | |
850 | ||
f461a28d | 851 | r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); |
12ef2617 DDM |
852 | if (r < 0) |
853 | return r; | |
854 | assert(path_fd >= 0); | |
855 | ||
856 | if (fstat(path_fd, ret_stat) < 0) | |
857 | return -errno; | |
858 | ||
859 | if (ret_path) | |
860 | *ret_path = TAKE_PTR(p); | |
861 | ||
862 | return 0; | |
863 | } | |
864 | ||
11659e48 | 865 | int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) { |
12ef2617 DDM |
866 | _cleanup_close_ int path_fd = -EBADF; |
867 | _cleanup_free_ char *p = NULL; | |
868 | int r; | |
869 | ||
870 | assert(path); | |
871 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
872 | ||
873 | if (dir_fd == AT_FDCWD && !ret_path && | |
874 | (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) | |
875 | /* Shortcut this call if none of the special features of this call are requested */ | |
876 | return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, | |
877 | FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); | |
878 | ||
f461a28d | 879 | r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); |
12ef2617 DDM |
880 | if (r < 0) |
881 | return r; | |
882 | assert(path_fd >= 0); | |
883 | ||
884 | r = access_fd(path_fd, access_mode); | |
885 | if (r < 0) | |
886 | return r; | |
887 | ||
888 | if (ret_path) | |
889 | *ret_path = TAKE_PTR(p); | |
890 | ||
891 | return 0; | |
892 | } | |
893 | ||
f461a28d | 894 | int chase_and_fopenat_unlocked( |
12ef2617 DDM |
895 | int dir_fd, |
896 | const char *path, | |
f461a28d | 897 | ChaseFlags chase_flags, |
12ef2617 DDM |
898 | const char *open_flags, |
899 | char **ret_path, | |
900 | FILE **ret_file) { | |
901 | ||
902 | _cleanup_free_ char *final_path = NULL; | |
903 | _cleanup_close_ int fd = -EBADF; | |
904 | int mode_flags, r; | |
905 | ||
906 | assert(path); | |
907 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); | |
908 | assert(open_flags); | |
909 | assert(ret_file); | |
910 | ||
911 | mode_flags = fopen_mode_to_flags(open_flags); | |
912 | if (mode_flags < 0) | |
913 | return mode_flags; | |
914 | ||
f461a28d | 915 | fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL); |
12ef2617 DDM |
916 | if (fd < 0) |
917 | return fd; | |
918 | ||
919 | r = take_fdopen_unlocked(&fd, open_flags, ret_file); | |
920 | if (r < 0) | |
921 | return r; | |
922 | ||
923 | if (ret_path) | |
924 | *ret_path = TAKE_PTR(final_path); | |
925 | ||
926 | return 0; | |
927 | } | |
928 | ||
11659e48 | 929 | int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { |
12ef2617 DDM |
930 | _cleanup_free_ char *p = NULL, *fname = NULL; |
931 | _cleanup_close_ int fd = -EBADF; | |
932 | int r; | |
933 | ||
934 | assert(path); | |
935 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); | |
936 | ||
f461a28d | 937 | fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); |
12ef2617 DDM |
938 | if (fd < 0) |
939 | return fd; | |
940 | ||
941 | r = path_extract_filename(p, &fname); | |
942 | if (r < 0) | |
943 | return r; | |
944 | ||
945 | if (unlinkat(fd, fname, unlink_flags) < 0) | |
946 | return -errno; | |
947 | ||
948 | if (ret_path) | |
949 | *ret_path = TAKE_PTR(p); | |
950 | ||
951 | return 0; | |
952 | } | |
670afc18 | 953 | |
11659e48 | 954 | int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) { |
670afc18 DDM |
955 | int pfd, r; |
956 | ||
957 | assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); | |
958 | ||
959 | r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd); | |
960 | if (r < 0) | |
961 | return r; | |
962 | ||
963 | return pfd; | |
964 | } |