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