]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/path-util.c
networkd: minor simplification
[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 */
3542eac7 309 free(u);
12ed81d9
ZJS
310 u = orig;
311 orig = NULL;
312 }
91a6489d
LP
313 } else
314 free(t);
9eb977db
KS
315
316 l[k++] = u;
317 }
318
319 l[k] = NULL;
320
321 if (enomem)
322 return NULL;
323
324 return l;
325}
326
7d8da2c9 327char **path_strv_resolve_uniq(char **l, const char *prefix) {
112cfb18 328
fabe5c0e
LP
329 if (strv_isempty(l))
330 return l;
331
7d8da2c9 332 if (!path_strv_resolve(l, prefix))
fabe5c0e
LP
333 return NULL;
334
335 return strv_uniq(l);
336}
337
9eb977db
KS
338char *path_kill_slashes(char *path) {
339 char *f, *t;
340 bool slash = false;
341
342 /* Removes redundant inner and trailing slashes. Modifies the
343 * passed string in-place.
344 *
345 * ///foo///bar/ becomes /foo/bar
346 */
347
348 for (f = path, t = path; *f; f++) {
349
350 if (*f == '/') {
351 slash = true;
352 continue;
353 }
354
355 if (slash) {
356 slash = false;
357 *(t++) = '/';
358 }
359
360 *(t++) = *f;
361 }
362
363 /* Special rule, if we are talking of the root directory, a
364 trailing slash is good */
365
366 if (t == path && slash)
367 *(t++) = '/';
368
369 *t = 0;
370 return path;
371}
372
424a19f8 373char* path_startswith(const char *path, const char *prefix) {
9eb977db
KS
374 assert(path);
375 assert(prefix);
376
377 if ((path[0] == '/') != (prefix[0] == '/'))
424a19f8 378 return NULL;
9eb977db
KS
379
380 for (;;) {
381 size_t a, b;
382
383 path += strspn(path, "/");
384 prefix += strspn(prefix, "/");
385
386 if (*prefix == 0)
424a19f8 387 return (char*) path;
9eb977db
KS
388
389 if (*path == 0)
424a19f8 390 return NULL;
9eb977db
KS
391
392 a = strcspn(path, "/");
393 b = strcspn(prefix, "/");
394
395 if (a != b)
424a19f8 396 return NULL;
9eb977db
KS
397
398 if (memcmp(path, prefix, a) != 0)
424a19f8 399 return NULL;
9eb977db
KS
400
401 path += a;
402 prefix += b;
403 }
404}
405
406bool path_equal(const char *a, const char *b) {
407 assert(a);
408 assert(b);
409
410 if ((a[0] == '/') != (b[0] == '/'))
411 return false;
412
413 for (;;) {
414 size_t j, k;
415
416 a += strspn(a, "/");
417 b += strspn(b, "/");
418
419 if (*a == 0 && *b == 0)
420 return true;
421
422 if (*a == 0 || *b == 0)
423 return false;
424
425 j = strcspn(a, "/");
426 k = strcspn(b, "/");
427
428 if (j != k)
429 return false;
430
431 if (memcmp(a, b, j) != 0)
432 return false;
433
434 a += j;
435 b += k;
436 }
437}
438
0c6ea3a4
ZJS
439char* path_join(const char *root, const char *path, const char *rest) {
440 assert(path);
441
442 if (!isempty(root))
bc854dc7 443 return strjoin(root, endswith(root, "/") ? "" : "/",
0c6ea3a4 444 path[0] == '/' ? path+1 : path,
bc854dc7 445 rest ? (endswith(path, "/") ? "" : "/") : NULL,
0c6ea3a4
ZJS
446 rest && rest[0] == '/' ? rest+1 : rest,
447 NULL);
448 else
449 return strjoin(path,
bc854dc7 450 rest ? (endswith(path, "/") ? "" : "/") : NULL,
0c6ea3a4
ZJS
451 rest && rest[0] == '/' ? rest+1 : rest,
452 NULL);
453}
454
9eb977db 455int path_is_mount_point(const char *t, bool allow_symlink) {
21749924 456
2695c5c4 457 union file_handle_union h = FILE_HANDLE_INIT;
e40872fc 458 int mount_id = -1, mount_id_parent = -1;
e1d75803 459 _cleanup_free_ char *parent = NULL;
1640a0b6 460 struct stat a, b;
21749924 461 int r;
6feeeab0 462 bool nosupp = false;
9eb977db 463
cde9cb34
LP
464 /* We are not actually interested in the file handles, but
465 * name_to_handle_at() also passes us the mount ID, hence use
466 * it but throw the handle away */
467
468 if (path_equal(t, "/"))
469 return 1;
9eb977db 470
21749924 471 r = name_to_handle_at(AT_FDCWD, t, &h.handle, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
9eb977db 472 if (r < 0) {
e40872fc
DR
473 if (errno == ENOSYS)
474 /* This kernel does not support name_to_handle_at()
475 * fall back to the traditional stat() logic. */
476 goto fallback;
477 else if (errno == EOPNOTSUPP)
fa125f4e 478 /* This kernel or file system does not support
1640a0b6
LP
479 * name_to_handle_at(), hence fallback to the
480 * traditional stat() logic */
6feeeab0
ZJS
481 nosupp = true;
482 else if (errno == ENOENT)
9eb977db 483 return 0;
6feeeab0
ZJS
484 else
485 return -errno;
9eb977db
KS
486 }
487
488 r = path_get_parent(t, &parent);
489 if (r < 0)
490 return r;
491
21749924 492 h.handle.handle_bytes = MAX_HANDLE_SZ;
b890bf6a 493 r = name_to_handle_at(AT_FDCWD, parent, &h.handle, &mount_id_parent, AT_SYMLINK_FOLLOW);
6feeeab0 494 if (r < 0)
21749924 495 if (errno == EOPNOTSUPP)
6feeeab0
ZJS
496 if (nosupp)
497 /* Neither parent nor child do name_to_handle_at()?
498 We have no choice but to fall back. */
499 goto fallback;
500 else
501 /* The parent can't do name_to_handle_at() but
502 * the directory we are interested in can?
503 * Or the other way around?
504 * If so, it must be a mount point. */
505 return 1;
506 else
507 return -errno;
508 else
509 return mount_id != mount_id_parent;
1640a0b6
LP
510
511fallback:
512 if (allow_symlink)
513 r = stat(t, &a);
514 else
f408b8f1 515 r = lstat(t, &a);
1640a0b6 516
8ac75493
MM
517 if (r < 0) {
518 if (errno == ENOENT)
519 return 0;
520
9eb977db 521 return -errno;
8ac75493 522 }
9eb977db 523
a0539d77
TG
524 free(parent);
525 parent = NULL;
526
1640a0b6
LP
527 r = path_get_parent(t, &parent);
528 if (r < 0)
529 return r;
cde9cb34 530
c0e57ba9 531 r = stat(parent, &b);
1640a0b6
LP
532 if (r < 0)
533 return -errno;
534
535 return a.st_dev != b.st_dev;
9eb977db
KS
536}
537
538int path_is_read_only_fs(const char *path) {
539 struct statvfs st;
540
541 assert(path);
542
543 if (statvfs(path, &st) < 0)
544 return -errno;
545
70421bdc
LP
546 if (st.f_flag & ST_RDONLY)
547 return true;
548
549 /* On NFS, statvfs() might not reflect whether we can actually
550 * write to the remote share. Let's try again with
551 * access(W_OK) which is more reliable, at least sometimes. */
552 if (access(path, W_OK) < 0 && errno == EROFS)
553 return true;
554
555 return false;
9eb977db 556}
66060897
LP
557
558int path_is_os_tree(const char *path) {
559 char *p;
560 int r;
561
5ae4d543
LP
562 /* We use /usr/lib/os-release as flag file if something is an OS */
563 p = strappenda(path, "/usr/lib/os-release");
564 r = access(p, F_OK);
565
566 if (r >= 0)
567 return 1;
66060897 568
5ae4d543 569 /* Also check for the old location in /etc, just in case. */
66060897
LP
570 p = strappenda(path, "/etc/os-release");
571 r = access(p, F_OK);
572
5ae4d543 573 return r >= 0;
66060897 574}
c9d954b2 575
b63bd109 576int find_binary(const char *name, bool local, char **filename) {
c9d954b2 577 assert(name);
4087cb9e 578
571d0134 579 if (is_path(name)) {
b63bd109 580 if (local && access(name, X_OK) < 0)
b972115c
ZJS
581 return -errno;
582
eb66db55
MG
583 if (filename) {
584 char *p;
c9d954b2 585
b972115c 586 p = path_make_absolute_cwd(name);
eb66db55
MG
587 if (!p)
588 return -ENOMEM;
b972115c 589
eb66db55
MG
590 *filename = p;
591 }
c9d954b2 592
c9d954b2
ZJS
593 return 0;
594 } else {
595 const char *path;
a2a5291b 596 const char *word, *state;
c9d954b2
ZJS
597 size_t l;
598
599 /**
600 * Plain getenv, not secure_getenv, because we want
601 * to actually allow the user to pick the binary.
602 */
603 path = getenv("PATH");
604 if (!path)
605 path = DEFAULT_PATH;
606
a2a5291b 607 FOREACH_WORD_SEPARATOR(word, l, path, ":", state) {
eb66db55 608 _cleanup_free_ char *p = NULL;
c9d954b2 609
a2a5291b 610 if (asprintf(&p, "%.*s/%s", (int) l, word, name) < 0)
c9d954b2
ZJS
611 return -ENOMEM;
612
eb66db55 613 if (access(p, X_OK) < 0)
c9d954b2 614 continue;
c9d954b2 615
eb66db55 616 if (filename) {
dbb9401d 617 *filename = path_kill_slashes(p);
eb66db55
MG
618 p = NULL;
619 }
c9d954b2
ZJS
620
621 return 0;
622 }
623
624 return -ENOENT;
625 }
626}
8e184852 627
2ad8416d 628bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
8e184852 629 bool changed = false;
2ad8416d 630 const char* const* i;
8e184852 631
97f2d76d
TG
632 assert(timestamp);
633
8e184852 634 if (paths == NULL)
4087cb9e 635 return false;
8e184852 636
4087cb9e 637 STRV_FOREACH(i, paths) {
8e184852 638 struct stat stats;
4087cb9e 639 usec_t u;
8e184852 640
4087cb9e 641 if (stat(*i, &stats) < 0)
8e184852
TG
642 continue;
643
4087cb9e
LP
644 u = timespec_load(&stats.st_mtim);
645
97f2d76d 646 /* first check */
4087cb9e 647 if (*timestamp >= u)
8e184852
TG
648 continue;
649
9f6445e3 650 log_debug("timestamp of '%s' changed", *i);
8e184852
TG
651
652 /* update timestamp */
4087cb9e
LP
653 if (update) {
654 *timestamp = u;
655 changed = true;
656 } else
657 return true;
8e184852 658 }
4087cb9e 659
8e184852
TG
660 return changed;
661}
eb66db55
MG
662
663int fsck_exists(const char *fstype) {
571d0134 664 _cleanup_free_ char *p = NULL, *d = NULL;
eb66db55 665 const char *checker;
571d0134 666 int r;
eb66db55
MG
667
668 checker = strappenda("fsck.", fstype);
571d0134 669
b63bd109 670 r = find_binary(checker, true, &p);
571d0134
LP
671 if (r < 0)
672 return r;
673
061df014 674 /* An fsck that is linked to /bin/true is a non-existent
571d0134
LP
675 * fsck */
676
677 r = readlink_malloc(p, &d);
678 if (r >= 0 &&
679 (path_equal(d, "/bin/true") ||
680 path_equal(d, "/usr/bin/true") ||
681 path_equal(d, "/dev/null")))
682 return -ENOENT;
683
684 return 0;
eb66db55 685}