]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/fs-util.c
16f97893c951cd1f5024e56033a30111348453c5
[thirdparty/systemd.git] / src / basic / fs-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
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
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>
27 #include <linux/magic.h>
28 #include <time.h>
29 #include <unistd.h>
30
31 #include "alloc-util.h"
32 #include "dirent-util.h"
33 #include "fd-util.h"
34 #include "fileio.h"
35 #include "fs-util.h"
36 #include "log.h"
37 #include "macro.h"
38 #include "missing.h"
39 #include "mkdir.h"
40 #include "parse-util.h"
41 #include "path-util.h"
42 #include "stat-util.h"
43 #include "stdio-util.h"
44 #include "string-util.h"
45 #include "strv.h"
46 #include "time-util.h"
47 #include "user-util.h"
48 #include "util.h"
49
50 int 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
61 int 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
108 int 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
148 int 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
181 int readlink_malloc(const char *p, char **ret) {
182 return readlinkat_malloc(AT_FDCWD, p, ret);
183 }
184
185 int 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
207 int 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
227 int readlink_and_canonicalize(const char *p, const char *root, char **ret) {
228 char *t, *s;
229 int r;
230
231 assert(p);
232 assert(ret);
233
234 r = readlink_and_make_absolute(p, &t);
235 if (r < 0)
236 return r;
237
238 r = chase_symlinks(t, root, 0, &s);
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;
244 free(t);
245 }
246
247 return 0;
248 }
249
250 int 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
270 int 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
288 int 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
299 int 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
311 if (getpid_cached() == 1 && (st.st_mode & 0044) != 0044)
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
317 int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
318 char fdpath[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
319 _cleanup_close_ int fd = -1;
320 int r, ret = 0;
321
322 assert(path);
323
324 /* Note that touch_file() does not follow symlinks: if invoked on an existing symlink, then it is the symlink
325 * itself which is updated, not its target
326 *
327 * Returns the first error we encounter, but tries to apply as much as possible. */
328
329 if (parents)
330 (void) mkdir_parents(path, 0755);
331
332 /* Initially, we try to open the node with O_PATH, so that we get a reference to the node. This is useful in
333 * case the path refers to an existing device or socket node, as we can open it successfully in all cases, and
334 * won't trigger any driver magic or so. */
335 fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
336 if (fd < 0) {
337 if (errno != ENOENT)
338 return -errno;
339
340 /* if the node doesn't exist yet, we create it, but with O_EXCL, so that we only create a regular file
341 * here, and nothing else */
342 fd = open(path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode);
343 if (fd < 0)
344 return -errno;
345 }
346
347 /* Let's make a path from the fd, and operate on that. With this logic, we can adjust the access mode,
348 * ownership and time of the file node in all cases, even if the fd refers to an O_PATH object — which is
349 * something fchown(), fchmod(), futimensat() don't allow. */
350 xsprintf(fdpath, "/proc/self/fd/%i", fd);
351
352 if (mode != MODE_INVALID)
353 if (chmod(fdpath, mode) < 0)
354 ret = -errno;
355
356 if (uid_is_valid(uid) || gid_is_valid(gid))
357 if (chown(fdpath, uid, gid) < 0 && ret >= 0)
358 ret = -errno;
359
360 if (stamp != USEC_INFINITY) {
361 struct timespec ts[2];
362
363 timespec_store(&ts[0], stamp);
364 ts[1] = ts[0];
365 r = utimensat(AT_FDCWD, fdpath, ts, 0);
366 } else
367 r = utimensat(AT_FDCWD, fdpath, NULL, 0);
368 if (r < 0 && ret >= 0)
369 return -errno;
370
371 return ret;
372 }
373
374 int touch(const char *path) {
375 return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID);
376 }
377
378 int symlink_idempotent(const char *from, const char *to) {
379 int r;
380
381 assert(from);
382 assert(to);
383
384 if (symlink(from, to) < 0) {
385 _cleanup_free_ char *p = NULL;
386
387 if (errno != EEXIST)
388 return -errno;
389
390 r = readlink_malloc(to, &p);
391 if (r == -EINVAL) /* Not a symlink? In that case return the original error we encountered: -EEXIST */
392 return -EEXIST;
393 if (r < 0) /* Any other error? In that case propagate it as is */
394 return r;
395
396 if (!streq(p, from)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
397 return -EEXIST;
398 }
399
400 return 0;
401 }
402
403 int symlink_atomic(const char *from, const char *to) {
404 _cleanup_free_ char *t = NULL;
405 int r;
406
407 assert(from);
408 assert(to);
409
410 r = tempfn_random(to, NULL, &t);
411 if (r < 0)
412 return r;
413
414 if (symlink(from, t) < 0)
415 return -errno;
416
417 if (rename(t, to) < 0) {
418 unlink_noerrno(t);
419 return -errno;
420 }
421
422 return 0;
423 }
424
425 int mknod_atomic(const char *path, mode_t mode, dev_t dev) {
426 _cleanup_free_ char *t = NULL;
427 int r;
428
429 assert(path);
430
431 r = tempfn_random(path, NULL, &t);
432 if (r < 0)
433 return r;
434
435 if (mknod(t, mode, dev) < 0)
436 return -errno;
437
438 if (rename(t, path) < 0) {
439 unlink_noerrno(t);
440 return -errno;
441 }
442
443 return 0;
444 }
445
446 int mkfifo_atomic(const char *path, mode_t mode) {
447 _cleanup_free_ char *t = NULL;
448 int r;
449
450 assert(path);
451
452 r = tempfn_random(path, NULL, &t);
453 if (r < 0)
454 return r;
455
456 if (mkfifo(t, mode) < 0)
457 return -errno;
458
459 if (rename(t, path) < 0) {
460 unlink_noerrno(t);
461 return -errno;
462 }
463
464 return 0;
465 }
466
467 int get_files_in_directory(const char *path, char ***list) {
468 _cleanup_closedir_ DIR *d = NULL;
469 struct dirent *de;
470 size_t bufsize = 0, n = 0;
471 _cleanup_strv_free_ char **l = NULL;
472
473 assert(path);
474
475 /* Returns all files in a directory in *list, and the number
476 * of files as return value. If list is NULL returns only the
477 * number. */
478
479 d = opendir(path);
480 if (!d)
481 return -errno;
482
483 FOREACH_DIRENT_ALL(de, d, return -errno) {
484 dirent_ensure_type(d, de);
485
486 if (!dirent_is_file(de))
487 continue;
488
489 if (list) {
490 /* one extra slot is needed for the terminating NULL */
491 if (!GREEDY_REALLOC(l, bufsize, n + 2))
492 return -ENOMEM;
493
494 l[n] = strdup(de->d_name);
495 if (!l[n])
496 return -ENOMEM;
497
498 l[++n] = NULL;
499 } else
500 n++;
501 }
502
503 if (list) {
504 *list = l;
505 l = NULL; /* avoid freeing */
506 }
507
508 return n;
509 }
510
511 static int getenv_tmp_dir(const char **ret_path) {
512 const char *n;
513 int r, ret = 0;
514
515 assert(ret_path);
516
517 /* We use the same order of environment variables python uses in tempfile.gettempdir():
518 * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */
519 FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP") {
520 const char *e;
521
522 e = secure_getenv(n);
523 if (!e)
524 continue;
525 if (!path_is_absolute(e)) {
526 r = -ENOTDIR;
527 goto next;
528 }
529 if (!path_is_normalized(e)) {
530 r = -EPERM;
531 goto next;
532 }
533
534 r = is_dir(e, true);
535 if (r < 0)
536 goto next;
537 if (r == 0) {
538 r = -ENOTDIR;
539 goto next;
540 }
541
542 *ret_path = e;
543 return 1;
544
545 next:
546 /* Remember first error, to make this more debuggable */
547 if (ret >= 0)
548 ret = r;
549 }
550
551 if (ret < 0)
552 return ret;
553
554 *ret_path = NULL;
555 return ret;
556 }
557
558 static int tmp_dir_internal(const char *def, const char **ret) {
559 const char *e;
560 int r, k;
561
562 assert(def);
563 assert(ret);
564
565 r = getenv_tmp_dir(&e);
566 if (r > 0) {
567 *ret = e;
568 return 0;
569 }
570
571 k = is_dir(def, true);
572 if (k == 0)
573 k = -ENOTDIR;
574 if (k < 0)
575 return r < 0 ? r : k;
576
577 *ret = def;
578 return 0;
579 }
580
581 int var_tmp_dir(const char **ret) {
582
583 /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus
584 * even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is
585 * returned preferably however. Note that both this function and tmp_dir() below are affected by $TMPDIR,
586 * making it a variable that overrides all temporary file storage locations. */
587
588 return tmp_dir_internal("/var/tmp", ret);
589 }
590
591 int tmp_dir(const char **ret) {
592
593 /* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually
594 * backed by an in-memory file system: /tmp. */
595
596 return tmp_dir_internal("/tmp", ret);
597 }
598
599 int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
600 char path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
601 int r;
602
603 /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */
604 xsprintf(path, "/proc/self/fd/%i", what);
605
606 r = inotify_add_watch(fd, path, mask);
607 if (r < 0)
608 return -errno;
609
610 return r;
611 }
612
613 int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) {
614 _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
615 _cleanup_close_ int fd = -1;
616 unsigned max_follow = 32; /* how many symlinks to follow before giving up and returning ELOOP */
617 bool exists = true;
618 char *todo;
619 int r;
620
621 assert(path);
622
623 /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following
624 * symlinks relative to a root directory, instead of the root of the host.
625 *
626 * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following
627 * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is
628 * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first
629 * prefixed accordingly.
630 *
631 * Algorithmically this operates on two path buffers: "done" are the components of the path we already
632 * processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to
633 * process. On each iteration, we move one component from "todo" to "done", processing it's special meaning
634 * each time. The "todo" path always starts with at least one slash, the "done" path always ends in no
635 * slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races
636 * at a minimum.
637 *
638 * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got
639 * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this
640 * function what to do when encountering a symlink with an absolute path as directory: prefix it by the
641 * specified path. */
642
643 if (original_root) {
644 r = path_make_absolute_cwd(original_root, &root);
645 if (r < 0)
646 return r;
647
648 if (flags & CHASE_PREFIX_ROOT)
649 path = prefix_roota(root, path);
650 }
651
652 r = path_make_absolute_cwd(path, &buffer);
653 if (r < 0)
654 return r;
655
656 fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
657 if (fd < 0)
658 return -errno;
659
660 todo = buffer;
661 for (;;) {
662 _cleanup_free_ char *first = NULL;
663 _cleanup_close_ int child = -1;
664 struct stat st;
665 size_t n, m;
666
667 /* Determine length of first component in the path */
668 n = strspn(todo, "/"); /* The slashes */
669 m = n + strcspn(todo + n, "/"); /* The entire length of the component */
670
671 /* Extract the first component. */
672 first = strndup(todo, m);
673 if (!first)
674 return -ENOMEM;
675
676 todo += m;
677
678 /* Empty? Then we reached the end. */
679 if (isempty(first))
680 break;
681
682 /* Just a single slash? Then we reached the end. */
683 if (path_equal(first, "/")) {
684 /* Preserve the trailing slash */
685 if (!strextend(&done, "/", NULL))
686 return -ENOMEM;
687
688 break;
689 }
690
691 /* Just a dot? Then let's eat this up. */
692 if (path_equal(first, "/."))
693 continue;
694
695 /* Two dots? Then chop off the last bit of what we already found out. */
696 if (path_equal(first, "/..")) {
697 _cleanup_free_ char *parent = NULL;
698 int fd_parent = -1;
699
700 /* If we already are at the top, then going up will not change anything. This is in-line with
701 * how the kernel handles this. */
702 if (isempty(done) || path_equal(done, "/"))
703 continue;
704
705 parent = dirname_malloc(done);
706 if (!parent)
707 return -ENOMEM;
708
709 /* Don't allow this to leave the root dir. */
710 if (root &&
711 path_startswith(done, root) &&
712 !path_startswith(parent, root))
713 continue;
714
715 free_and_replace(done, parent);
716
717 fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH);
718 if (fd_parent < 0)
719 return -errno;
720
721 safe_close(fd);
722 fd = fd_parent;
723
724 continue;
725 }
726
727 /* Otherwise let's see what this is. */
728 child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH);
729 if (child < 0) {
730
731 if (errno == ENOENT &&
732 (flags & CHASE_NONEXISTENT) &&
733 (isempty(todo) || path_is_normalized(todo))) {
734
735 /* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return
736 * what we got so far. But don't allow this if the remaining path contains "../ or "./"
737 * or something else weird. */
738
739 /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
740 if (streq_ptr(done, "/"))
741 *done = '\0';
742
743 if (!strextend(&done, first, todo, NULL))
744 return -ENOMEM;
745
746 exists = false;
747 break;
748 }
749
750 return -errno;
751 }
752
753 if (fstat(child, &st) < 0)
754 return -errno;
755 if ((flags & CHASE_NO_AUTOFS) &&
756 fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
757 return -EREMOTE;
758
759 if (S_ISLNK(st.st_mode)) {
760 char *joined;
761
762 _cleanup_free_ char *destination = NULL;
763
764 /* This is a symlink, in this case read the destination. But let's make sure we don't follow
765 * symlinks without bounds. */
766 if (--max_follow <= 0)
767 return -ELOOP;
768
769 r = readlinkat_malloc(fd, first + n, &destination);
770 if (r < 0)
771 return r;
772 if (isempty(destination))
773 return -EINVAL;
774
775 if (path_is_absolute(destination)) {
776
777 /* An absolute destination. Start the loop from the beginning, but use the root
778 * directory as base. */
779
780 safe_close(fd);
781 fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
782 if (fd < 0)
783 return -errno;
784
785 free(done);
786
787 /* Note that we do not revalidate the root, we take it as is. */
788 if (isempty(root))
789 done = NULL;
790 else {
791 done = strdup(root);
792 if (!done)
793 return -ENOMEM;
794 }
795
796 /* Prefix what's left to do with what we just read, and start the loop again, but
797 * remain in the current directory. */
798 joined = strjoin(destination, todo);
799 } else
800 joined = strjoin("/", destination, todo);
801 if (!joined)
802 return -ENOMEM;
803
804 free(buffer);
805 todo = buffer = joined;
806
807 continue;
808 }
809
810 /* If this is not a symlink, then let's just add the name we read to what we already verified. */
811 if (!done) {
812 done = first;
813 first = NULL;
814 } else {
815 /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
816 if (streq(done, "/"))
817 *done = '\0';
818
819 if (!strextend(&done, first, NULL))
820 return -ENOMEM;
821 }
822
823 /* And iterate again, but go one directory further down. */
824 safe_close(fd);
825 fd = child;
826 child = -1;
827 }
828
829 if (!done) {
830 /* Special case, turn the empty string into "/", to indicate the root directory. */
831 done = strdup("/");
832 if (!done)
833 return -ENOMEM;
834 }
835
836 if (ret) {
837 *ret = done;
838 done = NULL;
839 }
840
841 return exists;
842 }
843
844 int access_fd(int fd, int mode) {
845 char p[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
846 int r;
847
848 /* Like access() but operates on an already open fd */
849
850 xsprintf(p, "/proc/self/fd/%i", fd);
851
852 r = access(p, mode);
853 if (r < 0)
854 r = -errno;
855
856 return r;
857 }