chiark / gitweb /
path-util: Change path_is_mount_point() symlink arg from bool to flags
[elogind.git] / src / shared / path-util.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010-2012 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <string.h>
23 #include <unistd.h>
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <fcntl.h>
28 #include <sys/statvfs.h>
29
30 #include "macro.h"
31 #include "util.h"
32 #include "log.h"
33 #include "strv.h"
34 #include "path-util.h"
35 #include "missing.h"
36 #include "fileio.h"
37
38 bool path_is_absolute(const char *p) {
39         return p[0] == '/';
40 }
41
42 bool is_path(const char *p) {
43         return !!strchr(p, '/');
44 }
45
46 int path_get_parent(const char *path, char **_r) {
47         const char *e, *a = NULL, *b = NULL, *p;
48         char *r;
49         bool slash = false;
50
51         assert(path);
52         assert(_r);
53
54         if (!*path)
55                 return -EINVAL;
56
57         for (e = path; *e; e++) {
58
59                 if (!slash && *e == '/') {
60                         a = b;
61                         b = e;
62                         slash = true;
63                 } else if (slash && *e != '/')
64                         slash = false;
65         }
66
67         if (*(e-1) == '/')
68                 p = a;
69         else
70                 p = b;
71
72         if (!p)
73                 return -EINVAL;
74
75         if (p == path)
76                 r = strdup("/");
77         else
78                 r = strndup(path, p-path);
79
80         if (!r)
81                 return -ENOMEM;
82
83         *_r = r;
84         return 0;
85 }
86
87 char **path_split_and_make_absolute(const char *p) {
88         char **l;
89         assert(p);
90
91         l = strv_split(p, ":");
92         if (!l)
93                 return NULL;
94
95         if (!path_strv_make_absolute_cwd(l)) {
96                 strv_free(l);
97                 return NULL;
98         }
99
100         return l;
101 }
102
103 char *path_make_absolute(const char *p, const char *prefix) {
104         assert(p);
105
106         /* Makes every item in the list an absolute path by prepending
107          * the prefix, if specified and necessary */
108
109         if (path_is_absolute(p) || !prefix)
110                 return strdup(p);
111
112         return strjoin(prefix, "/", p, NULL);
113 }
114
115 char *path_make_absolute_cwd(const char *p) {
116         _cleanup_free_ char *cwd = NULL;
117
118         assert(p);
119
120         /* Similar to path_make_absolute(), but prefixes with the
121          * current working directory. */
122
123         if (path_is_absolute(p))
124                 return strdup(p);
125
126         cwd = get_current_dir_name();
127         if (!cwd)
128                 return NULL;
129
130         return strjoin(cwd, "/", p, NULL);
131 }
132
133 int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
134         char *r, *p;
135         unsigned n_parents;
136
137         assert(from_dir);
138         assert(to_path);
139         assert(_r);
140
141         /* Strips the common part, and adds ".." elements as necessary. */
142
143         if (!path_is_absolute(from_dir))
144                 return -EINVAL;
145
146         if (!path_is_absolute(to_path))
147                 return -EINVAL;
148
149         /* Skip the common part. */
150         for (;;) {
151                 size_t a;
152                 size_t b;
153
154                 from_dir += strspn(from_dir, "/");
155                 to_path += strspn(to_path, "/");
156
157                 if (!*from_dir) {
158                         if (!*to_path)
159                                 /* from_dir equals to_path. */
160                                 r = strdup(".");
161                         else
162                                 /* from_dir is a parent directory of to_path. */
163                                 r = strdup(to_path);
164
165                         if (!r)
166                                 return -ENOMEM;
167
168                         path_kill_slashes(r);
169
170                         *_r = r;
171                         return 0;
172                 }
173
174                 if (!*to_path)
175                         break;
176
177                 a = strcspn(from_dir, "/");
178                 b = strcspn(to_path, "/");
179
180                 if (a != b)
181                         break;
182
183                 if (memcmp(from_dir, to_path, a) != 0)
184                         break;
185
186                 from_dir += a;
187                 to_path += b;
188         }
189
190         /* If we're here, then "from_dir" has one or more elements that need to
191          * be replaced with "..". */
192
193         /* Count the number of necessary ".." elements. */
194         for (n_parents = 0;;) {
195                 from_dir += strspn(from_dir, "/");
196
197                 if (!*from_dir)
198                         break;
199
200                 from_dir += strcspn(from_dir, "/");
201                 n_parents++;
202         }
203
204         r = malloc(n_parents * 3 + strlen(to_path) + 1);
205         if (!r)
206                 return -ENOMEM;
207
208         for (p = r; n_parents > 0; n_parents--, p += 3)
209                 memcpy(p, "../", 3);
210
211         strcpy(p, to_path);
212         path_kill_slashes(r);
213
214         *_r = r;
215         return 0;
216 }
217
218 char **path_strv_make_absolute_cwd(char **l) {
219         char **s;
220
221         /* Goes through every item in the string list and makes it
222          * absolute. This works in place and won't rollback any
223          * changes on failure. */
224
225         STRV_FOREACH(s, l) {
226                 char *t;
227
228                 t = path_make_absolute_cwd(*s);
229                 if (!t)
230                         return NULL;
231
232                 free(*s);
233                 *s = t;
234         }
235
236         return l;
237 }
238
239 char **path_strv_resolve(char **l, const char *prefix) {
240         char **s;
241         unsigned k = 0;
242         bool enomem = false;
243
244         if (strv_isempty(l))
245                 return l;
246
247         /* Goes through every item in the string list and canonicalize
248          * the path. This works in place and won't rollback any
249          * changes on failure. */
250
251         STRV_FOREACH(s, l) {
252                 char *t, *u;
253                 _cleanup_free_ char *orig = NULL;
254
255                 if (!path_is_absolute(*s)) {
256                         free(*s);
257                         continue;
258                 }
259
260                 if (prefix) {
261                         orig = *s;
262                         t = strappend(prefix, orig);
263                         if (!t) {
264                                 enomem = true;
265                                 continue;
266                         }
267                 } else
268                         t = *s;
269
270                 errno = 0;
271                 u = canonicalize_file_name(t);
272                 if (!u) {
273                         if (errno == ENOENT) {
274                                 if (prefix) {
275                                         u = orig;
276                                         orig = NULL;
277                                         free(t);
278                                 } else
279                                         u = t;
280                         } else {
281                                 free(t);
282                                 if (errno == ENOMEM || errno == 0)
283                                         enomem = true;
284
285                                 continue;
286                         }
287                 } else if (prefix) {
288                         char *x;
289
290                         free(t);
291                         x = path_startswith(u, prefix);
292                         if (x) {
293                                 /* restore the slash if it was lost */
294                                 if (!startswith(x, "/"))
295                                         *(--x) = '/';
296
297                                 t = strdup(x);
298                                 free(u);
299                                 if (!t) {
300                                         enomem = true;
301                                         continue;
302                                 }
303                                 u = t;
304                         } else {
305                                 /* canonicalized path goes outside of
306                                  * prefix, keep the original path instead */
307                                 free(u);
308                                 u = orig;
309                                 orig = NULL;
310                         }
311                 } else
312                         free(t);
313
314                 l[k++] = u;
315         }
316
317         l[k] = NULL;
318
319         if (enomem)
320                 return NULL;
321
322         return l;
323 }
324
325 char **path_strv_resolve_uniq(char **l, const char *prefix) {
326
327         if (strv_isempty(l))
328                 return l;
329
330         if (!path_strv_resolve(l, prefix))
331                 return NULL;
332
333         return strv_uniq(l);
334 }
335
336 char *path_kill_slashes(char *path) {
337         char *f, *t;
338         bool slash = false;
339
340         /* Removes redundant inner and trailing slashes. Modifies the
341          * passed string in-place.
342          *
343          * ///foo///bar/ becomes /foo/bar
344          */
345
346         for (f = path, t = path; *f; f++) {
347
348                 if (*f == '/') {
349                         slash = true;
350                         continue;
351                 }
352
353                 if (slash) {
354                         slash = false;
355                         *(t++) = '/';
356                 }
357
358                 *(t++) = *f;
359         }
360
361         /* Special rule, if we are talking of the root directory, a
362         trailing slash is good */
363
364         if (t == path && slash)
365                 *(t++) = '/';
366
367         *t = 0;
368         return path;
369 }
370
371 char* path_startswith(const char *path, const char *prefix) {
372         assert(path);
373         assert(prefix);
374
375         if ((path[0] == '/') != (prefix[0] == '/'))
376                 return NULL;
377
378         for (;;) {
379                 size_t a, b;
380
381                 path += strspn(path, "/");
382                 prefix += strspn(prefix, "/");
383
384                 if (*prefix == 0)
385                         return (char*) path;
386
387                 if (*path == 0)
388                         return NULL;
389
390                 a = strcspn(path, "/");
391                 b = strcspn(prefix, "/");
392
393                 if (a != b)
394                         return NULL;
395
396                 if (memcmp(path, prefix, a) != 0)
397                         return NULL;
398
399                 path += a;
400                 prefix += b;
401         }
402 }
403
404 int path_compare(const char *a, const char *b) {
405         int d;
406
407         assert(a);
408         assert(b);
409
410         /* A relative path and an abolute path must not compare as equal.
411          * Which one is sorted before the other does not really matter.
412          * Here a relative path is ordered before an absolute path. */
413         d = (a[0] == '/') - (b[0] == '/');
414         if (d)
415                 return d;
416
417         for (;;) {
418                 size_t j, k;
419
420                 a += strspn(a, "/");
421                 b += strspn(b, "/");
422
423                 if (*a == 0 && *b == 0)
424                         return 0;
425
426                 /* Order prefixes first: "/foo" before "/foo/bar" */
427                 if (*a == 0)
428                         return -1;
429                 if (*b == 0)
430                         return 1;
431
432                 j = strcspn(a, "/");
433                 k = strcspn(b, "/");
434
435                 /* Alphabetical sort: "/foo/aaa" before "/foo/b" */
436                 d = memcmp(a, b, MIN(j, k));
437                 if (d)
438                         return (d > 0) - (d < 0); /* sign of d */
439
440                 /* Sort "/foo/a" before "/foo/aaa" */
441                 d = (j > k) - (j < k);  /* sign of (j - k) */
442                 if (d)
443                         return d;
444
445                 a += j;
446                 b += k;
447         }
448 }
449
450 bool path_equal(const char *a, const char *b) {
451         return path_compare(a, b) == 0;
452 }
453
454 bool path_equal_or_files_same(const char *a, const char *b) {
455         return path_equal(a, b) || files_same(a, b) > 0;
456 }
457
458 char* path_join(const char *root, const char *path, const char *rest) {
459         assert(path);
460
461         if (!isempty(root))
462                 return strjoin(root, endswith(root, "/") ? "" : "/",
463                                path[0] == '/' ? path+1 : path,
464                                rest ? (endswith(path, "/") ? "" : "/") : NULL,
465                                rest && rest[0] == '/' ? rest+1 : rest,
466                                NULL);
467         else
468                 return strjoin(path,
469                                rest ? (endswith(path, "/") ? "" : "/") : NULL,
470                                rest && rest[0] == '/' ? rest+1 : rest,
471                                NULL);
472 }
473
474 static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) {
475         char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
476         _cleanup_free_ char *fdinfo = NULL;
477         _cleanup_close_ int subfd = -1;
478         char *p;
479         int r;
480
481         if ((flags & AT_EMPTY_PATH) && isempty(filename))
482                 xsprintf(path, "/proc/self/fdinfo/%i", fd);
483         else {
484                 subfd = openat(fd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH);
485                 if (subfd < 0)
486                         return -errno;
487
488                 xsprintf(path, "/proc/self/fdinfo/%i", subfd);
489         }
490
491         r = read_full_file(path, &fdinfo, NULL);
492         if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */
493                 return -EOPNOTSUPP;
494         if (r < 0)
495                 return -errno;
496
497         p = startswith(fdinfo, "mnt_id:");
498         if (!p) {
499                 p = strstr(fdinfo, "\nmnt_id:");
500                 if (!p) /* The mnt_id field is a relatively new addition */
501                         return -EOPNOTSUPP;
502
503                 p += 8;
504         }
505
506         p += strspn(p, WHITESPACE);
507         p[strcspn(p, WHITESPACE)] = 0;
508
509         return safe_atoi(p, mnt_id);
510 }
511
512 int fd_is_mount_point(int fd, const char *filename, int flags) {
513         union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT;
514         int mount_id = -1, mount_id_parent = -1;
515         bool nosupp = false, check_st_dev = true;
516         struct stat a, b;
517         int r;
518
519         assert(fd >= 0);
520         assert(filename);
521
522         /* First we will try the name_to_handle_at() syscall, which
523          * tells us the mount id and an opaque file "handle". It is
524          * not supported everywhere though (kernel compile-time
525          * option, not all file systems are hooked up). If it works
526          * the mount id is usually good enough to tell us whether
527          * something is a mount point.
528          *
529          * If that didn't work we will try to read the mount id from
530          * /proc/self/fdinfo/<fd>. This is almost as good as
531          * name_to_handle_at(), however, does not return the the
532          * opaque file handle. The opaque file handle is pretty useful
533          * to detect the root directory, which we should always
534          * consider a mount point. Hence we use this only as
535          * fallback. Exporting the mnt_id in fdinfo is a pretty recent
536          * kernel addition.
537          *
538          * As last fallback we do traditional fstat() based st_dev
539          * comparisons. This is how things were traditionally done,
540          * but unionfs breaks breaks this since it exposes file
541          * systems with a variety of st_dev reported. Also, btrfs
542          * subvolumes have different st_dev, even though they aren't
543          * real mounts of their own. */
544
545         r = name_to_handle_at(fd, filename, &h.handle, &mount_id, flags);
546         if (r < 0) {
547                 if (errno == ENOSYS)
548                         /* This kernel does not support name_to_handle_at()
549                          * fall back to simpler logic. */
550                         goto fallback_fdinfo;
551                 else if (errno == EOPNOTSUPP)
552                         /* This kernel or file system does not support
553                          * name_to_handle_at(), hence let's see if the
554                          * upper fs supports it (in which case it is a
555                          * mount point), otherwise fallback to the
556                          * traditional stat() logic */
557                         nosupp = true;
558                 else
559                         return -errno;
560         }
561
562         r = name_to_handle_at(fd, "", &h_parent.handle, &mount_id_parent, AT_EMPTY_PATH);
563         if (r < 0) {
564                 if (errno == EOPNOTSUPP) {
565                         if (nosupp)
566                                 /* Neither parent nor child do name_to_handle_at()?
567                                    We have no choice but to fall back. */
568                                 goto fallback_fdinfo;
569                         else
570                                 /* The parent can't do name_to_handle_at() but the
571                                  * directory we are interested in can?
572                                  * If so, it must be a mount point. */
573                                 return 1;
574                 } else
575                         return -errno;
576         }
577
578         /* The parent can do name_to_handle_at() but the
579          * directory we are interested in can't? If so, it
580          * must be a mount point. */
581         if (nosupp)
582                 return 1;
583
584         /* If the file handle for the directory we are
585          * interested in and its parent are identical, we
586          * assume this is the root directory, which is a mount
587          * point. */
588
589         if (h.handle.handle_bytes == h_parent.handle.handle_bytes &&
590             h.handle.handle_type == h_parent.handle.handle_type &&
591             memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0)
592                 return 1;
593
594         return mount_id != mount_id_parent;
595
596 fallback_fdinfo:
597         r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id);
598         if (r == -EOPNOTSUPP)
599                 goto fallback_fstat;
600         if (r < 0)
601                 return r;
602
603         r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent);
604         if (r < 0)
605                 return r;
606
607         if (mount_id != mount_id_parent)
608                 return 1;
609
610         /* Hmm, so, the mount ids are the same. This leaves one
611          * special case though for the root file system. For that,
612          * let's see if the parent directory has the same inode as we
613          * are interested in. Hence, let's also do fstat() checks now,
614          * too, but avoid the st_dev comparisons, since they aren't
615          * that useful on unionfs mounts. */
616         check_st_dev = false;
617
618 fallback_fstat:
619         /* yay for fstatat() taking a different set of flags than the other
620          * _at() above */
621         if (flags & AT_SYMLINK_FOLLOW)
622                 flags &= ~AT_SYMLINK_FOLLOW;
623         else
624                 flags |= AT_SYMLINK_NOFOLLOW;
625         if (fstatat(fd, filename, &a, flags) < 0)
626                 return -errno;
627
628         if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0)
629                 return -errno;
630
631         /* A directory with same device and inode as its parent? Must
632          * be the root directory */
633         if (a.st_dev == b.st_dev &&
634             a.st_ino == b.st_ino)
635                 return 1;
636
637         return check_st_dev && (a.st_dev != b.st_dev);
638 }
639
640 /* flags can be AT_SYMLINK_FOLLOW or 0 */
641 int path_is_mount_point(const char *t, int flags) {
642         _cleanup_close_ int fd = -1;
643         _cleanup_free_ char *parent = NULL;
644         int r;
645
646         assert(t);
647
648         if (path_equal(t, "/"))
649                 return 1;
650
651         r = path_get_parent(t, &parent);
652         if (r < 0)
653                 return r;
654
655         fd = openat(AT_FDCWD, parent, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_PATH);
656         if (fd < 0)
657                 return -errno;
658
659         return fd_is_mount_point(fd, basename(t), flags);
660 }
661
662 int path_is_read_only_fs(const char *path) {
663         struct statvfs st;
664
665         assert(path);
666
667         if (statvfs(path, &st) < 0)
668                 return -errno;
669
670         if (st.f_flag & ST_RDONLY)
671                 return true;
672
673         /* On NFS, statvfs() might not reflect whether we can actually
674          * write to the remote share. Let's try again with
675          * access(W_OK) which is more reliable, at least sometimes. */
676         if (access(path, W_OK) < 0 && errno == EROFS)
677                 return true;
678
679         return false;
680 }
681
682 int path_is_os_tree(const char *path) {
683         char *p;
684         int r;
685
686         /* We use /usr/lib/os-release as flag file if something is an OS */
687         p = strjoina(path, "/usr/lib/os-release");
688         r = access(p, F_OK);
689
690         if (r >= 0)
691                 return 1;
692
693         /* Also check for the old location in /etc, just in case. */
694         p = strjoina(path, "/etc/os-release");
695         r = access(p, F_OK);
696
697         return r >= 0;
698 }
699
700 int find_binary(const char *name, bool local, char **filename) {
701         assert(name);
702
703         if (is_path(name)) {
704                 if (local && access(name, X_OK) < 0)
705                         return -errno;
706
707                 if (filename) {
708                         char *p;
709
710                         p = path_make_absolute_cwd(name);
711                         if (!p)
712                                 return -ENOMEM;
713
714                         *filename = p;
715                 }
716
717                 return 0;
718         } else {
719                 const char *path;
720                 const char *word, *state;
721                 size_t l;
722
723                 /**
724                  * Plain getenv, not secure_getenv, because we want
725                  * to actually allow the user to pick the binary.
726                  */
727                 path = getenv("PATH");
728                 if (!path)
729                         path = DEFAULT_PATH;
730
731                 FOREACH_WORD_SEPARATOR(word, l, path, ":", state) {
732                         _cleanup_free_ char *p = NULL;
733
734                         if (asprintf(&p, "%.*s/%s", (int) l, word, name) < 0)
735                                 return -ENOMEM;
736
737                         if (access(p, X_OK) < 0)
738                                 continue;
739
740                         if (filename) {
741                                 *filename = path_kill_slashes(p);
742                                 p = NULL;
743                         }
744
745                         return 0;
746                 }
747
748                 return -ENOENT;
749         }
750 }
751
752 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
753         bool changed = false;
754         const char* const* i;
755
756         assert(timestamp);
757
758         if (paths == NULL)
759                 return false;
760
761         STRV_FOREACH(i, paths) {
762                 struct stat stats;
763                 usec_t u;
764
765                 if (stat(*i, &stats) < 0)
766                         continue;
767
768                 u = timespec_load(&stats.st_mtim);
769
770                 /* first check */
771                 if (*timestamp >= u)
772                         continue;
773
774                 log_debug("timestamp of '%s' changed", *i);
775
776                 /* update timestamp */
777                 if (update) {
778                         *timestamp = u;
779                         changed = true;
780                 } else
781                         return true;
782         }
783
784         return changed;
785 }
786
787 int fsck_exists(const char *fstype) {
788         _cleanup_free_ char *p = NULL, *d = NULL;
789         const char *checker;
790         int r;
791
792         checker = strjoina("fsck.", fstype);
793
794         r = find_binary(checker, true, &p);
795         if (r < 0)
796                 return r;
797
798         /* An fsck that is linked to /bin/true is a non-existent
799          * fsck */
800
801         r = readlink_malloc(p, &d);
802         if (r >= 0 &&
803             (path_equal(d, "/bin/true") ||
804              path_equal(d, "/usr/bin/true") ||
805              path_equal(d, "/dev/null")))
806                 return -ENOENT;
807
808         return 0;
809 }
810
811 char *prefix_root(const char *root, const char *path) {
812         char *n, *p;
813         size_t l;
814
815         /* If root is passed, prefixes path with it. Otherwise returns
816          * it as is. */
817
818         assert(path);
819
820         /* First, drop duplicate prefixing slashes from the path */
821         while (path[0] == '/' && path[1] == '/')
822                 path++;
823
824         if (isempty(root) || path_equal(root, "/"))
825                 return strdup(path);
826
827         l = strlen(root) + 1 + strlen(path) + 1;
828
829         n = new(char, l);
830         if (!n)
831                 return NULL;
832
833         p = stpcpy(n, root);
834
835         while (p > n && p[-1] == '/')
836                 p--;
837
838         if (path[0] != '/')
839                 *(p++) = '/';
840
841         strcpy(p, path);
842         return n;
843 }