]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/chase-symlinks.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / basic / chase-symlinks.c
CommitLineData
f4351959
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <linux/magic.h>
4
5#include "alloc-util.h"
6#include "chase-symlinks.h"
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
16bool 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
8f47f880 27static int log_unsafe_transition(int a, int b, const char *path, ChaseSymlinksFlags 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
8f47f880 47static int log_autofs_mount_point(int fd, const char *path, ChaseSymlinksFlags 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
d43e78b6
LP
60static int log_prohibited_symlink(int fd, ChaseSymlinksFlags flags) {
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
5bc244aa
DDM
75int chase_symlinks_at(
76 int dir_fd,
8f47f880 77 const char *path,
8f47f880
LP
78 ChaseSymlinksFlags flags,
79 char **ret_path,
80 int *ret_fd) {
81
5bc244aa 82 _cleanup_free_ char *buffer = NULL, *done = NULL;
254d1313 83 _cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
f4351959
LP
84 unsigned max_follow = CHASE_SYMLINKS_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
85 bool exists = true, append_trail_slash = false;
86 struct stat previous_stat;
87 const char *todo;
88 int r;
89
90 assert(path);
5bc244aa
DDM
91 assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
92 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
f4351959
LP
93
94 /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
95 if ((flags & CHASE_NONEXISTENT) && ret_fd)
96 return -EINVAL;
97
98 if ((flags & CHASE_STEP) && ret_fd)
99 return -EINVAL;
100
101 if (isempty(path))
5bc244aa 102 path = ".";
f4351959 103
5bc244aa
DDM
104 /* This function resolves symlinks of the path relative to the given directory file descriptor. If
105 * CHASE_SYMLINKS_RESOLVE_IN_ROOT is specified, symlinks are resolved relative to the given directory
106 * file descriptor. Otherwise, they are resolved relative to the root directory of the host.
107 *
108 * Note that when CHASE_SYMLINKS_RESOLVE_IN_ROOT is specified and we find an absolute symlink, it is
109 * resolved relative to given directory file descriptor and not the root of the host. Also, when
110 * following relative symlinks, this functions ensure they cannot be used to "escape" the given
111 * directory file descriptor. The "path" parameter is always interpreted relative to the given
112 * directory file descriptor. If the given directory file descriptor is AT_FDCWD and "path" is
113 * absolute, it is interpreted relative to the root directory of the host.
f4351959 114 *
5bc244aa
DDM
115 * If "dir_fd" is a valid directory fd, "path" is an absolute path and "ret_path" is not NULL, this
116 * functions returns a relative path in "ret_path" because openat() like functions generally ignore
117 * the directory fd if they are provided with an absolute path. On the other hand, if "dir_fd" is
118 * AT_FDCWD and "path" is an absolute path, we need to return an absolute path in "ret_path" because
119 * otherwise, if the caller passes the returned relative path to another openat() like function, it
120 * would be resolved relative to the current working directory instead of to "/".
f4351959 121 *
5bc244aa
DDM
122 * Algorithmically this operates on two path buffers: "done" are the components of the path we
123 * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we
124 * still need to process. On each iteration, we move one component from "todo" to "done", processing
125 * it's special meaning each time. We always keep an O_PATH fd to the component we are currently
126 * processing, thus keeping lookup races to a minimum.
f4351959 127 *
5bc244aa
DDM
128 * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute
129 * path you got as-is: fully qualified and relative to your host's root. Optionally, specify the
130 * "dir_fd" parameter to tell this function what to do when encountering a symlink with an absolute
131 * path as directory: resolve it relative to the given directory file descriptor.
f4351959
LP
132 *
133 * There are five ways to invoke this function:
134 *
135 * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is
136 * returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0
137 * is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is
138 * returned if the destination was found, -ENOENT if it wasn't.
139 *
140 * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file
141 * descriptor is returned as return value. This is useful to open files relative to some root
5bc244aa
DDM
142 * directory. Note that the returned O_PATH file descriptors must be converted into a regular one
143 * (using fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be
144 * combined with CHASE_NONEXISTENT.
f4351959 145 *
5bc244aa
DDM
146 * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only
147 * the first symlink or ".." component of the path is resolved, and the resulting path is
148 * returned. This is useful if a caller wants to trace the path through the file system verbosely.
149 * Returns < 0 on error, > 0 if the path is fully normalized, and == 0 for each normalization
150 * step. This may be combined with CHASE_NONEXISTENT, in which case 1 is returned when a component
151 * is not found.
f4351959 152 *
5bc244aa
DDM
153 * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions
154 * from unprivileged to privileged files or directories. In such cases the return value is
155 * -ENOLINK. If CHASE_WARN is also set, a warning describing the unsafe transition is emitted.
156 * CHASE_WARN cannot be used in PID 1.
f4351959
LP
157 *
158 * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization
159 * is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of
9154bd57 160 * the mount point is emitted. CHASE_WARN cannot be used in PID 1.
f4351959
LP
161 */
162
5bc244aa
DDM
163 if (!(flags & (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP)) &&
164 !ret_path && ret_fd) {
f4351959 165
5bc244aa
DDM
166 /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
167 * set and doesn't care about any of the other special features we provide either. */
168 r = openat(dir_fd, path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
f4351959
LP
169 if (r < 0)
170 return -errno;
171
172 *ret_fd = r;
173 return 0;
174 }
175
5bc244aa
DDM
176 buffer = strdup(path);
177 if (!buffer)
178 return -ENOMEM;
f4351959 179
5bc244aa
DDM
180 bool need_absolute = !FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT) && path_is_absolute(path);
181 if (need_absolute) {
182 done = strdup("/");
183 if (!done)
184 return -ENOMEM;
f4351959
LP
185 }
186
5bc244aa
DDM
187 if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
188 root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
189 else
190 root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
191 if (root_fd < 0)
192 return -errno;
f4351959 193
5bc244aa
DDM
194 if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT) || !path_is_absolute(path))
195 fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
196 else
197 fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
f4351959
LP
198 if (fd < 0)
199 return -errno;
200
5bc244aa
DDM
201 if (fstat(fd, &previous_stat) < 0)
202 return -errno;
f4351959
LP
203
204 if (flags & CHASE_TRAIL_SLASH)
205 append_trail_slash = endswith(buffer, "/") || endswith(buffer, "/.");
206
5bc244aa 207 for (todo = buffer;;) {
f4351959
LP
208 _cleanup_free_ char *first = NULL;
209 _cleanup_close_ int child = -1;
210 struct stat st;
211 const char *e;
212
860f4c6a 213 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
f4351959
LP
214 if (r < 0)
215 return r;
216 if (r == 0) { /* We reached the end. */
217 if (append_trail_slash)
218 if (!strextend(&done, "/"))
219 return -ENOMEM;
220 break;
221 }
222
223 first = strndup(e, r);
224 if (!first)
225 return -ENOMEM;
226
227 /* Two dots? Then chop off the last bit of what we already found out. */
228 if (path_equal(first, "..")) {
229 _cleanup_free_ char *parent = NULL;
254d1313 230 _cleanup_close_ int fd_parent = -EBADF;
f4351959 231
5bc244aa
DDM
232 /* If we already are at the top, then going up will not change anything. This is
233 * in-line with how the kernel handles this. */
234 if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
f4351959
LP
235 continue;
236
5bc244aa
DDM
237 fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
238 if (fd_parent < 0)
239 return -errno;
f4351959 240
5bc244aa
DDM
241 if (fstat(fd_parent, &st) < 0)
242 return -errno;
243
244 /* If we opened the same directory, that means we're at the host root directory, so
245 * going up won't change anything. */
246 if (st.st_dev == previous_stat.st_dev && st.st_ino == previous_stat.st_ino)
f4351959
LP
247 continue;
248
5bc244aa
DDM
249 r = path_extract_directory(done, &parent);
250 if (r >= 0 || r == -EDESTADDRREQ)
251 free_and_replace(done, parent);
252 else if (IN_SET(r, -EINVAL, -EADDRNOTAVAIL)) {
253 /* If we're at the top of "dir_fd", start appending ".." to "done". */
254 if (!path_extend(&done, ".."))
255 return -ENOMEM;
256 } else
257 return r;
f4351959
LP
258
259 if (flags & CHASE_STEP)
260 goto chased_one;
261
f4351959 262 if (flags & CHASE_SAFE) {
f4351959
LP
263 if (unsafe_transition(&previous_stat, &st))
264 return log_unsafe_transition(fd, fd_parent, path, flags);
265
266 previous_stat = st;
267 }
268
ee3455cf 269 close_and_replace(fd, fd_parent);
f4351959
LP
270
271 continue;
272 }
273
274 /* Otherwise let's see what this is. */
275 child = openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH);
276 if (child < 0) {
277 if (errno == ENOENT &&
278 (flags & CHASE_NONEXISTENT) &&
279 (isempty(todo) || path_is_safe(todo))) {
280 /* If CHASE_NONEXISTENT is set, and the path does not exist, then
281 * that's OK, return what we got so far. But don't allow this if the
282 * remaining path contains "../" or something else weird. */
283
284 if (!path_extend(&done, first, todo))
285 return -ENOMEM;
286
287 exists = false;
288 break;
289 }
290
291 return -errno;
292 }
293
294 if (fstat(child, &st) < 0)
295 return -errno;
296 if ((flags & CHASE_SAFE) &&
297 unsafe_transition(&previous_stat, &st))
298 return log_unsafe_transition(fd, child, path, flags);
299
300 previous_stat = st;
301
302 if ((flags & CHASE_NO_AUTOFS) &&
303 fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
304 return log_autofs_mount_point(child, path, flags);
305
306 if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
307 _cleanup_free_ char *destination = NULL;
308
d43e78b6
LP
309 if (flags & CHASE_PROHIBIT_SYMLINKS)
310 return log_prohibited_symlink(child, flags);
311
f4351959
LP
312 /* This is a symlink, in this case read the destination. But let's make sure we
313 * don't follow symlinks without bounds. */
314 if (--max_follow <= 0)
315 return -ELOOP;
316
317 r = readlinkat_malloc(fd, first, &destination);
318 if (r < 0)
319 return r;
320 if (isempty(destination))
321 return -EINVAL;
322
323 if (path_is_absolute(destination)) {
324
5bc244aa
DDM
325 /* An absolute destination. Start the loop from the beginning, but use the
326 * root file descriptor as base. */
f4351959
LP
327
328 safe_close(fd);
5bc244aa 329 fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
f4351959 330 if (fd < 0)
5bc244aa 331 return fd;
f4351959
LP
332
333 if (flags & CHASE_SAFE) {
334 if (fstat(fd, &st) < 0)
335 return -errno;
336
337 if (unsafe_transition(&previous_stat, &st))
338 return log_unsafe_transition(child, fd, path, flags);
339
340 previous_stat = st;
341 }
342
5bc244aa 343 r = free_and_strdup(&done, need_absolute ? "/" : NULL);
f4351959
LP
344 if (r < 0)
345 return r;
346 }
347
348 /* Prefix what's left to do with what we just read, and start the loop again, but
349 * remain in the current directory. */
350 if (!path_extend(&destination, todo))
351 return -ENOMEM;
352
353 free_and_replace(buffer, destination);
354 todo = buffer;
355
356 if (flags & CHASE_STEP)
357 goto chased_one;
358
359 continue;
360 }
361
362 /* If this is not a symlink, then let's just add the name we read to what we already verified. */
363 if (!path_extend(&done, first))
364 return -ENOMEM;
365
366 /* And iterate again, but go one directory further down. */
ee3455cf 367 close_and_replace(fd, child);
f4351959
LP
368 }
369
370 if (ret_path)
371 *ret_path = TAKE_PTR(done);
372
373 if (ret_fd) {
374 /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
375 * proper fd by opening /proc/self/fd/xyz. */
376
377 assert(fd >= 0);
378 *ret_fd = TAKE_FD(fd);
379 }
380
381 if (flags & CHASE_STEP)
382 return 1;
383
384 return exists;
385
386chased_one:
387 if (ret_path) {
388 const char *e;
389
390 /* todo may contain slashes at the beginning. */
860f4c6a 391 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
f4351959
LP
392 if (r < 0)
393 return r;
394 if (r == 0)
395 *ret_path = TAKE_PTR(done);
396 else {
397 char *c;
398
399 c = path_join(done, e);
400 if (!c)
401 return -ENOMEM;
402
403 *ret_path = c;
404 }
405 }
406
407 return 0;
408}
409
5bc244aa
DDM
410int chase_symlinks(
411 const char *path,
412 const char *original_root,
413 ChaseSymlinksFlags flags,
414 char **ret_path,
415 int *ret_fd) {
416
417 _cleanup_free_ char *root = NULL, *absolute = NULL, *p = NULL;
254d1313 418 _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
5bc244aa
DDM
419 int r;
420
421 assert(path);
422
423 if (isempty(path))
424 return -EINVAL;
425
426 /* A root directory of "/" or "" is identical to none */
427 if (empty_or_root(original_root))
428 original_root = NULL;
429
430 if (original_root) {
431 r = path_make_absolute_cwd(original_root, &root);
432 if (r < 0)
433 return r;
434
435 /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
436 * end. While we won't resolve the root path we still simplify it. Note that dropping the
437 * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
438 * anyway. Moreover at the end of this function after processing everything we'll always turn
439 * the empty string back to "/". */
440 delete_trailing_chars(root, "/");
441 path_simplify(root);
442
443 if (flags & CHASE_PREFIX_ROOT) {
444 absolute = path_join(root, path);
445 if (!absolute)
446 return -ENOMEM;
447 }
448 }
449
450 if (!absolute) {
451 r = path_make_absolute_cwd(path, &absolute);
452 if (r < 0)
453 return r;
454 }
455
456 if (root) {
457 path = path_startswith(absolute, root);
458 if (!path)
459 return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG,
460 SYNTHETIC_ERRNO(ECHRNG),
461 "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
462 absolute, root);
5bc244aa 463
5bc244aa
DDM
464 fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
465 if (fd < 0)
466 return -errno;
467
468 flags |= CHASE_AT_RESOLVE_IN_ROOT;
2877b147
DDM
469 } else {
470 path = absolute;
5bc244aa 471 fd = AT_FDCWD;
2877b147 472 }
5bc244aa
DDM
473
474 r = chase_symlinks_at(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
475 if (r < 0)
476 return r;
477
478 if (ret_path) {
479 char *q = path_join(root, p);
480 if (!q)
481 return -ENOMEM;
482
483 *ret_path = TAKE_PTR(q);
484 }
485
486 if (ret_fd)
487 *ret_fd = TAKE_FD(pfd);
488
489 return r;
490}
491
f4351959
LP
492int chase_symlinks_and_open(
493 const char *path,
494 const char *root,
8f47f880 495 ChaseSymlinksFlags chase_flags,
f4351959
LP
496 int open_flags,
497 char **ret_path) {
498
254d1313 499 _cleanup_close_ int path_fd = -EBADF;
f4351959
LP
500 _cleanup_free_ char *p = NULL;
501 int r;
502
81a7eac1 503 if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
f4351959
LP
504 return -EINVAL;
505
506 if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) {
507 /* Shortcut this call if none of the special features of this call are requested */
69570232 508 r = open(path, open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
f4351959
LP
509 if (r < 0)
510 return -errno;
511
512 return r;
513 }
514
515 r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
516 if (r < 0)
517 return r;
518 assert(path_fd >= 0);
519
520 r = fd_reopen(path_fd, open_flags);
521 if (r < 0)
522 return r;
523
524 if (ret_path)
525 *ret_path = TAKE_PTR(p);
526
527 return r;
528}
529
530int chase_symlinks_and_opendir(
531 const char *path,
532 const char *root,
8f47f880 533 ChaseSymlinksFlags chase_flags,
f4351959
LP
534 char **ret_path,
535 DIR **ret_dir) {
536
254d1313 537 _cleanup_close_ int path_fd = -EBADF;
f4351959
LP
538 _cleanup_free_ char *p = NULL;
539 DIR *d;
540 int r;
541
542 if (!ret_dir)
543 return -EINVAL;
81a7eac1 544 if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
f4351959
LP
545 return -EINVAL;
546
547 if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) {
548 /* Shortcut this call if none of the special features of this call are requested */
549 d = opendir(path);
550 if (!d)
551 return -errno;
552
553 *ret_dir = d;
554 return 0;
555 }
556
1fe6e5c1 557 r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
f4351959
LP
558 if (r < 0)
559 return r;
560 assert(path_fd >= 0);
561
2075b6dd 562 d = xopendirat(path_fd, ".", O_NOFOLLOW);
1fe6e5c1
LP
563 if (!d)
564 return -errno;
f4351959
LP
565
566 if (ret_path)
567 *ret_path = TAKE_PTR(p);
568
569 *ret_dir = d;
570 return 0;
571}
572
573int chase_symlinks_and_stat(
574 const char *path,
575 const char *root,
8f47f880 576 ChaseSymlinksFlags chase_flags,
f4351959
LP
577 char **ret_path,
578 struct stat *ret_stat,
579 int *ret_fd) {
580
254d1313 581 _cleanup_close_ int path_fd = -EBADF;
f4351959
LP
582 _cleanup_free_ char *p = NULL;
583 int r;
584
585 assert(path);
586 assert(ret_stat);
587
81a7eac1 588 if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
f4351959
LP
589 return -EINVAL;
590
37b9bc56 591 if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0 && !ret_fd) {
f4351959 592 /* Shortcut this call if none of the special features of this call are requested */
69570232
LP
593
594 if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
f4351959
LP
595 return -errno;
596
597 return 1;
598 }
599
600 r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
601 if (r < 0)
602 return r;
603 assert(path_fd >= 0);
604
605 if (fstat(path_fd, ret_stat) < 0)
606 return -errno;
607
608 if (ret_path)
609 *ret_path = TAKE_PTR(p);
2b2caea2
LP
610 if (ret_fd)
611 *ret_fd = TAKE_FD(path_fd);
612
613 return 1;
614}
615
616int chase_symlinks_and_access(
617 const char *path,
618 const char *root,
619 ChaseSymlinksFlags chase_flags,
620 int access_mode,
621 char **ret_path,
622 int *ret_fd) {
623
254d1313 624 _cleanup_close_ int path_fd = -EBADF;
2b2caea2
LP
625 _cleanup_free_ char *p = NULL;
626 int r;
627
628 assert(path);
629
630 if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
631 return -EINVAL;
632
633 if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0 && !ret_fd) {
634 /* Shortcut this call if none of the special features of this call are requested */
635
636 if (faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
637 return -errno;
638
639 return 1;
640 }
641
642 r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
643 if (r < 0)
644 return r;
645 assert(path_fd >= 0);
646
647 r = access_fd(path_fd, access_mode);
648 if (r < 0)
649 return r;
650
651 if (ret_path)
652 *ret_path = TAKE_PTR(p);
f4351959
LP
653 if (ret_fd)
654 *ret_fd = TAKE_FD(path_fd);
655
656 return 1;
657}
01bebba3
LP
658
659int chase_symlinks_and_fopen_unlocked(
660 const char *path,
661 const char *root,
8f47f880 662 ChaseSymlinksFlags chase_flags,
01bebba3
LP
663 const char *open_flags,
664 char **ret_path,
665 FILE **ret_file) {
666
667 _cleanup_free_ char *final_path = NULL;
254d1313 668 _cleanup_close_ int fd = -EBADF;
01bebba3
LP
669 int mode_flags, r;
670
671 assert(path);
672 assert(open_flags);
673 assert(ret_file);
674
675 mode_flags = fopen_mode_to_flags(open_flags);
676 if (mode_flags < 0)
677 return mode_flags;
678
679 fd = chase_symlinks_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
680 if (fd < 0)
681 return fd;
682
683 r = take_fdopen_unlocked(&fd, open_flags, ret_file);
684 if (r < 0)
685 return r;
686
687 if (ret_path)
688 *ret_path = TAKE_PTR(final_path);
689
690 return 0;
691}