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