]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/path-util.c
path-util: make sure fd_is_mount_point() returns true for root directory
[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"
9eb977db
KS
36
37bool path_is_absolute(const char *p) {
38 return p[0] == '/';
39}
40
41bool is_path(const char *p) {
42 return !!strchr(p, '/');
43}
44
9eb977db
KS
45int path_get_parent(const char *path, char **_r) {
46 const char *e, *a = NULL, *b = NULL, *p;
47 char *r;
48 bool slash = false;
49
50 assert(path);
51 assert(_r);
52
53 if (!*path)
54 return -EINVAL;
55
56 for (e = path; *e; e++) {
57
58 if (!slash && *e == '/') {
59 a = b;
60 b = e;
61 slash = true;
62 } else if (slash && *e != '/')
63 slash = false;
64 }
65
66 if (*(e-1) == '/')
67 p = a;
68 else
69 p = b;
70
71 if (!p)
72 return -EINVAL;
73
74 if (p == path)
75 r = strdup("/");
76 else
77 r = strndup(path, p-path);
78
79 if (!r)
80 return -ENOMEM;
81
82 *_r = r;
83 return 0;
84}
85
86char **path_split_and_make_absolute(const char *p) {
87 char **l;
88 assert(p);
89
116cc028
ZJS
90 l = strv_split(p, ":");
91 if (!l)
9eb977db
KS
92 return NULL;
93
94 if (!path_strv_make_absolute_cwd(l)) {
95 strv_free(l);
96 return NULL;
97 }
98
99 return l;
100}
101
102char *path_make_absolute(const char *p, const char *prefix) {
103 assert(p);
104
105 /* Makes every item in the list an absolute path by prepending
106 * the prefix, if specified and necessary */
107
108 if (path_is_absolute(p) || !prefix)
109 return strdup(p);
110
b7def684 111 return strjoin(prefix, "/", p, NULL);
9eb977db
KS
112}
113
114char *path_make_absolute_cwd(const char *p) {
116cc028 115 _cleanup_free_ char *cwd = NULL;
9eb977db
KS
116
117 assert(p);
118
119 /* Similar to path_make_absolute(), but prefixes with the
120 * current working directory. */
121
122 if (path_is_absolute(p))
123 return strdup(p);
124
91a6489d
LP
125 cwd = get_current_dir_name();
126 if (!cwd)
9eb977db
KS
127 return NULL;
128
db594aef 129 return strjoin(cwd, "/", p, NULL);
9eb977db
KS
130}
131
7cb9c51c
TK
132int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
133 char *r, *p;
134 unsigned n_parents;
7cb9c51c
TK
135
136 assert(from_dir);
137 assert(to_path);
138 assert(_r);
139
140 /* Strips the common part, and adds ".." elements as necessary. */
141
142 if (!path_is_absolute(from_dir))
143 return -EINVAL;
144
145 if (!path_is_absolute(to_path))
146 return -EINVAL;
147
148 /* Skip the common part. */
149 for (;;) {
150 size_t a;
151 size_t b;
152
153 from_dir += strspn(from_dir, "/");
154 to_path += strspn(to_path, "/");
155
156 if (!*from_dir) {
157 if (!*to_path)
158 /* from_dir equals to_path. */
159 r = strdup(".");
160 else
161 /* from_dir is a parent directory of to_path. */
162 r = strdup(to_path);
163
164 if (!r)
165 return -ENOMEM;
166
5216f599
TK
167 path_kill_slashes(r);
168
7cb9c51c
TK
169 *_r = r;
170 return 0;
171 }
172
173 if (!*to_path)
174 break;
175
176 a = strcspn(from_dir, "/");
177 b = strcspn(to_path, "/");
178
179 if (a != b)
180 break;
181
182 if (memcmp(from_dir, to_path, a) != 0)
183 break;
184
185 from_dir += a;
186 to_path += b;
187 }
188
189 /* If we're here, then "from_dir" has one or more elements that need to
190 * be replaced with "..". */
191
192 /* Count the number of necessary ".." elements. */
193 for (n_parents = 0;;) {
194 from_dir += strspn(from_dir, "/");
195
196 if (!*from_dir)
197 break;
198
199 from_dir += strcspn(from_dir, "/");
200 n_parents++;
201 }
202
5216f599 203 r = malloc(n_parents * 3 + strlen(to_path) + 1);
7cb9c51c
TK
204 if (!r)
205 return -ENOMEM;
206
207 for (p = r; n_parents > 0; n_parents--, p += 3)
208 memcpy(p, "../", 3);
209
5216f599
TK
210 strcpy(p, to_path);
211 path_kill_slashes(r);
7cb9c51c
TK
212
213 *_r = r;
214 return 0;
215}
216
9eb977db
KS
217char **path_strv_make_absolute_cwd(char **l) {
218 char **s;
219
220 /* Goes through every item in the string list and makes it
221 * absolute. This works in place and won't rollback any
222 * changes on failure. */
223
224 STRV_FOREACH(s, l) {
225 char *t;
226
116cc028
ZJS
227 t = path_make_absolute_cwd(*s);
228 if (!t)
9eb977db
KS
229 return NULL;
230
231 free(*s);
232 *s = t;
233 }
234
235 return l;
236}
237
7d8da2c9 238char **path_strv_resolve(char **l, const char *prefix) {
9eb977db
KS
239 char **s;
240 unsigned k = 0;
241 bool enomem = false;
242
243 if (strv_isempty(l))
244 return l;
245
246 /* Goes through every item in the string list and canonicalize
247 * the path. This works in place and won't rollback any
248 * changes on failure. */
249
250 STRV_FOREACH(s, l) {
251 char *t, *u;
12ed81d9 252 _cleanup_free_ char *orig = NULL;
9eb977db 253
12ed81d9
ZJS
254 if (!path_is_absolute(*s)) {
255 free(*s);
9eb977db 256 continue;
12ed81d9 257 }
112cfb18
MM
258
259 if (prefix) {
12ed81d9
ZJS
260 orig = *s;
261 t = strappend(prefix, orig);
112cfb18
MM
262 if (!t) {
263 enomem = true;
264 continue;
265 }
12ed81d9 266 } else
112cfb18 267 t = *s;
9eb977db
KS
268
269 errno = 0;
270 u = canonicalize_file_name(t);
9eb977db 271 if (!u) {
12ed81d9
ZJS
272 if (errno == ENOENT) {
273 if (prefix) {
274 u = orig;
275 orig = NULL;
276 free(t);
277 } else
278 u = t;
279 } else {
874310b7 280 free(t);
112cfb18 281 if (errno == ENOMEM || errno == 0)
874310b7
ZJS
282 enomem = true;
283
284 continue;
285 }
12ed81d9
ZJS
286 } else if (prefix) {
287 char *x;
288
289 free(t);
290 x = path_startswith(u, prefix);
291 if (x) {
292 /* restore the slash if it was lost */
293 if (!startswith(x, "/"))
294 *(--x) = '/';
295
296 t = strdup(x);
297 free(u);
298 if (!t) {
299 enomem = true;
300 continue;
301 }
302 u = t;
303 } else {
304 /* canonicalized path goes outside of
305 * prefix, keep the original path instead */
3542eac7 306 free(u);
12ed81d9
ZJS
307 u = orig;
308 orig = NULL;
309 }
91a6489d
LP
310 } else
311 free(t);
9eb977db
KS
312
313 l[k++] = u;
314 }
315
316 l[k] = NULL;
317
318 if (enomem)
319 return NULL;
320
321 return l;
322}
323
7d8da2c9 324char **path_strv_resolve_uniq(char **l, const char *prefix) {
112cfb18 325
fabe5c0e
LP
326 if (strv_isempty(l))
327 return l;
328
7d8da2c9 329 if (!path_strv_resolve(l, prefix))
fabe5c0e
LP
330 return NULL;
331
332 return strv_uniq(l);
333}
334
9eb977db
KS
335char *path_kill_slashes(char *path) {
336 char *f, *t;
337 bool slash = false;
338
339 /* Removes redundant inner and trailing slashes. Modifies the
340 * passed string in-place.
341 *
342 * ///foo///bar/ becomes /foo/bar
343 */
344
345 for (f = path, t = path; *f; f++) {
346
347 if (*f == '/') {
348 slash = true;
349 continue;
350 }
351
352 if (slash) {
353 slash = false;
354 *(t++) = '/';
355 }
356
357 *(t++) = *f;
358 }
359
360 /* Special rule, if we are talking of the root directory, a
361 trailing slash is good */
362
363 if (t == path && slash)
364 *(t++) = '/';
365
366 *t = 0;
367 return path;
368}
369
424a19f8 370char* path_startswith(const char *path, const char *prefix) {
9eb977db
KS
371 assert(path);
372 assert(prefix);
373
374 if ((path[0] == '/') != (prefix[0] == '/'))
424a19f8 375 return NULL;
9eb977db
KS
376
377 for (;;) {
378 size_t a, b;
379
380 path += strspn(path, "/");
381 prefix += strspn(prefix, "/");
382
383 if (*prefix == 0)
424a19f8 384 return (char*) path;
9eb977db
KS
385
386 if (*path == 0)
424a19f8 387 return NULL;
9eb977db
KS
388
389 a = strcspn(path, "/");
390 b = strcspn(prefix, "/");
391
392 if (a != b)
424a19f8 393 return NULL;
9eb977db
KS
394
395 if (memcmp(path, prefix, a) != 0)
424a19f8 396 return NULL;
9eb977db
KS
397
398 path += a;
399 prefix += b;
400 }
401}
402
2230852b
MS
403int path_compare(const char *a, const char *b) {
404 int d;
405
9eb977db
KS
406 assert(a);
407 assert(b);
408
2230852b
MS
409 /* A relative path and an abolute path must not compare as equal.
410 * Which one is sorted before the other does not really matter.
411 * Here a relative path is ordered before an absolute path. */
412 d = (a[0] == '/') - (b[0] == '/');
413 if (d)
414 return d;
9eb977db
KS
415
416 for (;;) {
417 size_t j, k;
418
419 a += strspn(a, "/");
420 b += strspn(b, "/");
421
422 if (*a == 0 && *b == 0)
2230852b 423 return 0;
9eb977db 424
2230852b
MS
425 /* Order prefixes first: "/foo" before "/foo/bar" */
426 if (*a == 0)
427 return -1;
428 if (*b == 0)
429 return 1;
9eb977db
KS
430
431 j = strcspn(a, "/");
432 k = strcspn(b, "/");
433
2230852b
MS
434 /* Alphabetical sort: "/foo/aaa" before "/foo/b" */
435 d = memcmp(a, b, MIN(j, k));
436 if (d)
437 return (d > 0) - (d < 0); /* sign of d */
9eb977db 438
2230852b
MS
439 /* Sort "/foo/a" before "/foo/aaa" */
440 d = (j > k) - (j < k); /* sign of (j - k) */
441 if (d)
442 return d;
9eb977db
KS
443
444 a += j;
445 b += k;
446 }
447}
448
2230852b
MS
449bool path_equal(const char *a, const char *b) {
450 return path_compare(a, b) == 0;
451}
452
c78e47a6
MS
453bool path_equal_or_files_same(const char *a, const char *b) {
454 return path_equal(a, b) || files_same(a, b) > 0;
455}
456
0c6ea3a4
ZJS
457char* path_join(const char *root, const char *path, const char *rest) {
458 assert(path);
459
460 if (!isempty(root))
bc854dc7 461 return strjoin(root, endswith(root, "/") ? "" : "/",
0c6ea3a4 462 path[0] == '/' ? path+1 : path,
bc854dc7 463 rest ? (endswith(path, "/") ? "" : "/") : NULL,
0c6ea3a4
ZJS
464 rest && rest[0] == '/' ? rest+1 : rest,
465 NULL);
466 else
467 return strjoin(path,
bc854dc7 468 rest ? (endswith(path, "/") ? "" : "/") : NULL,
0c6ea3a4
ZJS
469 rest && rest[0] == '/' ? rest+1 : rest,
470 NULL);
471}
472
f25afeb6 473int fd_is_mount_point(int fd) {
05d990ef 474 union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT;
e40872fc 475 int mount_id = -1, mount_id_parent = -1;
f25afeb6 476 bool nosupp = false;
1640a0b6 477 struct stat a, b;
21749924 478 int r;
9eb977db 479
05d990ef
LP
480 assert(fd >= 0);
481
cde9cb34
LP
482 /* We are not actually interested in the file handles, but
483 * name_to_handle_at() also passes us the mount ID, hence use
484 * it but throw the handle away */
485
27cc6f16 486 r = name_to_handle_at(fd, "", &h.handle, &mount_id, AT_EMPTY_PATH);
9eb977db 487 if (r < 0) {
e40872fc
DR
488 if (errno == ENOSYS)
489 /* This kernel does not support name_to_handle_at()
490 * fall back to the traditional stat() logic. */
491 goto fallback;
492 else if (errno == EOPNOTSUPP)
fa125f4e 493 /* This kernel or file system does not support
f25afeb6
LP
494 * name_to_handle_at(), hence let's see if the
495 * upper fs supports it (in which case it is a
496 * mount point), otherwise fallback to the
1640a0b6 497 * traditional stat() logic */
6feeeab0
ZJS
498 nosupp = true;
499 else if (errno == ENOENT)
9eb977db 500 return 0;
6feeeab0
ZJS
501 else
502 return -errno;
9eb977db
KS
503 }
504
05d990ef 505 r = name_to_handle_at(fd, "..", &h_parent.handle, &mount_id_parent, 0);
f25afeb6
LP
506 if (r < 0) {
507 if (errno == EOPNOTSUPP) {
6feeeab0
ZJS
508 if (nosupp)
509 /* Neither parent nor child do name_to_handle_at()?
510 We have no choice but to fall back. */
511 goto fallback;
512 else
f25afeb6
LP
513 /* The parent can't do name_to_handle_at() but the
514 * directory we are interested in can?
6feeeab0
ZJS
515 * If so, it must be a mount point. */
516 return 1;
f25afeb6 517 } else
6feeeab0 518 return -errno;
8f06b239
LP
519 } else if (nosupp)
520 /* The parent can do name_to_handle_at() but the
521 * directory we are interested in can't? If so, it
522 * must be a mount point. */
523 return 1;
05d990ef
LP
524 else {
525 /* If the file handle for the directory we are
526 * interested in and its parent are identical, we
527 * assume this is the root directory, which is a mount
528 * point. */
529
530 if (h.handle.handle_bytes == h_parent.handle.handle_bytes &&
531 h.handle.handle_type == h_parent.handle.handle_type &&
532 memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0)
533 return 1;
534
6feeeab0 535 return mount_id != mount_id_parent;
05d990ef 536 }
1640a0b6
LP
537
538fallback:
27cc6f16 539 r = fstatat(fd, "", &a, AT_EMPTY_PATH);
8ac75493
MM
540 if (r < 0) {
541 if (errno == ENOENT)
542 return 0;
543
9eb977db 544 return -errno;
8ac75493 545 }
9eb977db 546
27cc6f16 547 r = fstatat(fd, "..", &b, 0);
1640a0b6
LP
548 if (r < 0)
549 return -errno;
550
05d990ef
LP
551 /* A directory with same device and inode as its parent? Must
552 * be the root directory */
553 if (a.st_dev == b.st_dev &&
554 a.st_ino == b.st_ino)
555 return 1;
556
1640a0b6 557 return a.st_dev != b.st_dev;
9eb977db
KS
558}
559
f25afeb6
LP
560int path_is_mount_point(const char *t, bool allow_symlink) {
561 _cleanup_close_ int fd = -1;
562 assert(t);
563
564 if (path_equal(t, "/"))
565 return 1;
566
567 fd = openat(AT_FDCWD, t, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|(allow_symlink ? 0 : O_PATH));
568 if (fd < 0) {
569 if (errno == ENOENT)
570 return 0;
571
572 return -errno;
573 }
574
575 return fd_is_mount_point(fd);
576}
577
9eb977db
KS
578int path_is_read_only_fs(const char *path) {
579 struct statvfs st;
580
581 assert(path);
582
583 if (statvfs(path, &st) < 0)
584 return -errno;
585
70421bdc
LP
586 if (st.f_flag & ST_RDONLY)
587 return true;
588
589 /* On NFS, statvfs() might not reflect whether we can actually
590 * write to the remote share. Let's try again with
591 * access(W_OK) which is more reliable, at least sometimes. */
592 if (access(path, W_OK) < 0 && errno == EROFS)
593 return true;
594
595 return false;
9eb977db 596}
66060897
LP
597
598int path_is_os_tree(const char *path) {
599 char *p;
600 int r;
601
5ae4d543 602 /* We use /usr/lib/os-release as flag file if something is an OS */
63c372cb 603 p = strjoina(path, "/usr/lib/os-release");
5ae4d543
LP
604 r = access(p, F_OK);
605
606 if (r >= 0)
607 return 1;
66060897 608
5ae4d543 609 /* Also check for the old location in /etc, just in case. */
63c372cb 610 p = strjoina(path, "/etc/os-release");
66060897
LP
611 r = access(p, F_OK);
612
5ae4d543 613 return r >= 0;
66060897 614}
c9d954b2 615
b63bd109 616int find_binary(const char *name, bool local, char **filename) {
c9d954b2 617 assert(name);
4087cb9e 618
571d0134 619 if (is_path(name)) {
b63bd109 620 if (local && access(name, X_OK) < 0)
b972115c
ZJS
621 return -errno;
622
eb66db55
MG
623 if (filename) {
624 char *p;
c9d954b2 625
b972115c 626 p = path_make_absolute_cwd(name);
eb66db55
MG
627 if (!p)
628 return -ENOMEM;
b972115c 629
eb66db55
MG
630 *filename = p;
631 }
c9d954b2 632
c9d954b2
ZJS
633 return 0;
634 } else {
635 const char *path;
a2a5291b 636 const char *word, *state;
c9d954b2
ZJS
637 size_t l;
638
639 /**
640 * Plain getenv, not secure_getenv, because we want
641 * to actually allow the user to pick the binary.
642 */
643 path = getenv("PATH");
644 if (!path)
645 path = DEFAULT_PATH;
646
a2a5291b 647 FOREACH_WORD_SEPARATOR(word, l, path, ":", state) {
eb66db55 648 _cleanup_free_ char *p = NULL;
c9d954b2 649
a2a5291b 650 if (asprintf(&p, "%.*s/%s", (int) l, word, name) < 0)
c9d954b2
ZJS
651 return -ENOMEM;
652
eb66db55 653 if (access(p, X_OK) < 0)
c9d954b2 654 continue;
c9d954b2 655
eb66db55 656 if (filename) {
dbb9401d 657 *filename = path_kill_slashes(p);
eb66db55
MG
658 p = NULL;
659 }
c9d954b2
ZJS
660
661 return 0;
662 }
663
664 return -ENOENT;
665 }
666}
8e184852 667
2ad8416d 668bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
8e184852 669 bool changed = false;
2ad8416d 670 const char* const* i;
8e184852 671
97f2d76d
TG
672 assert(timestamp);
673
8e184852 674 if (paths == NULL)
4087cb9e 675 return false;
8e184852 676
4087cb9e 677 STRV_FOREACH(i, paths) {
8e184852 678 struct stat stats;
4087cb9e 679 usec_t u;
8e184852 680
4087cb9e 681 if (stat(*i, &stats) < 0)
8e184852
TG
682 continue;
683
4087cb9e
LP
684 u = timespec_load(&stats.st_mtim);
685
97f2d76d 686 /* first check */
4087cb9e 687 if (*timestamp >= u)
8e184852
TG
688 continue;
689
9f6445e3 690 log_debug("timestamp of '%s' changed", *i);
8e184852
TG
691
692 /* update timestamp */
4087cb9e
LP
693 if (update) {
694 *timestamp = u;
695 changed = true;
696 } else
697 return true;
8e184852 698 }
4087cb9e 699
8e184852
TG
700 return changed;
701}
eb66db55
MG
702
703int fsck_exists(const char *fstype) {
571d0134 704 _cleanup_free_ char *p = NULL, *d = NULL;
eb66db55 705 const char *checker;
571d0134 706 int r;
eb66db55 707
63c372cb 708 checker = strjoina("fsck.", fstype);
571d0134 709
b63bd109 710 r = find_binary(checker, true, &p);
571d0134
LP
711 if (r < 0)
712 return r;
713
061df014 714 /* An fsck that is linked to /bin/true is a non-existent
571d0134
LP
715 * fsck */
716
717 r = readlink_malloc(p, &d);
718 if (r >= 0 &&
719 (path_equal(d, "/bin/true") ||
720 path_equal(d, "/usr/bin/true") ||
721 path_equal(d, "/dev/null")))
722 return -ENOENT;
723
724 return 0;
eb66db55 725}