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