]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/chase-symlinks.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / basic / chase-symlinks.c
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"
8 #include "fileio.h"
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
27 static int log_unsafe_transition(int a, int b, const char *path, ChaseSymlinksFlags flags) {
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.",
44 strna(n1), strna(user_a), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path);
45 }
46
47 static int log_autofs_mount_point(int fd, const char *path, ChaseSymlinksFlags flags) {
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
60 static 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
75 int chase_symlinks_at(
76 int dir_fd,
77 const char *path,
78 ChaseSymlinksFlags flags,
79 char **ret_path,
80 int *ret_fd) {
81
82 _cleanup_free_ char *buffer = NULL, *done = NULL;
83 _cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
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);
91 assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
92 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
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))
102 path = ".";
103
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.
114 *
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 "/".
121 *
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.
127 *
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.
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
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.
145 *
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.
152 *
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.
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
160 * the mount point is emitted. CHASE_WARN cannot be used in PID 1.
161 */
162
163 if (!(flags & (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP)) &&
164 !ret_path && ret_fd) {
165
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));
169 if (r < 0)
170 return -errno;
171
172 *ret_fd = r;
173 return 0;
174 }
175
176 buffer = strdup(path);
177 if (!buffer)
178 return -ENOMEM;
179
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;
185 }
186
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;
193
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);
198 if (fd < 0)
199 return -errno;
200
201 if (fstat(fd, &previous_stat) < 0)
202 return -errno;
203
204 if (flags & CHASE_TRAIL_SLASH)
205 append_trail_slash = endswith(buffer, "/") || endswith(buffer, "/.");
206
207 for (todo = buffer;;) {
208 _cleanup_free_ char *first = NULL;
209 _cleanup_close_ int child = -1;
210 struct stat st;
211 const char *e;
212
213 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
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;
230 _cleanup_close_ int fd_parent = -EBADF;
231
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))
235 continue;
236
237 fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
238 if (fd_parent < 0)
239 return -errno;
240
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)
247 continue;
248
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;
258
259 if (flags & CHASE_STEP)
260 goto chased_one;
261
262 if (flags & CHASE_SAFE) {
263 if (unsafe_transition(&previous_stat, &st))
264 return log_unsafe_transition(fd, fd_parent, path, flags);
265
266 previous_stat = st;
267 }
268
269 close_and_replace(fd, fd_parent);
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
309 if (flags & CHASE_PROHIBIT_SYMLINKS)
310 return log_prohibited_symlink(child, flags);
311
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
325 /* An absolute destination. Start the loop from the beginning, but use the
326 * root file descriptor as base. */
327
328 safe_close(fd);
329 fd = fd_reopen(root_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
330 if (fd < 0)
331 return fd;
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
343 r = free_and_strdup(&done, need_absolute ? "/" : NULL);
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. */
367 close_and_replace(fd, child);
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
386 chased_one:
387 if (ret_path) {
388 const char *e;
389
390 /* todo may contain slashes at the beginning. */
391 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
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
410 int 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;
418 _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
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);
463
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;
469 } else {
470 path = absolute;
471 fd = AT_FDCWD;
472 }
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
492 int chase_symlinks_and_open(
493 const char *path,
494 const char *root,
495 ChaseSymlinksFlags chase_flags,
496 int open_flags,
497 char **ret_path) {
498
499 _cleanup_close_ int path_fd = -EBADF;
500 _cleanup_free_ char *p = NULL;
501 int r;
502
503 if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
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 */
508 r = open(path, open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
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
530 int chase_symlinks_and_opendir(
531 const char *path,
532 const char *root,
533 ChaseSymlinksFlags chase_flags,
534 char **ret_path,
535 DIR **ret_dir) {
536
537 _cleanup_close_ int path_fd = -EBADF;
538 _cleanup_free_ char *p = NULL;
539 DIR *d;
540 int r;
541
542 if (!ret_dir)
543 return -EINVAL;
544 if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
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
557 r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
558 if (r < 0)
559 return r;
560 assert(path_fd >= 0);
561
562 d = xopendirat(path_fd, ".", O_NOFOLLOW);
563 if (!d)
564 return -errno;
565
566 if (ret_path)
567 *ret_path = TAKE_PTR(p);
568
569 *ret_dir = d;
570 return 0;
571 }
572
573 int chase_symlinks_and_stat(
574 const char *path,
575 const char *root,
576 ChaseSymlinksFlags chase_flags,
577 char **ret_path,
578 struct stat *ret_stat,
579 int *ret_fd) {
580
581 _cleanup_close_ int path_fd = -EBADF;
582 _cleanup_free_ char *p = NULL;
583 int r;
584
585 assert(path);
586 assert(ret_stat);
587
588 if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
589 return -EINVAL;
590
591 if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0 && !ret_fd) {
592 /* Shortcut this call if none of the special features of this call are requested */
593
594 if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
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);
610 if (ret_fd)
611 *ret_fd = TAKE_FD(path_fd);
612
613 return 1;
614 }
615
616 int 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
624 _cleanup_close_ int path_fd = -EBADF;
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);
653 if (ret_fd)
654 *ret_fd = TAKE_FD(path_fd);
655
656 return 1;
657 }
658
659 int chase_symlinks_and_fopen_unlocked(
660 const char *path,
661 const char *root,
662 ChaseSymlinksFlags chase_flags,
663 const char *open_flags,
664 char **ret_path,
665 FILE **ret_file) {
666
667 _cleanup_free_ char *final_path = NULL;
668 _cleanup_close_ int fd = -EBADF;
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 }