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