]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/path-util.c
sd-*.h: clean up exported (or to-be-exported) header files
[thirdparty/systemd.git] / src / basic / path-util.c
CommitLineData
9eb977db
KS
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2010-2012 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
9eb977db 22#include <errno.h>
9eb977db 23#include <fcntl.h>
07630cea
LP
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
9eb977db 27#include <sys/statvfs.h>
07630cea 28#include <unistd.h>
9eb977db 29
07630cea 30#include "fileio.h"
9eb977db 31#include "log.h"
07630cea
LP
32#include "macro.h"
33#include "missing.h"
34#include "string-util.h"
9eb977db 35#include "strv.h"
07630cea 36#include "util.h"
9eb977db
KS
37#include "path-util.h"
38
39bool path_is_absolute(const char *p) {
40 return p[0] == '/';
41}
42
43bool is_path(const char *p) {
44 return !!strchr(p, '/');
45}
46
9eb977db
KS
47int path_get_parent(const char *path, char **_r) {
48 const char *e, *a = NULL, *b = NULL, *p;
49 char *r;
50 bool slash = false;
51
52 assert(path);
53 assert(_r);
54
55 if (!*path)
56 return -EINVAL;
57
58 for (e = path; *e; e++) {
59
60 if (!slash && *e == '/') {
61 a = b;
62 b = e;
63 slash = true;
64 } else if (slash && *e != '/')
65 slash = false;
66 }
67
68 if (*(e-1) == '/')
69 p = a;
70 else
71 p = b;
72
73 if (!p)
74 return -EINVAL;
75
76 if (p == path)
77 r = strdup("/");
78 else
79 r = strndup(path, p-path);
80
81 if (!r)
82 return -ENOMEM;
83
84 *_r = r;
85 return 0;
86}
87
0f474365 88int path_split_and_make_absolute(const char *p, char ***ret) {
9eb977db 89 char **l;
0f474365
LP
90 int r;
91
9eb977db 92 assert(p);
0f474365 93 assert(ret);
9eb977db 94
116cc028
ZJS
95 l = strv_split(p, ":");
96 if (!l)
9eb977db
KS
97 return NULL;
98
0f474365
LP
99 r = path_strv_make_absolute_cwd(l);
100 if (r < 0) {
9eb977db 101 strv_free(l);
0f474365 102 return r;
9eb977db
KS
103 }
104
0f474365
LP
105 *ret = l;
106 return r;
9eb977db
KS
107}
108
109char *path_make_absolute(const char *p, const char *prefix) {
110 assert(p);
111
112 /* Makes every item in the list an absolute path by prepending
113 * the prefix, if specified and necessary */
114
115 if (path_is_absolute(p) || !prefix)
116 return strdup(p);
117
b7def684 118 return strjoin(prefix, "/", p, NULL);
9eb977db
KS
119}
120
0f474365
LP
121int path_make_absolute_cwd(const char *p, char **ret) {
122 char *c;
9eb977db
KS
123
124 assert(p);
0f474365 125 assert(ret);
9eb977db
KS
126
127 /* Similar to path_make_absolute(), but prefixes with the
128 * current working directory. */
129
130 if (path_is_absolute(p))
0f474365
LP
131 c = strdup(p);
132 else {
133 _cleanup_free_ char *cwd = NULL;
9eb977db 134
0f474365
LP
135 cwd = get_current_dir_name();
136 if (!cwd)
137 return -errno;
138
139 c = strjoin(cwd, "/", p, NULL);
140 }
141 if (!c)
142 return -ENOMEM;
9eb977db 143
0f474365
LP
144 *ret = c;
145 return 0;
9eb977db
KS
146}
147
7cb9c51c
TK
148int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
149 char *r, *p;
150 unsigned n_parents;
7cb9c51c
TK
151
152 assert(from_dir);
153 assert(to_path);
154 assert(_r);
155
156 /* Strips the common part, and adds ".." elements as necessary. */
157
158 if (!path_is_absolute(from_dir))
159 return -EINVAL;
160
161 if (!path_is_absolute(to_path))
162 return -EINVAL;
163
164 /* Skip the common part. */
165 for (;;) {
166 size_t a;
167 size_t b;
168
169 from_dir += strspn(from_dir, "/");
170 to_path += strspn(to_path, "/");
171
172 if (!*from_dir) {
173 if (!*to_path)
174 /* from_dir equals to_path. */
175 r = strdup(".");
176 else
177 /* from_dir is a parent directory of to_path. */
178 r = strdup(to_path);
179
180 if (!r)
181 return -ENOMEM;
182
5216f599
TK
183 path_kill_slashes(r);
184
7cb9c51c
TK
185 *_r = r;
186 return 0;
187 }
188
189 if (!*to_path)
190 break;
191
192 a = strcspn(from_dir, "/");
193 b = strcspn(to_path, "/");
194
195 if (a != b)
196 break;
197
198 if (memcmp(from_dir, to_path, a) != 0)
199 break;
200
201 from_dir += a;
202 to_path += b;
203 }
204
205 /* If we're here, then "from_dir" has one or more elements that need to
206 * be replaced with "..". */
207
208 /* Count the number of necessary ".." elements. */
209 for (n_parents = 0;;) {
210 from_dir += strspn(from_dir, "/");
211
212 if (!*from_dir)
213 break;
214
215 from_dir += strcspn(from_dir, "/");
216 n_parents++;
217 }
218
5216f599 219 r = malloc(n_parents * 3 + strlen(to_path) + 1);
7cb9c51c
TK
220 if (!r)
221 return -ENOMEM;
222
223 for (p = r; n_parents > 0; n_parents--, p += 3)
224 memcpy(p, "../", 3);
225
5216f599
TK
226 strcpy(p, to_path);
227 path_kill_slashes(r);
7cb9c51c
TK
228
229 *_r = r;
230 return 0;
231}
232
0f474365 233int path_strv_make_absolute_cwd(char **l) {
9eb977db 234 char **s;
0f474365 235 int r;
9eb977db
KS
236
237 /* Goes through every item in the string list and makes it
238 * absolute. This works in place and won't rollback any
239 * changes on failure. */
240
241 STRV_FOREACH(s, l) {
242 char *t;
243
0f474365
LP
244 r = path_make_absolute_cwd(*s, &t);
245 if (r < 0)
246 return r;
9eb977db
KS
247
248 free(*s);
249 *s = t;
250 }
251
0f474365 252 return 0;
9eb977db
KS
253}
254
7d8da2c9 255char **path_strv_resolve(char **l, const char *prefix) {
9eb977db
KS
256 char **s;
257 unsigned k = 0;
258 bool enomem = false;
259
260 if (strv_isempty(l))
261 return l;
262
263 /* Goes through every item in the string list and canonicalize
264 * the path. This works in place and won't rollback any
265 * changes on failure. */
266
267 STRV_FOREACH(s, l) {
268 char *t, *u;
12ed81d9 269 _cleanup_free_ char *orig = NULL;
9eb977db 270
12ed81d9
ZJS
271 if (!path_is_absolute(*s)) {
272 free(*s);
9eb977db 273 continue;
12ed81d9 274 }
112cfb18
MM
275
276 if (prefix) {
12ed81d9
ZJS
277 orig = *s;
278 t = strappend(prefix, orig);
112cfb18
MM
279 if (!t) {
280 enomem = true;
281 continue;
282 }
12ed81d9 283 } else
112cfb18 284 t = *s;
9eb977db
KS
285
286 errno = 0;
287 u = canonicalize_file_name(t);
9eb977db 288 if (!u) {
12ed81d9
ZJS
289 if (errno == ENOENT) {
290 if (prefix) {
291 u = orig;
292 orig = NULL;
293 free(t);
294 } else
295 u = t;
296 } else {
874310b7 297 free(t);
112cfb18 298 if (errno == ENOMEM || errno == 0)
874310b7
ZJS
299 enomem = true;
300
301 continue;
302 }
12ed81d9
ZJS
303 } else if (prefix) {
304 char *x;
305
306 free(t);
307 x = path_startswith(u, prefix);
308 if (x) {
309 /* restore the slash if it was lost */
310 if (!startswith(x, "/"))
311 *(--x) = '/';
312
313 t = strdup(x);
314 free(u);
315 if (!t) {
316 enomem = true;
317 continue;
318 }
319 u = t;
320 } else {
321 /* canonicalized path goes outside of
322 * prefix, keep the original path instead */
3542eac7 323 free(u);
12ed81d9
ZJS
324 u = orig;
325 orig = NULL;
326 }
91a6489d
LP
327 } else
328 free(t);
9eb977db
KS
329
330 l[k++] = u;
331 }
332
333 l[k] = NULL;
334
335 if (enomem)
336 return NULL;
337
338 return l;
339}
340
7d8da2c9 341char **path_strv_resolve_uniq(char **l, const char *prefix) {
112cfb18 342
fabe5c0e
LP
343 if (strv_isempty(l))
344 return l;
345
7d8da2c9 346 if (!path_strv_resolve(l, prefix))
fabe5c0e
LP
347 return NULL;
348
349 return strv_uniq(l);
350}
351
9eb977db
KS
352char *path_kill_slashes(char *path) {
353 char *f, *t;
354 bool slash = false;
355
356 /* Removes redundant inner and trailing slashes. Modifies the
357 * passed string in-place.
358 *
359 * ///foo///bar/ becomes /foo/bar
360 */
361
362 for (f = path, t = path; *f; f++) {
363
364 if (*f == '/') {
365 slash = true;
366 continue;
367 }
368
369 if (slash) {
370 slash = false;
371 *(t++) = '/';
372 }
373
374 *(t++) = *f;
375 }
376
377 /* Special rule, if we are talking of the root directory, a
378 trailing slash is good */
379
380 if (t == path && slash)
381 *(t++) = '/';
382
383 *t = 0;
384 return path;
385}
386
424a19f8 387char* path_startswith(const char *path, const char *prefix) {
9eb977db
KS
388 assert(path);
389 assert(prefix);
390
391 if ((path[0] == '/') != (prefix[0] == '/'))
424a19f8 392 return NULL;
9eb977db
KS
393
394 for (;;) {
395 size_t a, b;
396
397 path += strspn(path, "/");
398 prefix += strspn(prefix, "/");
399
400 if (*prefix == 0)
424a19f8 401 return (char*) path;
9eb977db
KS
402
403 if (*path == 0)
424a19f8 404 return NULL;
9eb977db
KS
405
406 a = strcspn(path, "/");
407 b = strcspn(prefix, "/");
408
409 if (a != b)
424a19f8 410 return NULL;
9eb977db
KS
411
412 if (memcmp(path, prefix, a) != 0)
424a19f8 413 return NULL;
9eb977db
KS
414
415 path += a;
416 prefix += b;
417 }
418}
419
2230852b
MS
420int path_compare(const char *a, const char *b) {
421 int d;
422
9eb977db
KS
423 assert(a);
424 assert(b);
425
2230852b
MS
426 /* A relative path and an abolute path must not compare as equal.
427 * Which one is sorted before the other does not really matter.
428 * Here a relative path is ordered before an absolute path. */
429 d = (a[0] == '/') - (b[0] == '/');
430 if (d)
431 return d;
9eb977db
KS
432
433 for (;;) {
434 size_t j, k;
435
436 a += strspn(a, "/");
437 b += strspn(b, "/");
438
439 if (*a == 0 && *b == 0)
2230852b 440 return 0;
9eb977db 441
2230852b
MS
442 /* Order prefixes first: "/foo" before "/foo/bar" */
443 if (*a == 0)
444 return -1;
445 if (*b == 0)
446 return 1;
9eb977db
KS
447
448 j = strcspn(a, "/");
449 k = strcspn(b, "/");
450
2230852b
MS
451 /* Alphabetical sort: "/foo/aaa" before "/foo/b" */
452 d = memcmp(a, b, MIN(j, k));
453 if (d)
454 return (d > 0) - (d < 0); /* sign of d */
9eb977db 455
2230852b
MS
456 /* Sort "/foo/a" before "/foo/aaa" */
457 d = (j > k) - (j < k); /* sign of (j - k) */
458 if (d)
459 return d;
9eb977db
KS
460
461 a += j;
462 b += k;
463 }
464}
465
2230852b
MS
466bool path_equal(const char *a, const char *b) {
467 return path_compare(a, b) == 0;
468}
469
c78e47a6
MS
470bool path_equal_or_files_same(const char *a, const char *b) {
471 return path_equal(a, b) || files_same(a, b) > 0;
472}
473
0c6ea3a4
ZJS
474char* path_join(const char *root, const char *path, const char *rest) {
475 assert(path);
476
477 if (!isempty(root))
bc854dc7 478 return strjoin(root, endswith(root, "/") ? "" : "/",
0c6ea3a4 479 path[0] == '/' ? path+1 : path,
bc854dc7 480 rest ? (endswith(path, "/") ? "" : "/") : NULL,
0c6ea3a4
ZJS
481 rest && rest[0] == '/' ? rest+1 : rest,
482 NULL);
483 else
484 return strjoin(path,
bc854dc7 485 rest ? (endswith(path, "/") ? "" : "/") : NULL,
0c6ea3a4
ZJS
486 rest && rest[0] == '/' ? rest+1 : rest,
487 NULL);
488}
489
3f72b427
LP
490static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) {
491 char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
492 _cleanup_free_ char *fdinfo = NULL;
493 _cleanup_close_ int subfd = -1;
494 char *p;
495 int r;
496
497 if ((flags & AT_EMPTY_PATH) && isempty(filename))
498 xsprintf(path, "/proc/self/fdinfo/%i", fd);
499 else {
500 subfd = openat(fd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH);
501 if (subfd < 0)
502 return -errno;
503
504 xsprintf(path, "/proc/self/fdinfo/%i", subfd);
505 }
506
507 r = read_full_file(path, &fdinfo, NULL);
508 if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */
509 return -EOPNOTSUPP;
510 if (r < 0)
511 return -errno;
512
513 p = startswith(fdinfo, "mnt_id:");
514 if (!p) {
515 p = strstr(fdinfo, "\nmnt_id:");
516 if (!p) /* The mnt_id field is a relatively new addition */
517 return -EOPNOTSUPP;
518
519 p += 8;
520 }
521
522 p += strspn(p, WHITESPACE);
523 p[strcspn(p, WHITESPACE)] = 0;
524
525 return safe_atoi(p, mnt_id);
526}
527
5d409034 528int fd_is_mount_point(int fd, const char *filename, int flags) {
05d990ef 529 union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT;
e40872fc 530 int mount_id = -1, mount_id_parent = -1;
84317788 531 bool nosupp = false, check_st_dev = true;
1640a0b6 532 struct stat a, b;
21749924 533 int r;
9eb977db 534
05d990ef 535 assert(fd >= 0);
5d409034 536 assert(filename);
05d990ef 537
3f72b427
LP
538 /* First we will try the name_to_handle_at() syscall, which
539 * tells us the mount id and an opaque file "handle". It is
540 * not supported everywhere though (kernel compile-time
541 * option, not all file systems are hooked up). If it works
542 * the mount id is usually good enough to tell us whether
543 * something is a mount point.
544 *
545 * If that didn't work we will try to read the mount id from
546 * /proc/self/fdinfo/<fd>. This is almost as good as
af86c440 547 * name_to_handle_at(), however, does not return the
3f72b427
LP
548 * opaque file handle. The opaque file handle is pretty useful
549 * to detect the root directory, which we should always
550 * consider a mount point. Hence we use this only as
551 * fallback. Exporting the mnt_id in fdinfo is a pretty recent
552 * kernel addition.
553 *
554 * As last fallback we do traditional fstat() based st_dev
555 * comparisons. This is how things were traditionally done,
556 * but unionfs breaks breaks this since it exposes file
557 * systems with a variety of st_dev reported. Also, btrfs
558 * subvolumes have different st_dev, even though they aren't
559 * real mounts of their own. */
cde9cb34 560
5d409034 561 r = name_to_handle_at(fd, filename, &h.handle, &mount_id, flags);
9eb977db 562 if (r < 0) {
e40872fc
DR
563 if (errno == ENOSYS)
564 /* This kernel does not support name_to_handle_at()
3f72b427
LP
565 * fall back to simpler logic. */
566 goto fallback_fdinfo;
e40872fc 567 else if (errno == EOPNOTSUPP)
fa125f4e 568 /* This kernel or file system does not support
f25afeb6
LP
569 * name_to_handle_at(), hence let's see if the
570 * upper fs supports it (in which case it is a
571 * mount point), otherwise fallback to the
1640a0b6 572 * traditional stat() logic */
6feeeab0 573 nosupp = true;
6feeeab0
ZJS
574 else
575 return -errno;
9eb977db
KS
576 }
577
5d409034 578 r = name_to_handle_at(fd, "", &h_parent.handle, &mount_id_parent, AT_EMPTY_PATH);
f25afeb6
LP
579 if (r < 0) {
580 if (errno == EOPNOTSUPP) {
6feeeab0
ZJS
581 if (nosupp)
582 /* Neither parent nor child do name_to_handle_at()?
583 We have no choice but to fall back. */
3f72b427 584 goto fallback_fdinfo;
6feeeab0 585 else
f25afeb6
LP
586 /* The parent can't do name_to_handle_at() but the
587 * directory we are interested in can?
6feeeab0
ZJS
588 * If so, it must be a mount point. */
589 return 1;
f25afeb6 590 } else
6feeeab0 591 return -errno;
3f72b427
LP
592 }
593
594 /* The parent can do name_to_handle_at() but the
595 * directory we are interested in can't? If so, it
596 * must be a mount point. */
597 if (nosupp)
8f06b239 598 return 1;
05d990ef 599
3f72b427
LP
600 /* If the file handle for the directory we are
601 * interested in and its parent are identical, we
602 * assume this is the root directory, which is a mount
603 * point. */
604
605 if (h.handle.handle_bytes == h_parent.handle.handle_bytes &&
606 h.handle.handle_type == h_parent.handle.handle_type &&
607 memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0)
608 return 1;
1640a0b6 609
3f72b427
LP
610 return mount_id != mount_id_parent;
611
612fallback_fdinfo:
5d409034 613 r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id);
3f72b427
LP
614 if (r == -EOPNOTSUPP)
615 goto fallback_fstat;
e792e890 616 if (r < 0)
3f72b427 617 return r;
9eb977db 618
5d409034 619 r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent);
1640a0b6 620 if (r < 0)
3f72b427
LP
621 return r;
622
623 if (mount_id != mount_id_parent)
624 return 1;
625
626 /* Hmm, so, the mount ids are the same. This leaves one
627 * special case though for the root file system. For that,
628 * let's see if the parent directory has the same inode as we
629 * are interested in. Hence, let's also do fstat() checks now,
630 * too, but avoid the st_dev comparisons, since they aren't
631 * that useful on unionfs mounts. */
632 check_st_dev = false;
633
634fallback_fstat:
5d409034
MP
635 /* yay for fstatat() taking a different set of flags than the other
636 * _at() above */
637 if (flags & AT_SYMLINK_FOLLOW)
638 flags &= ~AT_SYMLINK_FOLLOW;
639 else
640 flags |= AT_SYMLINK_NOFOLLOW;
641 if (fstatat(fd, filename, &a, flags) < 0)
3f72b427
LP
642 return -errno;
643
5d409034 644 if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0)
1640a0b6
LP
645 return -errno;
646
05d990ef
LP
647 /* A directory with same device and inode as its parent? Must
648 * be the root directory */
649 if (a.st_dev == b.st_dev &&
650 a.st_ino == b.st_ino)
651 return 1;
652
3f72b427 653 return check_st_dev && (a.st_dev != b.st_dev);
9eb977db
KS
654}
655
e26d6ce5
MP
656/* flags can be AT_SYMLINK_FOLLOW or 0 */
657int path_is_mount_point(const char *t, int flags) {
f25afeb6 658 _cleanup_close_ int fd = -1;
36908eb8 659 _cleanup_free_ char *canonical = NULL, *parent = NULL;
5d409034 660 int r;
e792e890 661
f25afeb6
LP
662 assert(t);
663
664 if (path_equal(t, "/"))
665 return 1;
666
36908eb8
MP
667 /* we need to resolve symlinks manually, we can't just rely on
668 * fd_is_mount_point() to do that for us; if we have a structure like
669 * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we
670 * look at needs to be /usr, not /. */
671 if (flags & AT_SYMLINK_FOLLOW) {
672 canonical = canonicalize_file_name(t);
673 if (!canonical)
674 return -errno;
10c03e9e
LP
675
676 t = canonical;
36908eb8
MP
677 }
678
10c03e9e 679 r = path_get_parent(t, &parent);
5d409034
MP
680 if (r < 0)
681 return r;
682
683 fd = openat(AT_FDCWD, parent, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_PATH);
e792e890 684 if (fd < 0)
f25afeb6 685 return -errno;
f25afeb6 686
10c03e9e 687 return fd_is_mount_point(fd, basename(t), flags);
f25afeb6
LP
688}
689
9eb977db
KS
690int path_is_read_only_fs(const char *path) {
691 struct statvfs st;
692
693 assert(path);
694
695 if (statvfs(path, &st) < 0)
696 return -errno;
697
70421bdc
LP
698 if (st.f_flag & ST_RDONLY)
699 return true;
700
701 /* On NFS, statvfs() might not reflect whether we can actually
702 * write to the remote share. Let's try again with
703 * access(W_OK) which is more reliable, at least sometimes. */
704 if (access(path, W_OK) < 0 && errno == EROFS)
705 return true;
706
707 return false;
9eb977db 708}
66060897
LP
709
710int path_is_os_tree(const char *path) {
711 char *p;
712 int r;
713
5ae4d543 714 /* We use /usr/lib/os-release as flag file if something is an OS */
63c372cb 715 p = strjoina(path, "/usr/lib/os-release");
5ae4d543 716 r = access(p, F_OK);
5ae4d543
LP
717 if (r >= 0)
718 return 1;
66060897 719
5ae4d543 720 /* Also check for the old location in /etc, just in case. */
63c372cb 721 p = strjoina(path, "/etc/os-release");
66060897
LP
722 r = access(p, F_OK);
723
5ae4d543 724 return r >= 0;
66060897 725}
c9d954b2 726
85eca92e
LP
727int find_binary(const char *name, char **ret) {
728 int last_error, r;
729 const char *p;
730
c9d954b2 731 assert(name);
4087cb9e 732
571d0134 733 if (is_path(name)) {
85eca92e 734 if (access(name, X_OK) < 0)
b972115c
ZJS
735 return -errno;
736
85eca92e 737 if (ret) {
0f474365
LP
738 r = path_make_absolute_cwd(name, ret);
739 if (r < 0)
740 return r;
eb66db55 741 }
c9d954b2 742
c9d954b2 743 return 0;
85eca92e 744 }
c9d954b2 745
85eca92e
LP
746 /**
747 * Plain getenv, not secure_getenv, because we want
748 * to actually allow the user to pick the binary.
749 */
750 p = getenv("PATH");
751 if (!p)
752 p = DEFAULT_PATH;
753
754 last_error = -ENOENT;
755
756 for (;;) {
757 _cleanup_free_ char *j = NULL, *element = NULL;
758
759 r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS);
760 if (r < 0)
761 return r;
762 if (r == 0)
763 break;
764
0f474365
LP
765 if (!path_is_absolute(element))
766 continue;
767
85eca92e
LP
768 j = strjoin(element, "/", name, NULL);
769 if (!j)
770 return -ENOMEM;
771
772 if (access(j, X_OK) >= 0) {
773 /* Found it! */
c9d954b2 774
85eca92e
LP
775 if (ret) {
776 *ret = path_kill_slashes(j);
777 j = NULL;
eb66db55 778 }
c9d954b2
ZJS
779
780 return 0;
781 }
782
85eca92e 783 last_error = -errno;
c9d954b2 784 }
85eca92e
LP
785
786 return last_error;
c9d954b2 787}
8e184852 788
2ad8416d 789bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
8e184852 790 bool changed = false;
2ad8416d 791 const char* const* i;
8e184852 792
97f2d76d
TG
793 assert(timestamp);
794
8e184852 795 if (paths == NULL)
4087cb9e 796 return false;
8e184852 797
4087cb9e 798 STRV_FOREACH(i, paths) {
8e184852 799 struct stat stats;
4087cb9e 800 usec_t u;
8e184852 801
4087cb9e 802 if (stat(*i, &stats) < 0)
8e184852
TG
803 continue;
804
4087cb9e
LP
805 u = timespec_load(&stats.st_mtim);
806
97f2d76d 807 /* first check */
4087cb9e 808 if (*timestamp >= u)
8e184852
TG
809 continue;
810
9f6445e3 811 log_debug("timestamp of '%s' changed", *i);
8e184852
TG
812
813 /* update timestamp */
4087cb9e
LP
814 if (update) {
815 *timestamp = u;
816 changed = true;
817 } else
818 return true;
8e184852 819 }
4087cb9e 820
8e184852
TG
821 return changed;
822}
eb66db55 823
5bcd08db 824static int binary_is_good(const char *binary) {
571d0134 825 _cleanup_free_ char *p = NULL, *d = NULL;
571d0134 826 int r;
eb66db55 827
85eca92e
LP
828 r = find_binary(binary, &p);
829 if (r == -ENOENT)
830 return 0;
571d0134
LP
831 if (r < 0)
832 return r;
833
061df014 834 /* An fsck that is linked to /bin/true is a non-existent
571d0134
LP
835 * fsck */
836
837 r = readlink_malloc(p, &d);
85eca92e
LP
838 if (r == -EINVAL) /* not a symlink */
839 return 1;
840 if (r < 0)
841 return r;
571d0134 842
85eca92e
LP
843 return !path_equal(d, "true") &&
844 !path_equal(d, "/bin/true") &&
845 !path_equal(d, "/usr/bin/true") &&
846 !path_equal(d, "/dev/null");
eb66db55 847}
1d13f648 848
5bcd08db
LP
849int fsck_exists(const char *fstype) {
850 const char *checker;
851
85eca92e 852 assert(fstype);
5bcd08db 853
85eca92e
LP
854 if (streq(fstype, "auto"))
855 return -EINVAL;
856
857 checker = strjoina("fsck.", fstype);
5bcd08db
LP
858 return binary_is_good(checker);
859}
860
861int mkfs_exists(const char *fstype) {
862 const char *mkfs;
863
85eca92e 864 assert(fstype);
5bcd08db 865
85eca92e
LP
866 if (streq(fstype, "auto"))
867 return -EINVAL;
868
869 mkfs = strjoina("mkfs.", fstype);
5bcd08db
LP
870 return binary_is_good(mkfs);
871}
872
1d13f648
LP
873char *prefix_root(const char *root, const char *path) {
874 char *n, *p;
875 size_t l;
876
877 /* If root is passed, prefixes path with it. Otherwise returns
878 * it as is. */
879
880 assert(path);
881
882 /* First, drop duplicate prefixing slashes from the path */
883 while (path[0] == '/' && path[1] == '/')
884 path++;
885
886 if (isempty(root) || path_equal(root, "/"))
887 return strdup(path);
888
889 l = strlen(root) + 1 + strlen(path) + 1;
890
891 n = new(char, l);
892 if (!n)
893 return NULL;
894
895 p = stpcpy(n, root);
896
897 while (p > n && p[-1] == '/')
898 p--;
899
900 if (path[0] != '/')
901 *(p++) = '/';
902
903 strcpy(p, path);
904 return n;
905}
0f03c2a4
LP
906
907int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) {
908 char *p;
909 int r;
910
911 /*
912 * This function is intended to be used in command line
913 * parsers, to handle paths that are passed in. It makes the
914 * path absolute, and reduces it to NULL if omitted or
915 * root (the latter optionally).
916 *
917 * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON
918 * SUCCESS! Hence, do not pass in uninitialized pointers.
919 */
920
921 if (isempty(path)) {
922 *arg = mfree(*arg);
923 return 0;
924 }
925
926 r = path_make_absolute_cwd(path, &p);
927 if (r < 0)
928 return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path);
929
930 path_kill_slashes(p);
931 if (suppress_root && path_equal(p, "/"))
932 p = mfree(p);
933
934 free(*arg);
935 *arg = p;
936 return 0;
937}