chiark / gitweb /
path-util: make sure fd_is_mount_point() returns true for root directory
[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
37 bool path_is_absolute(const char *p) {
38         return p[0] == '/';
39 }
40
41 bool is_path(const char *p) {
42         return !!strchr(p, '/');
43 }
44
45 int path_get_parent(const char *path, char **_r) {
46         const char *e, *a = NULL, *b = NULL, *p;
47         char *r;
48         bool slash = false;
49
50         assert(path);
51         assert(_r);
52
53         if (!*path)
54                 return -EINVAL;
55
56         for (e = path; *e; e++) {
57
58                 if (!slash && *e == '/') {
59                         a = b;
60                         b = e;
61                         slash = true;
62                 } else if (slash && *e != '/')
63                         slash = false;
64         }
65
66         if (*(e-1) == '/')
67                 p = a;
68         else
69                 p = b;
70
71         if (!p)
72                 return -EINVAL;
73
74         if (p == path)
75                 r = strdup("/");
76         else
77                 r = strndup(path, p-path);
78
79         if (!r)
80                 return -ENOMEM;
81
82         *_r = r;
83         return 0;
84 }
85
86 char **path_split_and_make_absolute(const char *p) {
87         char **l;
88         assert(p);
89
90         l = strv_split(p, ":");
91         if (!l)
92                 return NULL;
93
94         if (!path_strv_make_absolute_cwd(l)) {
95                 strv_free(l);
96                 return NULL;
97         }
98
99         return l;
100 }
101
102 char *path_make_absolute(const char *p, const char *prefix) {
103         assert(p);
104
105         /* Makes every item in the list an absolute path by prepending
106          * the prefix, if specified and necessary */
107
108         if (path_is_absolute(p) || !prefix)
109                 return strdup(p);
110
111         return strjoin(prefix, "/", p, NULL);
112 }
113
114 char *path_make_absolute_cwd(const char *p) {
115         _cleanup_free_ char *cwd = NULL;
116
117         assert(p);
118
119         /* Similar to path_make_absolute(), but prefixes with the
120          * current working directory. */
121
122         if (path_is_absolute(p))
123                 return strdup(p);
124
125         cwd = get_current_dir_name();
126         if (!cwd)
127                 return NULL;
128
129         return strjoin(cwd, "/", p, NULL);
130 }
131
132 int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
133         char *r, *p;
134         unsigned n_parents;
135
136         assert(from_dir);
137         assert(to_path);
138         assert(_r);
139
140         /* Strips the common part, and adds ".." elements as necessary. */
141
142         if (!path_is_absolute(from_dir))
143                 return -EINVAL;
144
145         if (!path_is_absolute(to_path))
146                 return -EINVAL;
147
148         /* Skip the common part. */
149         for (;;) {
150                 size_t a;
151                 size_t b;
152
153                 from_dir += strspn(from_dir, "/");
154                 to_path += strspn(to_path, "/");
155
156                 if (!*from_dir) {
157                         if (!*to_path)
158                                 /* from_dir equals to_path. */
159                                 r = strdup(".");
160                         else
161                                 /* from_dir is a parent directory of to_path. */
162                                 r = strdup(to_path);
163
164                         if (!r)
165                                 return -ENOMEM;
166
167                         path_kill_slashes(r);
168
169                         *_r = r;
170                         return 0;
171                 }
172
173                 if (!*to_path)
174                         break;
175
176                 a = strcspn(from_dir, "/");
177                 b = strcspn(to_path, "/");
178
179                 if (a != b)
180                         break;
181
182                 if (memcmp(from_dir, to_path, a) != 0)
183                         break;
184
185                 from_dir += a;
186                 to_path += b;
187         }
188
189         /* If we're here, then "from_dir" has one or more elements that need to
190          * be replaced with "..". */
191
192         /* Count the number of necessary ".." elements. */
193         for (n_parents = 0;;) {
194                 from_dir += strspn(from_dir, "/");
195
196                 if (!*from_dir)
197                         break;
198
199                 from_dir += strcspn(from_dir, "/");
200                 n_parents++;
201         }
202
203         r = malloc(n_parents * 3 + strlen(to_path) + 1);
204         if (!r)
205                 return -ENOMEM;
206
207         for (p = r; n_parents > 0; n_parents--, p += 3)
208                 memcpy(p, "../", 3);
209
210         strcpy(p, to_path);
211         path_kill_slashes(r);
212
213         *_r = r;
214         return 0;
215 }
216
217 char **path_strv_make_absolute_cwd(char **l) {
218         char **s;
219
220         /* Goes through every item in the string list and makes it
221          * absolute. This works in place and won't rollback any
222          * changes on failure. */
223
224         STRV_FOREACH(s, l) {
225                 char *t;
226
227                 t = path_make_absolute_cwd(*s);
228                 if (!t)
229                         return NULL;
230
231                 free(*s);
232                 *s = t;
233         }
234
235         return l;
236 }
237
238 char **path_strv_resolve(char **l, const char *prefix) {
239         char **s;
240         unsigned k = 0;
241         bool enomem = false;
242
243         if (strv_isempty(l))
244                 return l;
245
246         /* Goes through every item in the string list and canonicalize
247          * the path. This works in place and won't rollback any
248          * changes on failure. */
249
250         STRV_FOREACH(s, l) {
251                 char *t, *u;
252                 _cleanup_free_ char *orig = NULL;
253
254                 if (!path_is_absolute(*s)) {
255                         free(*s);
256                         continue;
257                 }
258
259                 if (prefix) {
260                         orig = *s;
261                         t = strappend(prefix, orig);
262                         if (!t) {
263                                 enomem = true;
264                                 continue;
265                         }
266                 } else
267                         t = *s;
268
269                 errno = 0;
270                 u = canonicalize_file_name(t);
271                 if (!u) {
272                         if (errno == ENOENT) {
273                                 if (prefix) {
274                                         u = orig;
275                                         orig = NULL;
276                                         free(t);
277                                 } else
278                                         u = t;
279                         } else {
280                                 free(t);
281                                 if (errno == ENOMEM || errno == 0)
282                                         enomem = true;
283
284                                 continue;
285                         }
286                 } else if (prefix) {
287                         char *x;
288
289                         free(t);
290                         x = path_startswith(u, prefix);
291                         if (x) {
292                                 /* restore the slash if it was lost */
293                                 if (!startswith(x, "/"))
294                                         *(--x) = '/';
295
296                                 t = strdup(x);
297                                 free(u);
298                                 if (!t) {
299                                         enomem = true;
300                                         continue;
301                                 }
302                                 u = t;
303                         } else {
304                                 /* canonicalized path goes outside of
305                                  * prefix, keep the original path instead */
306                                 free(u);
307                                 u = orig;
308                                 orig = NULL;
309                         }
310                 } else
311                         free(t);
312
313                 l[k++] = u;
314         }
315
316         l[k] = NULL;
317
318         if (enomem)
319                 return NULL;
320
321         return l;
322 }
323
324 char **path_strv_resolve_uniq(char **l, const char *prefix) {
325
326         if (strv_isempty(l))
327                 return l;
328
329         if (!path_strv_resolve(l, prefix))
330                 return NULL;
331
332         return strv_uniq(l);
333 }
334
335 char *path_kill_slashes(char *path) {
336         char *f, *t;
337         bool slash = false;
338
339         /* Removes redundant inner and trailing slashes. Modifies the
340          * passed string in-place.
341          *
342          * ///foo///bar/ becomes /foo/bar
343          */
344
345         for (f = path, t = path; *f; f++) {
346
347                 if (*f == '/') {
348                         slash = true;
349                         continue;
350                 }
351
352                 if (slash) {
353                         slash = false;
354                         *(t++) = '/';
355                 }
356
357                 *(t++) = *f;
358         }
359
360         /* Special rule, if we are talking of the root directory, a
361         trailing slash is good */
362
363         if (t == path && slash)
364                 *(t++) = '/';
365
366         *t = 0;
367         return path;
368 }
369
370 char* path_startswith(const char *path, const char *prefix) {
371         assert(path);
372         assert(prefix);
373
374         if ((path[0] == '/') != (prefix[0] == '/'))
375                 return NULL;
376
377         for (;;) {
378                 size_t a, b;
379
380                 path += strspn(path, "/");
381                 prefix += strspn(prefix, "/");
382
383                 if (*prefix == 0)
384                         return (char*) path;
385
386                 if (*path == 0)
387                         return NULL;
388
389                 a = strcspn(path, "/");
390                 b = strcspn(prefix, "/");
391
392                 if (a != b)
393                         return NULL;
394
395                 if (memcmp(path, prefix, a) != 0)
396                         return NULL;
397
398                 path += a;
399                 prefix += b;
400         }
401 }
402
403 int path_compare(const char *a, const char *b) {
404         int d;
405
406         assert(a);
407         assert(b);
408
409         /* A relative path and an abolute path must not compare as equal.
410          * Which one is sorted before the other does not really matter.
411          * Here a relative path is ordered before an absolute path. */
412         d = (a[0] == '/') - (b[0] == '/');
413         if (d)
414                 return d;
415
416         for (;;) {
417                 size_t j, k;
418
419                 a += strspn(a, "/");
420                 b += strspn(b, "/");
421
422                 if (*a == 0 && *b == 0)
423                         return 0;
424
425                 /* Order prefixes first: "/foo" before "/foo/bar" */
426                 if (*a == 0)
427                         return -1;
428                 if (*b == 0)
429                         return 1;
430
431                 j = strcspn(a, "/");
432                 k = strcspn(b, "/");
433
434                 /* Alphabetical sort: "/foo/aaa" before "/foo/b" */
435                 d = memcmp(a, b, MIN(j, k));
436                 if (d)
437                         return (d > 0) - (d < 0); /* sign of d */
438
439                 /* Sort "/foo/a" before "/foo/aaa" */
440                 d = (j > k) - (j < k);  /* sign of (j - k) */
441                 if (d)
442                         return d;
443
444                 a += j;
445                 b += k;
446         }
447 }
448
449 bool path_equal(const char *a, const char *b) {
450         return path_compare(a, b) == 0;
451 }
452
453 bool path_equal_or_files_same(const char *a, const char *b) {
454         return path_equal(a, b) || files_same(a, b) > 0;
455 }
456
457 char* path_join(const char *root, const char *path, const char *rest) {
458         assert(path);
459
460         if (!isempty(root))
461                 return strjoin(root, endswith(root, "/") ? "" : "/",
462                                path[0] == '/' ? path+1 : path,
463                                rest ? (endswith(path, "/") ? "" : "/") : NULL,
464                                rest && rest[0] == '/' ? rest+1 : rest,
465                                NULL);
466         else
467                 return strjoin(path,
468                                rest ? (endswith(path, "/") ? "" : "/") : NULL,
469                                rest && rest[0] == '/' ? rest+1 : rest,
470                                NULL);
471 }
472
473 int fd_is_mount_point(int fd) {
474         union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT;
475         int mount_id = -1, mount_id_parent = -1;
476         bool nosupp = false;
477         struct stat a, b;
478         int r;
479
480         assert(fd >= 0);
481
482         /* We are not actually interested in the file handles, but
483          * name_to_handle_at() also passes us the mount ID, hence use
484          * it but throw the handle away */
485
486         r = name_to_handle_at(fd, "", &h.handle, &mount_id, AT_EMPTY_PATH);
487         if (r < 0) {
488                 if (errno == ENOSYS)
489                         /* This kernel does not support name_to_handle_at()
490                          * fall back to the traditional stat() logic. */
491                         goto fallback;
492                 else if (errno == EOPNOTSUPP)
493                         /* This kernel or file system does not support
494                          * name_to_handle_at(), hence let's see if the
495                          * upper fs supports it (in which case it is a
496                          * mount point), otherwise fallback to the
497                          * traditional stat() logic */
498                         nosupp = true;
499                 else if (errno == ENOENT)
500                         return 0;
501                 else
502                         return -errno;
503         }
504
505         r = name_to_handle_at(fd, "..", &h_parent.handle, &mount_id_parent, 0);
506         if (r < 0) {
507                 if (errno == EOPNOTSUPP) {
508                         if (nosupp)
509                                 /* Neither parent nor child do name_to_handle_at()?
510                                    We have no choice but to fall back. */
511                                 goto fallback;
512                         else
513                                 /* The parent can't do name_to_handle_at() but the
514                                  * directory we are interested in can?
515                                  * If so, it must be a mount point. */
516                                 return 1;
517                 } else
518                         return -errno;
519         } else if (nosupp)
520                 /* The parent can do name_to_handle_at() but the
521                  * directory we are interested in can't? If so, it
522                  * must be a mount point. */
523                 return 1;
524         else {
525                 /* If the file handle for the directory we are
526                  * interested in and its parent are identical, we
527                  * assume this is the root directory, which is a mount
528                  * point. */
529
530                 if (h.handle.handle_bytes == h_parent.handle.handle_bytes &&
531                     h.handle.handle_type == h_parent.handle.handle_type &&
532                     memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0)
533                         return 1;
534
535                 return mount_id != mount_id_parent;
536         }
537
538 fallback:
539         r = fstatat(fd, "", &a, AT_EMPTY_PATH);
540         if (r < 0) {
541                 if (errno == ENOENT)
542                         return 0;
543
544                 return -errno;
545         }
546
547         r = fstatat(fd, "..", &b, 0);
548         if (r < 0)
549                 return -errno;
550
551         /* A directory with same device and inode as its parent? Must
552          * be the root directory */
553         if (a.st_dev == b.st_dev &&
554             a.st_ino == b.st_ino)
555                 return 1;
556
557         return a.st_dev != b.st_dev;
558 }
559
560 int path_is_mount_point(const char *t, bool allow_symlink) {
561         _cleanup_close_ int fd = -1;
562         assert(t);
563
564         if (path_equal(t, "/"))
565                 return 1;
566
567         fd = openat(AT_FDCWD, t, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|(allow_symlink ? 0 : O_PATH));
568         if (fd < 0) {
569                 if (errno == ENOENT)
570                         return 0;
571
572                 return -errno;
573         }
574
575         return fd_is_mount_point(fd);
576 }
577
578 int path_is_read_only_fs(const char *path) {
579         struct statvfs st;
580
581         assert(path);
582
583         if (statvfs(path, &st) < 0)
584                 return -errno;
585
586         if (st.f_flag & ST_RDONLY)
587                 return true;
588
589         /* On NFS, statvfs() might not reflect whether we can actually
590          * write to the remote share. Let's try again with
591          * access(W_OK) which is more reliable, at least sometimes. */
592         if (access(path, W_OK) < 0 && errno == EROFS)
593                 return true;
594
595         return false;
596 }
597
598 int path_is_os_tree(const char *path) {
599         char *p;
600         int r;
601
602         /* We use /usr/lib/os-release as flag file if something is an OS */
603         p = strjoina(path, "/usr/lib/os-release");
604         r = access(p, F_OK);
605
606         if (r >= 0)
607                 return 1;
608
609         /* Also check for the old location in /etc, just in case. */
610         p = strjoina(path, "/etc/os-release");
611         r = access(p, F_OK);
612
613         return r >= 0;
614 }
615
616 int find_binary(const char *name, bool local, char **filename) {
617         assert(name);
618
619         if (is_path(name)) {
620                 if (local && access(name, X_OK) < 0)
621                         return -errno;
622
623                 if (filename) {
624                         char *p;
625
626                         p = path_make_absolute_cwd(name);
627                         if (!p)
628                                 return -ENOMEM;
629
630                         *filename = p;
631                 }
632
633                 return 0;
634         } else {
635                 const char *path;
636                 const char *word, *state;
637                 size_t l;
638
639                 /**
640                  * Plain getenv, not secure_getenv, because we want
641                  * to actually allow the user to pick the binary.
642                  */
643                 path = getenv("PATH");
644                 if (!path)
645                         path = DEFAULT_PATH;
646
647                 FOREACH_WORD_SEPARATOR(word, l, path, ":", state) {
648                         _cleanup_free_ char *p = NULL;
649
650                         if (asprintf(&p, "%.*s/%s", (int) l, word, name) < 0)
651                                 return -ENOMEM;
652
653                         if (access(p, X_OK) < 0)
654                                 continue;
655
656                         if (filename) {
657                                 *filename = path_kill_slashes(p);
658                                 p = NULL;
659                         }
660
661                         return 0;
662                 }
663
664                 return -ENOENT;
665         }
666 }
667
668 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
669         bool changed = false;
670         const char* const* i;
671
672         assert(timestamp);
673
674         if (paths == NULL)
675                 return false;
676
677         STRV_FOREACH(i, paths) {
678                 struct stat stats;
679                 usec_t u;
680
681                 if (stat(*i, &stats) < 0)
682                         continue;
683
684                 u = timespec_load(&stats.st_mtim);
685
686                 /* first check */
687                 if (*timestamp >= u)
688                         continue;
689
690                 log_debug("timestamp of '%s' changed", *i);
691
692                 /* update timestamp */
693                 if (update) {
694                         *timestamp = u;
695                         changed = true;
696                 } else
697                         return true;
698         }
699
700         return changed;
701 }
702
703 int fsck_exists(const char *fstype) {
704         _cleanup_free_ char *p = NULL, *d = NULL;
705         const char *checker;
706         int r;
707
708         checker = strjoina("fsck.", fstype);
709
710         r = find_binary(checker, true, &p);
711         if (r < 0)
712                 return r;
713
714         /* An fsck that is linked to /bin/true is a non-existent
715          * fsck */
716
717         r = readlink_malloc(p, &d);
718         if (r >= 0 &&
719             (path_equal(d, "/bin/true") ||
720              path_equal(d, "/usr/bin/true") ||
721              path_equal(d, "/dev/null")))
722                 return -ENOENT;
723
724         return 0;
725 }