chiark / gitweb /
util-lib: kill duplicate slashes in lookup paths
[elogind.git] / src / basic / path-util.c
1 /***
2   This file is part of systemd.
3
4   Copyright 2010-2012 Lennart Poettering
5
6   systemd is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   systemd is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <errno.h>
21 #include <limits.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27
28 /* When we include libgen.h because we need dirname() we immediately
29  * undefine basename() since libgen.h defines it as a macro to the
30  * POSIX version which is really broken. We prefer GNU basename(). */
31 #include <libgen.h>
32 #undef basename
33
34 #include "alloc-util.h"
35 #include "extract-word.h"
36 #include "fs-util.h"
37 //#include "glob-util.h"
38 #include "log.h"
39 #include "macro.h"
40 #include "missing.h"
41 #include "parse-util.h"
42 #include "path-util.h"
43 #include "stat-util.h"
44 #include "string-util.h"
45 #include "strv.h"
46 #include "time-util.h"
47
48 bool path_is_absolute(const char *p) {
49         return p[0] == '/';
50 }
51
52 bool is_path(const char *p) {
53         return !!strchr(p, '/');
54 }
55
56 #if 0 /// UNNEEDED by elogind
57 int path_split_and_make_absolute(const char *p, char ***ret) {
58         char **l;
59         int r;
60
61         assert(p);
62         assert(ret);
63
64         l = strv_split(p, ":");
65         if (!l)
66                 return -ENOMEM;
67
68         r = path_strv_make_absolute_cwd(l);
69         if (r < 0) {
70                 strv_free(l);
71                 return r;
72         }
73
74         *ret = l;
75         return r;
76 }
77
78 char *path_make_absolute(const char *p, const char *prefix) {
79         assert(p);
80
81         /* Makes every item in the list an absolute path by prepending
82          * the prefix, if specified and necessary */
83
84         if (path_is_absolute(p) || !prefix)
85                 return strdup(p);
86
87         return strjoin(prefix, "/", p);
88 }
89 #endif // 0
90
91 int path_make_absolute_cwd(const char *p, char **ret) {
92         char *c;
93
94         assert(p);
95         assert(ret);
96
97         /* Similar to path_make_absolute(), but prefixes with the
98          * current working directory. */
99
100         if (path_is_absolute(p))
101                 c = strdup(p);
102         else {
103                 _cleanup_free_ char *cwd = NULL;
104
105                 cwd = get_current_dir_name();
106                 if (!cwd)
107                         return negative_errno();
108
109                 c = strjoin(cwd, "/", p);
110         }
111         if (!c)
112                 return -ENOMEM;
113
114         *ret = c;
115         return 0;
116 }
117
118 #if 0 /// UNNEEDED by elogind
119 int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
120         char *r, *p;
121         unsigned n_parents;
122
123         assert(from_dir);
124         assert(to_path);
125         assert(_r);
126
127         /* Strips the common part, and adds ".." elements as necessary. */
128
129         if (!path_is_absolute(from_dir))
130                 return -EINVAL;
131
132         if (!path_is_absolute(to_path))
133                 return -EINVAL;
134
135         /* Skip the common part. */
136         for (;;) {
137                 size_t a, b;
138
139                 from_dir += strspn(from_dir, "/");
140                 to_path += strspn(to_path, "/");
141
142                 if (!*from_dir) {
143                         if (!*to_path)
144                                 /* from_dir equals to_path. */
145                                 r = strdup(".");
146                         else
147                                 /* from_dir is a parent directory of to_path. */
148                                 r = strdup(to_path);
149                         if (!r)
150                                 return -ENOMEM;
151
152                         path_kill_slashes(r);
153
154                         *_r = r;
155                         return 0;
156                 }
157
158                 if (!*to_path)
159                         break;
160
161                 a = strcspn(from_dir, "/");
162                 b = strcspn(to_path, "/");
163
164                 if (a != b)
165                         break;
166
167                 if (memcmp(from_dir, to_path, a) != 0)
168                         break;
169
170                 from_dir += a;
171                 to_path += b;
172         }
173
174         /* If we're here, then "from_dir" has one or more elements that need to
175          * be replaced with "..". */
176
177         /* Count the number of necessary ".." elements. */
178         for (n_parents = 0;;) {
179                 size_t w;
180
181                 from_dir += strspn(from_dir, "/");
182
183                 if (!*from_dir)
184                         break;
185
186                 w = strcspn(from_dir, "/");
187
188                 /* If this includes ".." we can't do a simple series of "..", refuse */
189                 if (w == 2 && from_dir[0] == '.' && from_dir[1] == '.')
190                         return -EINVAL;
191
192                 /* Count number of elements, except if they are "." */
193                 if (w != 1 || from_dir[0] != '.')
194                         n_parents++;
195
196                 from_dir += w;
197         }
198
199         r = new(char, n_parents * 3 + strlen(to_path) + 1);
200         if (!r)
201                 return -ENOMEM;
202
203         for (p = r; n_parents > 0; n_parents--)
204                 p = mempcpy(p, "../", 3);
205
206         strcpy(p, to_path);
207         path_kill_slashes(r);
208
209         *_r = r;
210         return 0;
211 }
212
213 int path_strv_make_absolute_cwd(char **l) {
214         char **s;
215         int r;
216
217         /* Goes through every item in the string list and makes it
218          * absolute. This works in place and won't rollback any
219          * changes on failure. */
220
221         STRV_FOREACH(s, l) {
222                 char *t;
223
224                 r = path_make_absolute_cwd(*s, &t);
225                 if (r < 0)
226                         return r;
227
228                 path_kill_slashes(t);
229                 free_and_replace(*s, t);
230         }
231
232         return 0;
233 }
234 #endif // 0
235
236 char **path_strv_resolve(char **l, const char *root) {
237         char **s;
238         unsigned k = 0;
239         bool enomem = false;
240         int r;
241
242         if (strv_isempty(l))
243                 return l;
244
245         /* Goes through every item in the string list and canonicalize
246          * the path. This works in place and won't rollback any
247          * changes on failure. */
248
249         STRV_FOREACH(s, l) {
250                 _cleanup_free_ char *orig = NULL;
251                 char *t, *u;
252
253                 if (!path_is_absolute(*s)) {
254                         free(*s);
255                         continue;
256                 }
257
258                 if (root) {
259                         orig = *s;
260                         t = prefix_root(root, orig);
261                         if (!t) {
262                                 enomem = true;
263                                 continue;
264                         }
265                 } else
266                         t = *s;
267
268                 r = chase_symlinks(t, root, 0, &u);
269                 if (r == -ENOENT) {
270                         if (root) {
271                                 u = orig;
272                                 orig = NULL;
273                                 free(t);
274                         } else
275                                 u = t;
276                 } else if (r < 0) {
277                         free(t);
278
279                         if (r == -ENOMEM)
280                                 enomem = true;
281
282                         continue;
283                 } else if (root) {
284                         char *x;
285
286                         free(t);
287                         x = path_startswith(u, root);
288                         if (x) {
289                                 /* restore the slash if it was lost */
290                                 if (!startswith(x, "/"))
291                                         *(--x) = '/';
292
293                                 t = strdup(x);
294                                 free(u);
295                                 if (!t) {
296                                         enomem = true;
297                                         continue;
298                                 }
299                                 u = t;
300                         } else {
301                                 /* canonicalized path goes outside of
302                                  * prefix, keep the original path instead */
303                                 free_and_replace(u, orig);
304                         }
305                 } else
306                         free(t);
307
308                 l[k++] = u;
309         }
310
311         l[k] = NULL;
312
313         if (enomem)
314                 return NULL;
315
316         return l;
317 }
318
319 char **path_strv_resolve_uniq(char **l, const char *root) {
320
321         if (strv_isempty(l))
322                 return l;
323
324         if (!path_strv_resolve(l, root))
325                 return NULL;
326
327         return strv_uniq(l);
328 }
329
330 char *path_kill_slashes(char *path) {
331         char *f, *t;
332         bool slash = false;
333
334         /* Removes redundant inner and trailing slashes. Modifies the
335          * passed string in-place.
336          *
337          * ///foo///bar/ becomes /foo/bar
338          */
339
340         for (f = path, t = path; *f; f++) {
341
342                 if (*f == '/') {
343                         slash = true;
344                         continue;
345                 }
346
347                 if (slash) {
348                         slash = false;
349                         *(t++) = '/';
350                 }
351
352                 *(t++) = *f;
353         }
354
355         /* Special rule, if we are talking of the root directory, a
356         trailing slash is good */
357
358         if (t == path && slash)
359                 *(t++) = '/';
360
361         *t = 0;
362         return path;
363 }
364
365 char* path_startswith(const char *path, const char *prefix) {
366         assert(path);
367         assert(prefix);
368
369         /* Returns a pointer to the start of the first component after the parts matched by
370          * the prefix, iff
371          * - both paths are absolute or both paths are relative,
372          * and
373          * - each component in prefix in turn matches a component in path at the same position.
374          * An empty string will be returned when the prefix and path are equivalent.
375          *
376          * Returns NULL otherwise.
377          */
378
379         if ((path[0] == '/') != (prefix[0] == '/'))
380                 return NULL;
381
382         for (;;) {
383                 size_t a, b;
384
385                 path += strspn(path, "/");
386                 prefix += strspn(prefix, "/");
387
388                 if (*prefix == 0)
389                         return (char*) path;
390
391                 if (*path == 0)
392                         return NULL;
393
394                 a = strcspn(path, "/");
395                 b = strcspn(prefix, "/");
396
397                 if (a != b)
398                         return NULL;
399
400                 if (memcmp(path, prefix, a) != 0)
401                         return NULL;
402
403                 path += a;
404                 prefix += b;
405         }
406 }
407
408 int path_compare(const char *a, const char *b) {
409         int d;
410
411         assert(a);
412         assert(b);
413
414         /* A relative path and an abolute path must not compare as equal.
415          * Which one is sorted before the other does not really matter.
416          * Here a relative path is ordered before an absolute path. */
417         d = (a[0] == '/') - (b[0] == '/');
418         if (d != 0)
419                 return d;
420
421         for (;;) {
422                 size_t j, k;
423
424                 a += strspn(a, "/");
425                 b += strspn(b, "/");
426
427                 if (*a == 0 && *b == 0)
428                         return 0;
429
430                 /* Order prefixes first: "/foo" before "/foo/bar" */
431                 if (*a == 0)
432                         return -1;
433                 if (*b == 0)
434                         return 1;
435
436                 j = strcspn(a, "/");
437                 k = strcspn(b, "/");
438
439                 /* Alphabetical sort: "/foo/aaa" before "/foo/b" */
440                 d = memcmp(a, b, MIN(j, k));
441                 if (d != 0)
442                         return (d > 0) - (d < 0); /* sign of d */
443
444                 /* Sort "/foo/a" before "/foo/aaa" */
445                 d = (j > k) - (j < k);  /* sign of (j - k) */
446                 if (d != 0)
447                         return d;
448
449                 a += j;
450                 b += k;
451         }
452 }
453
454 bool path_equal(const char *a, const char *b) {
455         return path_compare(a, b) == 0;
456 }
457
458 bool path_equal_or_files_same(const char *a, const char *b, int flags) {
459         return path_equal(a, b) || files_same(a, b, flags) > 0;
460 }
461
462 char* path_join(const char *root, const char *path, const char *rest) {
463         assert(path);
464
465         if (!isempty(root))
466                 return strjoin(root, endswith(root, "/") ? "" : "/",
467                                path[0] == '/' ? path+1 : path,
468                                rest ? (endswith(path, "/") ? "" : "/") : NULL,
469                                rest && rest[0] == '/' ? rest+1 : rest);
470         else
471                 return strjoin(path,
472                                rest ? (endswith(path, "/") ? "" : "/") : NULL,
473                                rest && rest[0] == '/' ? rest+1 : rest);
474 }
475
476 int find_binary(const char *name, char **ret) {
477         int last_error, r;
478         const char *p;
479
480         assert(name);
481
482         if (is_path(name)) {
483                 if (access(name, X_OK) < 0)
484                         return -errno;
485
486                 if (ret) {
487                         r = path_make_absolute_cwd(name, ret);
488                         if (r < 0)
489                                 return r;
490                 }
491
492                 return 0;
493         }
494
495         /**
496          * Plain getenv, not secure_getenv, because we want
497          * to actually allow the user to pick the binary.
498          */
499         p = getenv("PATH");
500         if (!p)
501                 p = DEFAULT_PATH;
502
503         last_error = -ENOENT;
504
505         for (;;) {
506                 _cleanup_free_ char *j = NULL, *element = NULL;
507
508                 r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS);
509                 if (r < 0)
510                         return r;
511                 if (r == 0)
512                         break;
513
514                 if (!path_is_absolute(element))
515                         continue;
516
517                 j = strjoin(element, "/", name);
518                 if (!j)
519                         return -ENOMEM;
520
521                 if (access(j, X_OK) >= 0) {
522                         /* Found it! */
523
524                         if (ret) {
525                                 *ret = path_kill_slashes(j);
526                                 j = NULL;
527                         }
528
529                         return 0;
530                 }
531
532                 last_error = -errno;
533         }
534
535         return last_error;
536 }
537
538 #if 0 /// UNNEEDED by elogind
539 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
540         bool changed = false;
541         const char* const* i;
542
543         assert(timestamp);
544
545         if (paths == NULL)
546                 return false;
547
548         STRV_FOREACH(i, paths) {
549                 struct stat stats;
550                 usec_t u;
551
552                 if (stat(*i, &stats) < 0)
553                         continue;
554
555                 u = timespec_load(&stats.st_mtim);
556
557                 /* first check */
558                 if (*timestamp >= u)
559                         continue;
560
561                 log_debug("timestamp of '%s' changed", *i);
562
563                 /* update timestamp */
564                 if (update) {
565                         *timestamp = u;
566                         changed = true;
567                 } else
568                         return true;
569         }
570
571         return changed;
572 }
573
574 static int binary_is_good(const char *binary) {
575         _cleanup_free_ char *p = NULL, *d = NULL;
576         int r;
577
578         r = find_binary(binary, &p);
579         if (r == -ENOENT)
580                 return 0;
581         if (r < 0)
582                 return r;
583
584         /* An fsck that is linked to /bin/true is a non-existent
585          * fsck */
586
587         r = readlink_malloc(p, &d);
588         if (r == -EINVAL) /* not a symlink */
589                 return 1;
590         if (r < 0)
591                 return r;
592
593         return !PATH_IN_SET(d, "true"
594                                "/bin/true",
595                                "/usr/bin/true",
596                                "/dev/null");
597 }
598
599 int fsck_exists(const char *fstype) {
600         const char *checker;
601
602         assert(fstype);
603
604         if (streq(fstype, "auto"))
605                 return -EINVAL;
606
607         checker = strjoina("fsck.", fstype);
608         return binary_is_good(checker);
609 }
610
611 int mkfs_exists(const char *fstype) {
612         const char *mkfs;
613
614         assert(fstype);
615
616         if (streq(fstype, "auto"))
617                 return -EINVAL;
618
619         mkfs = strjoina("mkfs.", fstype);
620         return binary_is_good(mkfs);
621 }
622 #endif // 0
623
624 char *prefix_root(const char *root, const char *path) {
625         char *n, *p;
626         size_t l;
627
628         /* If root is passed, prefixes path with it. Otherwise returns
629          * it as is. */
630
631         assert(path);
632
633         /* First, drop duplicate prefixing slashes from the path */
634         while (path[0] == '/' && path[1] == '/')
635                 path++;
636
637         if (isempty(root) || path_equal(root, "/"))
638                 return strdup(path);
639
640         l = strlen(root) + 1 + strlen(path) + 1;
641
642         n = new(char, l);
643         if (!n)
644                 return NULL;
645
646         p = stpcpy(n, root);
647
648         while (p > n && p[-1] == '/')
649                 p--;
650
651         if (path[0] != '/')
652                 *(p++) = '/';
653
654         strcpy(p, path);
655         return n;
656 }
657
658 #if 0 /// UNNEEDED by elogind
659 int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) {
660         char *p;
661         int r;
662
663         /*
664          * This function is intended to be used in command line
665          * parsers, to handle paths that are passed in. It makes the
666          * path absolute, and reduces it to NULL if omitted or
667          * root (the latter optionally).
668          *
669          * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON
670          * SUCCESS! Hence, do not pass in uninitialized pointers.
671          */
672
673         if (isempty(path)) {
674                 *arg = mfree(*arg);
675                 return 0;
676         }
677
678         r = path_make_absolute_cwd(path, &p);
679         if (r < 0)
680                 return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path);
681
682         path_kill_slashes(p);
683         if (suppress_root && path_equal(p, "/"))
684                 p = mfree(p);
685
686         free(*arg);
687         *arg = p;
688         return 0;
689 }
690 #endif // 0
691
692 char* dirname_malloc(const char *path) {
693         char *d, *dir, *dir2;
694
695         assert(path);
696
697         d = strdup(path);
698         if (!d)
699                 return NULL;
700
701         dir = dirname(d);
702         assert(dir);
703
704         if (dir == d)
705                 return d;
706
707         dir2 = strdup(dir);
708         free(d);
709
710         return dir2;
711 }
712
713 bool filename_is_valid(const char *p) {
714         const char *e;
715
716         if (isempty(p))
717                 return false;
718
719         if (dot_or_dot_dot(p))
720                 return false;
721
722         e = strchrnul(p, '/');
723         if (*e != 0)
724                 return false;
725
726         if (e - p > FILENAME_MAX)
727                 return false;
728
729         return true;
730 }
731
732 bool path_is_safe(const char *p) {
733
734         if (isempty(p))
735                 return false;
736
737         if (dot_or_dot_dot(p))
738                 return false;
739
740         if (startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../"))
741                 return false;
742
743         if (strlen(p)+1 > PATH_MAX)
744                 return false;
745
746         /* The following two checks are not really dangerous, but hey, they still are confusing */
747         if (startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./"))
748                 return false;
749
750         if (strstr(p, "//"))
751                 return false;
752
753         return true;
754 }
755
756 char *file_in_same_dir(const char *path, const char *filename) {
757         char *e, *ret;
758         size_t k;
759
760         assert(path);
761         assert(filename);
762
763         /* This removes the last component of path and appends
764          * filename, unless the latter is absolute anyway or the
765          * former isn't */
766
767         if (path_is_absolute(filename))
768                 return strdup(filename);
769
770         e = strrchr(path, '/');
771         if (!e)
772                 return strdup(filename);
773
774         k = strlen(filename);
775         ret = new(char, (e + 1 - path) + k + 1);
776         if (!ret)
777                 return NULL;
778
779         memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1);
780         return ret;
781 }
782
783 bool hidden_or_backup_file(const char *filename) {
784         const char *p;
785
786         assert(filename);
787
788         if (filename[0] == '.' ||
789             streq(filename, "lost+found") ||
790             streq(filename, "aquota.user") ||
791             streq(filename, "aquota.group") ||
792             endswith(filename, "~"))
793                 return true;
794
795         p = strrchr(filename, '.');
796         if (!p)
797                 return false;
798
799         /* Please, let's not add more entries to the list below. If external projects think it's a good idea to come up
800          * with always new suffixes and that everybody else should just adjust to that, then it really should be on
801          * them. Hence, in future, let's not add any more entries. Instead, let's ask those packages to instead adopt
802          * one of the generic suffixes/prefixes for hidden files or backups, possibly augmented with an additional
803          * string. Specifically: there's now:
804          *
805          *    The generic suffixes "~" and ".bak" for backup files
806          *    The generic prefix "." for hidden files
807          *
808          * Thus, if a new package manager "foopkg" wants its own set of ".foopkg-new", ".foopkg-old", ".foopkg-dist"
809          * or so registered, let's refuse that and ask them to use ".foopkg.new", ".foopkg.old" or ".foopkg~" instead.
810          */
811
812         return STR_IN_SET(p + 1,
813                           "rpmnew",
814                           "rpmsave",
815                           "rpmorig",
816                           "dpkg-old",
817                           "dpkg-new",
818                           "dpkg-tmp",
819                           "dpkg-dist",
820                           "dpkg-bak",
821                           "dpkg-backup",
822                           "dpkg-remove",
823                           "ucf-new",
824                           "ucf-old",
825                           "ucf-dist",
826                           "swp",
827                           "bak",
828                           "old",
829                           "new");
830 }
831
832 #if 0 /// UNNEEDED by elogind
833 bool is_device_path(const char *path) {
834
835         /* Returns true on paths that refer to a device, either in
836          * sysfs or in /dev */
837
838         return path_startswith(path, "/dev/") ||
839                path_startswith(path, "/sys/");
840 }
841
842 bool is_deviceallow_pattern(const char *path) {
843         return path_startswith(path, "/dev/") ||
844                startswith(path, "block-") ||
845                startswith(path, "char-");
846 }
847
848 int systemd_installation_has_version(const char *root, unsigned minimal_version) {
849         const char *pattern;
850         int r;
851
852         /* Try to guess if systemd installation is later than the specified version. This
853          * is hacky and likely to yield false negatives, particularly if the installation
854          * is non-standard. False positives should be relatively rare.
855          */
856
857         NULSTR_FOREACH(pattern,
858                        /* /lib works for systems without usr-merge, and for systems with a sane
859                         * usr-merge, where /lib is a symlink to /usr/lib. /usr/lib is necessary
860                         * for Gentoo which does a merge without making /lib a symlink.
861                         */
862                        "lib/systemd/libsystemd-shared-*.so\0"
863                        "usr/lib/systemd/libsystemd-shared-*.so\0") {
864
865                 _cleanup_strv_free_ char **names = NULL;
866                 _cleanup_free_ char *path = NULL;
867                 char *c, **name;
868
869                 path = prefix_root(root, pattern);
870                 if (!path)
871                         return -ENOMEM;
872
873                 r = glob_extend(&names, path);
874                 if (r == -ENOENT)
875                         continue;
876                 if (r < 0)
877                         return r;
878
879                 assert_se((c = endswith(path, "*.so")));
880                 *c = '\0'; /* truncate the glob part */
881
882                 STRV_FOREACH(name, names) {
883                         /* This is most likely to run only once, hence let's not optimize anything. */
884                         char *t, *t2;
885                         unsigned version;
886
887                         t = startswith(*name, path);
888                         if (!t)
889                                 continue;
890
891                         t2 = endswith(t, ".so");
892                         if (!t2)
893                                 continue;
894
895                         t2[0] = '\0'; /* truncate the suffix */
896
897                         r = safe_atou(t, &version);
898                         if (r < 0) {
899                                 log_debug_errno(r, "Found libsystemd shared at \"%s.so\", but failed to parse version: %m", *name);
900                                 continue;
901                         }
902
903                         log_debug("Found libsystemd shared at \"%s.so\", version %u (%s).",
904                                   *name, version,
905                                   version >= minimal_version ? "OK" : "too old");
906                         if (version >= minimal_version)
907                                 return true;
908                 }
909         }
910
911         return false;
912 }
913 #endif // 0
914
915 bool dot_or_dot_dot(const char *path) {
916         if (!path)
917                 return false;
918         if (path[0] != '.')
919                 return false;
920         if (path[1] == 0)
921                 return true;
922         if (path[1] != '.')
923                 return false;
924
925         return path[2] == 0;
926 }