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