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