]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/fs-util.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / basic / fs-util.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
f4f15635
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
11c3a366
TA
21#include <errno.h>
22#include <stddef.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <sys/stat.h>
655f2da0 27#include <linux/magic.h>
11c3a366
TA
28#include <time.h>
29#include <unistd.h>
30
b5efdb8a 31#include "alloc-util.h"
f4f15635
LP
32#include "dirent-util.h"
33#include "fd-util.h"
34#include "fileio.h"
35#include "fs-util.h"
11c3a366
TA
36#include "log.h"
37#include "macro.h"
38#include "missing.h"
93cc7779
TA
39#include "mkdir.h"
40#include "parse-util.h"
41#include "path-util.h"
34a8f081 42#include "stat-util.h"
430fbf8e 43#include "stdio-util.h"
f4f15635
LP
44#include "string-util.h"
45#include "strv.h"
93cc7779 46#include "time-util.h"
ee104e11 47#include "user-util.h"
f4f15635
LP
48#include "util.h"
49
50int unlink_noerrno(const char *path) {
51 PROTECT_ERRNO;
52 int r;
53
54 r = unlink(path);
55 if (r < 0)
56 return -errno;
57
58 return 0;
59}
60
61int rmdir_parents(const char *path, const char *stop) {
62 size_t l;
63 int r = 0;
64
65 assert(path);
66 assert(stop);
67
68 l = strlen(path);
69
70 /* Skip trailing slashes */
71 while (l > 0 && path[l-1] == '/')
72 l--;
73
74 while (l > 0) {
75 char *t;
76
77 /* Skip last component */
78 while (l > 0 && path[l-1] != '/')
79 l--;
80
81 /* Skip trailing slashes */
82 while (l > 0 && path[l-1] == '/')
83 l--;
84
85 if (l <= 0)
86 break;
87
88 t = strndup(path, l);
89 if (!t)
90 return -ENOMEM;
91
92 if (path_startswith(stop, t)) {
93 free(t);
94 return 0;
95 }
96
97 r = rmdir(t);
98 free(t);
99
100 if (r < 0)
101 if (errno != ENOENT)
102 return -errno;
103 }
104
105 return 0;
106}
107
f4f15635
LP
108int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
109 struct stat buf;
110 int ret;
111
112 ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE);
113 if (ret >= 0)
114 return 0;
115
116 /* renameat2() exists since Linux 3.15, btrfs added support for it later.
117 * If it is not implemented, fallback to another method. */
118 if (!IN_SET(errno, EINVAL, ENOSYS))
119 return -errno;
120
121 /* The link()/unlink() fallback does not work on directories. But
122 * renameat() without RENAME_NOREPLACE gives the same semantics on
123 * directories, except when newpath is an *empty* directory. This is
124 * good enough. */
125 ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW);
126 if (ret >= 0 && S_ISDIR(buf.st_mode)) {
127 ret = renameat(olddirfd, oldpath, newdirfd, newpath);
128 return ret >= 0 ? 0 : -errno;
129 }
130
131 /* If it is not a directory, use the link()/unlink() fallback. */
132 ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0);
133 if (ret < 0)
134 return -errno;
135
136 ret = unlinkat(olddirfd, oldpath, 0);
137 if (ret < 0) {
138 /* backup errno before the following unlinkat() alters it */
139 ret = errno;
140 (void) unlinkat(newdirfd, newpath, 0);
141 errno = ret;
142 return -errno;
143 }
144
145 return 0;
146}
147
148int readlinkat_malloc(int fd, const char *p, char **ret) {
149 size_t l = 100;
150 int r;
151
152 assert(p);
153 assert(ret);
154
155 for (;;) {
156 char *c;
157 ssize_t n;
158
159 c = new(char, l);
160 if (!c)
161 return -ENOMEM;
162
163 n = readlinkat(fd, p, c, l-1);
164 if (n < 0) {
165 r = -errno;
166 free(c);
167 return r;
168 }
169
170 if ((size_t) n < l-1) {
171 c[n] = 0;
172 *ret = c;
173 return 0;
174 }
175
176 free(c);
177 l *= 2;
178 }
179}
180
181int readlink_malloc(const char *p, char **ret) {
182 return readlinkat_malloc(AT_FDCWD, p, ret);
183}
184
185int readlink_value(const char *p, char **ret) {
186 _cleanup_free_ char *link = NULL;
187 char *value;
188 int r;
189
190 r = readlink_malloc(p, &link);
191 if (r < 0)
192 return r;
193
194 value = basename(link);
195 if (!value)
196 return -ENOENT;
197
198 value = strdup(value);
199 if (!value)
200 return -ENOMEM;
201
202 *ret = value;
203
204 return 0;
205}
206
207int readlink_and_make_absolute(const char *p, char **r) {
208 _cleanup_free_ char *target = NULL;
209 char *k;
210 int j;
211
212 assert(p);
213 assert(r);
214
215 j = readlink_malloc(p, &target);
216 if (j < 0)
217 return j;
218
219 k = file_in_same_dir(p, target);
220 if (!k)
221 return -ENOMEM;
222
223 *r = k;
224 return 0;
225}
226
e1873695 227int readlink_and_canonicalize(const char *p, const char *root, char **ret) {
f4f15635 228 char *t, *s;
e1873695 229 int r;
f4f15635
LP
230
231 assert(p);
e1873695 232 assert(ret);
f4f15635 233
e1873695
LP
234 r = readlink_and_make_absolute(p, &t);
235 if (r < 0)
236 return r;
f4f15635 237
c4f4fce7 238 r = chase_symlinks(t, root, 0, &s);
e1873695
LP
239 if (r < 0)
240 /* If we can't follow up, then let's return the original string, slightly cleaned up. */
241 *ret = path_kill_slashes(t);
242 else {
243 *ret = s;
f4f15635 244 free(t);
e1873695 245 }
f4f15635
LP
246
247 return 0;
248}
249
0ec0deaa
LP
250int readlink_and_make_absolute_root(const char *root, const char *path, char **ret) {
251 _cleanup_free_ char *target = NULL, *t = NULL;
252 const char *full;
253 int r;
254
255 full = prefix_roota(root, path);
256 r = readlink_malloc(full, &target);
257 if (r < 0)
258 return r;
259
260 t = file_in_same_dir(path, target);
261 if (!t)
262 return -ENOMEM;
263
264 *ret = t;
265 t = NULL;
266
267 return 0;
268}
269
f4f15635
LP
270int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
271 assert(path);
272
273 /* Under the assumption that we are running privileged we
274 * first change the access mode and only then hand out
275 * ownership to avoid a window where access is too open. */
276
277 if (mode != MODE_INVALID)
278 if (chmod(path, mode) < 0)
279 return -errno;
280
281 if (uid != UID_INVALID || gid != GID_INVALID)
282 if (chown(path, uid, gid) < 0)
283 return -errno;
284
285 return 0;
286}
287
f4f15635
LP
288int fchmod_umask(int fd, mode_t m) {
289 mode_t u;
290 int r;
291
292 u = umask(0777);
293 r = fchmod(fd, m & (~u)) < 0 ? -errno : 0;
294 umask(u);
295
296 return r;
297}
298
299int fd_warn_permissions(const char *path, int fd) {
300 struct stat st;
301
302 if (fstat(fd, &st) < 0)
303 return -errno;
304
305 if (st.st_mode & 0111)
306 log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path);
307
308 if (st.st_mode & 0002)
309 log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
310
df0ff127 311 if (getpid_cached() == 1 && (st.st_mode & 0044) != 0044)
f4f15635
LP
312 log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path);
313
314 return 0;
315}
316
317int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
318 _cleanup_close_ int fd;
319 int r;
320
321 assert(path);
322
323 if (parents)
324 mkdir_parents(path, 0755);
325
06eeacb6 326 fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY,
4c701096 327 IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode);
f4f15635
LP
328 if (fd < 0)
329 return -errno;
330
ee735086 331 if (mode != MODE_INVALID) {
f4f15635
LP
332 r = fchmod(fd, mode);
333 if (r < 0)
334 return -errno;
335 }
336
337 if (uid != UID_INVALID || gid != GID_INVALID) {
338 r = fchown(fd, uid, gid);
339 if (r < 0)
340 return -errno;
341 }
342
343 if (stamp != USEC_INFINITY) {
344 struct timespec ts[2];
345
346 timespec_store(&ts[0], stamp);
347 ts[1] = ts[0];
348 r = futimens(fd, ts);
349 } else
350 r = futimens(fd, NULL);
351 if (r < 0)
352 return -errno;
353
354 return 0;
355}
356
357int touch(const char *path) {
ee735086 358 return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID);
f4f15635
LP
359}
360
361int symlink_idempotent(const char *from, const char *to) {
f4f15635
LP
362 int r;
363
364 assert(from);
365 assert(to);
366
367 if (symlink(from, to) < 0) {
77b79723
LP
368 _cleanup_free_ char *p = NULL;
369
f4f15635
LP
370 if (errno != EEXIST)
371 return -errno;
372
373 r = readlink_malloc(to, &p);
77b79723
LP
374 if (r == -EINVAL) /* Not a symlink? In that case return the original error we encountered: -EEXIST */
375 return -EEXIST;
376 if (r < 0) /* Any other error? In that case propagate it as is */
f4f15635
LP
377 return r;
378
77b79723
LP
379 if (!streq(p, from)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
380 return -EEXIST;
f4f15635
LP
381 }
382
383 return 0;
384}
385
386int symlink_atomic(const char *from, const char *to) {
387 _cleanup_free_ char *t = NULL;
388 int r;
389
390 assert(from);
391 assert(to);
392
393 r = tempfn_random(to, NULL, &t);
394 if (r < 0)
395 return r;
396
397 if (symlink(from, t) < 0)
398 return -errno;
399
400 if (rename(t, to) < 0) {
401 unlink_noerrno(t);
402 return -errno;
403 }
404
405 return 0;
406}
407
408int mknod_atomic(const char *path, mode_t mode, dev_t dev) {
409 _cleanup_free_ char *t = NULL;
410 int r;
411
412 assert(path);
413
414 r = tempfn_random(path, NULL, &t);
415 if (r < 0)
416 return r;
417
418 if (mknod(t, mode, dev) < 0)
419 return -errno;
420
421 if (rename(t, path) < 0) {
422 unlink_noerrno(t);
423 return -errno;
424 }
425
426 return 0;
427}
428
429int mkfifo_atomic(const char *path, mode_t mode) {
430 _cleanup_free_ char *t = NULL;
431 int r;
432
433 assert(path);
434
435 r = tempfn_random(path, NULL, &t);
436 if (r < 0)
437 return r;
438
439 if (mkfifo(t, mode) < 0)
440 return -errno;
441
442 if (rename(t, path) < 0) {
443 unlink_noerrno(t);
444 return -errno;
445 }
446
447 return 0;
448}
449
450int get_files_in_directory(const char *path, char ***list) {
451 _cleanup_closedir_ DIR *d = NULL;
8fb3f009 452 struct dirent *de;
f4f15635
LP
453 size_t bufsize = 0, n = 0;
454 _cleanup_strv_free_ char **l = NULL;
455
456 assert(path);
457
458 /* Returns all files in a directory in *list, and the number
459 * of files as return value. If list is NULL returns only the
460 * number. */
461
462 d = opendir(path);
463 if (!d)
464 return -errno;
465
8fb3f009 466 FOREACH_DIRENT_ALL(de, d, return -errno) {
f4f15635
LP
467 dirent_ensure_type(d, de);
468
469 if (!dirent_is_file(de))
470 continue;
471
472 if (list) {
473 /* one extra slot is needed for the terminating NULL */
474 if (!GREEDY_REALLOC(l, bufsize, n + 2))
475 return -ENOMEM;
476
477 l[n] = strdup(de->d_name);
478 if (!l[n])
479 return -ENOMEM;
480
481 l[++n] = NULL;
482 } else
483 n++;
484 }
485
486 if (list) {
487 *list = l;
488 l = NULL; /* avoid freeing */
489 }
490
491 return n;
492}
430fbf8e 493
992e8f22
LP
494static int getenv_tmp_dir(const char **ret_path) {
495 const char *n;
496 int r, ret = 0;
34a8f081 497
992e8f22 498 assert(ret_path);
34a8f081 499
992e8f22
LP
500 /* We use the same order of environment variables python uses in tempfile.gettempdir():
501 * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */
502 FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP") {
503 const char *e;
504
505 e = secure_getenv(n);
506 if (!e)
507 continue;
508 if (!path_is_absolute(e)) {
509 r = -ENOTDIR;
510 goto next;
511 }
512 if (!path_is_safe(e)) {
513 r = -EPERM;
514 goto next;
515 }
516
517 r = is_dir(e, true);
518 if (r < 0)
519 goto next;
520 if (r == 0) {
521 r = -ENOTDIR;
522 goto next;
523 }
524
525 *ret_path = e;
526 return 1;
527
528 next:
529 /* Remember first error, to make this more debuggable */
530 if (ret >= 0)
531 ret = r;
34a8f081
OW
532 }
533
992e8f22
LP
534 if (ret < 0)
535 return ret;
34a8f081 536
992e8f22
LP
537 *ret_path = NULL;
538 return ret;
539}
34a8f081 540
992e8f22
LP
541static int tmp_dir_internal(const char *def, const char **ret) {
542 const char *e;
543 int r, k;
544
545 assert(def);
546 assert(ret);
547
548 r = getenv_tmp_dir(&e);
549 if (r > 0) {
550 *ret = e;
551 return 0;
552 }
553
554 k = is_dir(def, true);
555 if (k == 0)
556 k = -ENOTDIR;
557 if (k < 0)
558 return r < 0 ? r : k;
559
560 *ret = def;
34a8f081
OW
561 return 0;
562}
563
992e8f22
LP
564int var_tmp_dir(const char **ret) {
565
566 /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus
567 * even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is
568 * returned preferably however. Note that both this function and tmp_dir() below are affected by $TMPDIR,
569 * making it a variable that overrides all temporary file storage locations. */
570
571 return tmp_dir_internal("/var/tmp", ret);
572}
573
574int tmp_dir(const char **ret) {
575
576 /* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually
577 * backed by an in-memory file system: /tmp. */
578
579 return tmp_dir_internal("/tmp", ret);
580}
581
430fbf8e
LP
582int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
583 char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
584 int r;
585
586 /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */
587 xsprintf(path, "/proc/self/fd/%i", what);
588
589 r = inotify_add_watch(fd, path, mask);
590 if (r < 0)
591 return -errno;
592
593 return r;
594}
d944dc95 595
c4f4fce7 596int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) {
d944dc95
LP
597 _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
598 _cleanup_close_ int fd = -1;
599 unsigned max_follow = 32; /* how many symlinks to follow before giving up and returning ELOOP */
a9fb0867 600 bool exists = true;
d944dc95
LP
601 char *todo;
602 int r;
603
604 assert(path);
605
606 /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following
607 * symlinks relative to a root directory, instead of the root of the host.
608 *
fc4b68e5 609 * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following
c4f4fce7
LP
610 * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is
611 * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first
612 * prefixed accordingly.
d944dc95
LP
613 *
614 * Algorithmically this operates on two path buffers: "done" are the components of the path we already
615 * processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to
616 * process. On each iteration, we move one component from "todo" to "done", processing it's special meaning
617 * each time. The "todo" path always starts with at least one slash, the "done" path always ends in no
618 * slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races
fc4b68e5
LP
619 * at a minimum.
620 *
621 * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got
622 * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this
623 * function what to do when encountering a symlink with an absolute path as directory: prefix it by the
624 * specified path.
625 *
626 * Note: there's also chase_symlinks_prefix() (see below), which as first step prefixes the passed path by the
627 * passed root. */
d944dc95 628
c4f4fce7
LP
629 if (original_root) {
630 r = path_make_absolute_cwd(original_root, &root);
d944dc95
LP
631 if (r < 0)
632 return r;
c4f4fce7
LP
633
634 if (flags & CHASE_PREFIX_ROOT)
635 path = prefix_roota(root, path);
d944dc95
LP
636 }
637
c4f4fce7
LP
638 r = path_make_absolute_cwd(path, &buffer);
639 if (r < 0)
640 return r;
641
d944dc95
LP
642 fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
643 if (fd < 0)
644 return -errno;
645
646 todo = buffer;
647 for (;;) {
648 _cleanup_free_ char *first = NULL;
649 _cleanup_close_ int child = -1;
650 struct stat st;
651 size_t n, m;
652
653 /* Determine length of first component in the path */
654 n = strspn(todo, "/"); /* The slashes */
655 m = n + strcspn(todo + n, "/"); /* The entire length of the component */
656
657 /* Extract the first component. */
658 first = strndup(todo, m);
659 if (!first)
660 return -ENOMEM;
661
662 todo += m;
663
664 /* Just a single slash? Then we reached the end. */
665 if (isempty(first) || path_equal(first, "/"))
666 break;
667
668 /* Just a dot? Then let's eat this up. */
669 if (path_equal(first, "/."))
670 continue;
671
672 /* Two dots? Then chop off the last bit of what we already found out. */
673 if (path_equal(first, "/..")) {
674 _cleanup_free_ char *parent = NULL;
675 int fd_parent = -1;
676
a4eaf3cf
LP
677 /* If we already are at the top, then going up will not change anything. This is in-line with
678 * how the kernel handles this. */
d944dc95 679 if (isempty(done) || path_equal(done, "/"))
a4eaf3cf 680 continue;
d944dc95
LP
681
682 parent = dirname_malloc(done);
683 if (!parent)
684 return -ENOMEM;
685
a4eaf3cf 686 /* Don't allow this to leave the root dir. */
d944dc95
LP
687 if (root &&
688 path_startswith(done, root) &&
689 !path_startswith(parent, root))
a4eaf3cf 690 continue;
d944dc95 691
3b319885 692 free_and_replace(done, parent);
d944dc95
LP
693
694 fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH);
695 if (fd_parent < 0)
696 return -errno;
697
698 safe_close(fd);
699 fd = fd_parent;
700
701 continue;
702 }
703
704 /* Otherwise let's see what this is. */
705 child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH);
a9fb0867
LP
706 if (child < 0) {
707
708 if (errno == ENOENT &&
cb638b5e 709 (flags & CHASE_NONEXISTENT) &&
a9fb0867
LP
710 (isempty(todo) || path_is_safe(todo))) {
711
cb638b5e 712 /* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return
a9fb0867
LP
713 * what we got so far. But don't allow this if the remaining path contains "../ or "./"
714 * or something else weird. */
715
716 if (!strextend(&done, first, todo, NULL))
717 return -ENOMEM;
718
719 exists = false;
720 break;
721 }
722
d944dc95 723 return -errno;
a9fb0867 724 }
d944dc95
LP
725
726 if (fstat(child, &st) < 0)
727 return -errno;
655f2da0
N
728 if ((flags & CHASE_NO_AUTOFS) &&
729 fd_check_fstype(child, AUTOFS_SUPER_MAGIC) > 0)
730 return -EREMOTE;
d944dc95
LP
731
732 if (S_ISLNK(st.st_mode)) {
877777d7
CCW
733 char *joined;
734
d944dc95
LP
735 _cleanup_free_ char *destination = NULL;
736
737 /* This is a symlink, in this case read the destination. But let's make sure we don't follow
738 * symlinks without bounds. */
739 if (--max_follow <= 0)
740 return -ELOOP;
741
742 r = readlinkat_malloc(fd, first + n, &destination);
743 if (r < 0)
744 return r;
745 if (isempty(destination))
746 return -EINVAL;
747
748 if (path_is_absolute(destination)) {
749
750 /* An absolute destination. Start the loop from the beginning, but use the root
751 * directory as base. */
752
753 safe_close(fd);
754 fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
755 if (fd < 0)
756 return -errno;
757
d944dc95
LP
758 free(done);
759
760 /* Note that we do not revalidate the root, we take it as is. */
761 if (isempty(root))
762 done = NULL;
763 else {
764 done = strdup(root);
765 if (!done)
766 return -ENOMEM;
767 }
768
8c4a8ea2
LP
769 /* Prefix what's left to do with what we just read, and start the loop again, but
770 * remain in the current directory. */
771 joined = strjoin(destination, todo);
772 } else
773 joined = strjoin("/", destination, todo);
877777d7
CCW
774 if (!joined)
775 return -ENOMEM;
d944dc95 776
877777d7
CCW
777 free(buffer);
778 todo = buffer = joined;
d944dc95
LP
779
780 continue;
781 }
782
783 /* If this is not a symlink, then let's just add the name we read to what we already verified. */
784 if (!done) {
785 done = first;
786 first = NULL;
787 } else {
788 if (!strextend(&done, first, NULL))
789 return -ENOMEM;
790 }
791
792 /* And iterate again, but go one directory further down. */
793 safe_close(fd);
794 fd = child;
795 child = -1;
796 }
797
798 if (!done) {
799 /* Special case, turn the empty string into "/", to indicate the root directory. */
800 done = strdup("/");
801 if (!done)
802 return -ENOMEM;
803 }
804
245f1d24
LP
805 if (ret) {
806 *ret = done;
807 done = NULL;
808 }
d944dc95 809
a9fb0867 810 return exists;
d944dc95 811}
57a4359e
LP
812
813int access_fd(int fd, int mode) {
814 char p[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
815 int r;
816
817 /* Like access() but operates on an already open fd */
818
819 xsprintf(p, "/proc/self/fd/%i", fd);
820
821 r = access(p, mode);
822 if (r < 0)
823 r = -errno;
824
825 return r;
826}