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