chiark / gitweb /
path-util: minor coding style fix
[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
223 char **path_strv_make_absolute_cwd(char **l) {
224         char **s;
225
226         /* Goes through every item in the string list and makes it
227          * absolute. This works in place and won't rollback any
228          * changes on failure. */
229
230         STRV_FOREACH(s, l) {
231                 char *t;
232
233                 t = path_make_absolute_cwd(*s);
234                 if (!t)
235                         return NULL;
236
237                 free(*s);
238                 *s = t;
239         }
240
241         return l;
242 }
243 #endif // 0
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 != 0)
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 != 0)
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 != 0)
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                 t = canonical;
670         }
671
672         r = path_get_parent(t, &parent);
673         if (r < 0)
674                 return r;
675
676         fd = openat(AT_FDCWD, parent, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_PATH);
677         if (fd < 0)
678                 return -errno;
679
680         return fd_is_mount_point(fd, basename(t), flags);
681 }
682
683 int path_is_read_only_fs(const char *path) {
684         struct statvfs st;
685
686         assert(path);
687
688         if (statvfs(path, &st) < 0)
689                 return -errno;
690
691         if (st.f_flag & ST_RDONLY)
692                 return true;
693
694         /* On NFS, statvfs() might not reflect whether we can actually
695          * write to the remote share. Let's try again with
696          * access(W_OK) which is more reliable, at least sometimes. */
697         if (access(path, W_OK) < 0 && errno == EROFS)
698                 return true;
699
700         return false;
701 }
702
703 /// UNNEEDED by elogind
704 #if 0
705 int path_is_os_tree(const char *path) {
706         char *p;
707         int r;
708
709         /* We use /usr/lib/os-release as flag file if something is an OS */
710         p = strjoina(path, "/usr/lib/os-release");
711         r = access(p, F_OK);
712
713         if (r >= 0)
714                 return 1;
715
716         /* Also check for the old location in /etc, just in case. */
717         p = strjoina(path, "/etc/os-release");
718         r = access(p, F_OK);
719
720         return r >= 0;
721 }
722
723 int find_binary(const char *name, bool local, char **filename) {
724         assert(name);
725
726         if (is_path(name)) {
727                 if (local && access(name, X_OK) < 0)
728                         return -errno;
729
730                 if (filename) {
731                         char *p;
732
733                         p = path_make_absolute_cwd(name);
734                         if (!p)
735                                 return -ENOMEM;
736
737                         *filename = p;
738                 }
739
740                 return 0;
741         } else {
742                 const char *path;
743                 const char *word, *state;
744                 size_t l;
745
746                 /**
747                  * Plain getenv, not secure_getenv, because we want
748                  * to actually allow the user to pick the binary.
749                  */
750                 path = getenv("PATH");
751                 if (!path)
752                         path = DEFAULT_PATH;
753
754                 FOREACH_WORD_SEPARATOR(word, l, path, ":", state) {
755                         _cleanup_free_ char *p = NULL;
756
757                         if (asprintf(&p, "%.*s/%s", (int) l, word, name) < 0)
758                                 return -ENOMEM;
759
760                         if (access(p, X_OK) < 0)
761                                 continue;
762
763                         if (filename) {
764                                 *filename = path_kill_slashes(p);
765                                 p = NULL;
766                         }
767
768                         return 0;
769                 }
770
771                 return -ENOENT;
772         }
773 }
774
775 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
776         bool changed = false;
777         const char* const* i;
778
779         assert(timestamp);
780
781         if (paths == NULL)
782                 return false;
783
784         STRV_FOREACH(i, paths) {
785                 struct stat stats;
786                 usec_t u;
787
788                 if (stat(*i, &stats) < 0)
789                         continue;
790
791                 u = timespec_load(&stats.st_mtim);
792
793                 /* first check */
794                 if (*timestamp >= u)
795                         continue;
796
797                 log_debug("timestamp of '%s' changed", *i);
798
799                 /* update timestamp */
800                 if (update) {
801                         *timestamp = u;
802                         changed = true;
803                 } else
804                         return true;
805         }
806
807         return changed;
808 }
809
810 int fsck_exists(const char *fstype) {
811         _cleanup_free_ char *p = NULL, *d = NULL;
812         const char *checker;
813         int r;
814
815         checker = strjoina("fsck.", fstype);
816
817         r = find_binary(checker, true, &p);
818         if (r < 0)
819                 return r;
820
821         /* An fsck that is linked to /bin/true is a non-existent
822          * fsck */
823
824         r = readlink_malloc(p, &d);
825         if (r >= 0 &&
826             (path_equal(d, "/bin/true") ||
827              path_equal(d, "/usr/bin/true") ||
828              path_equal(d, "/dev/null")))
829                 return -ENOENT;
830
831         return 0;
832 }
833
834 char *prefix_root(const char *root, const char *path) {
835         char *n, *p;
836         size_t l;
837
838         /* If root is passed, prefixes path with it. Otherwise returns
839          * it as is. */
840
841         assert(path);
842
843         /* First, drop duplicate prefixing slashes from the path */
844         while (path[0] == '/' && path[1] == '/')
845                 path++;
846
847         if (isempty(root) || path_equal(root, "/"))
848                 return strdup(path);
849
850         l = strlen(root) + 1 + strlen(path) + 1;
851
852         n = new(char, l);
853         if (!n)
854                 return NULL;
855
856         p = stpcpy(n, root);
857
858         while (p > n && p[-1] == '/')
859                 p--;
860
861         if (path[0] != '/')
862                 *(p++) = '/';
863
864         strcpy(p, path);
865         return n;
866 }
867 #endif // 0