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