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