]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/path-util.c
rules: keyboard - prefix "atkbd" match strings like we prefix the "name" strings
[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
403bool path_equal(const char *a, const char *b) {
404 assert(a);
405 assert(b);
406
407 if ((a[0] == '/') != (b[0] == '/'))
408 return false;
409
410 for (;;) {
411 size_t j, k;
412
413 a += strspn(a, "/");
414 b += strspn(b, "/");
415
416 if (*a == 0 && *b == 0)
417 return true;
418
419 if (*a == 0 || *b == 0)
420 return false;
421
422 j = strcspn(a, "/");
423 k = strcspn(b, "/");
424
425 if (j != k)
426 return false;
427
428 if (memcmp(a, b, j) != 0)
429 return false;
430
431 a += j;
432 b += k;
433 }
434}
435
c78e47a6
MS
436bool path_equal_or_files_same(const char *a, const char *b) {
437 return path_equal(a, b) || files_same(a, b) > 0;
438}
439
0c6ea3a4
ZJS
440char* path_join(const char *root, const char *path, const char *rest) {
441 assert(path);
442
443 if (!isempty(root))
bc854dc7 444 return strjoin(root, endswith(root, "/") ? "" : "/",
0c6ea3a4 445 path[0] == '/' ? path+1 : path,
bc854dc7 446 rest ? (endswith(path, "/") ? "" : "/") : NULL,
0c6ea3a4
ZJS
447 rest && rest[0] == '/' ? rest+1 : rest,
448 NULL);
449 else
450 return strjoin(path,
bc854dc7 451 rest ? (endswith(path, "/") ? "" : "/") : NULL,
0c6ea3a4
ZJS
452 rest && rest[0] == '/' ? rest+1 : rest,
453 NULL);
454}
455
9eb977db 456int path_is_mount_point(const char *t, bool allow_symlink) {
21749924 457
2695c5c4 458 union file_handle_union h = FILE_HANDLE_INIT;
e40872fc 459 int mount_id = -1, mount_id_parent = -1;
1640a0b6 460 struct stat a, b;
21749924 461 int r;
27cc6f16 462 _cleanup_close_ int fd = -1;
6feeeab0 463 bool nosupp = false;
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
27cc6f16
HH
472 fd = openat(AT_FDCWD, t, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|(allow_symlink ? 0 : O_PATH));
473 if (fd < 0) {
474 if (errno == ENOENT)
475 return 0;
476
477 return -errno;
478 }
479
480 r = name_to_handle_at(fd, "", &h.handle, &mount_id, AT_EMPTY_PATH);
9eb977db 481 if (r < 0) {
e40872fc
DR
482 if (errno == ENOSYS)
483 /* This kernel does not support name_to_handle_at()
484 * fall back to the traditional stat() logic. */
485 goto fallback;
486 else if (errno == EOPNOTSUPP)
fa125f4e 487 /* This kernel or file system does not support
1640a0b6
LP
488 * name_to_handle_at(), hence fallback to the
489 * traditional stat() logic */
6feeeab0
ZJS
490 nosupp = true;
491 else if (errno == ENOENT)
9eb977db 492 return 0;
6feeeab0
ZJS
493 else
494 return -errno;
9eb977db
KS
495 }
496
9eb977db 497
21749924 498 h.handle.handle_bytes = MAX_HANDLE_SZ;
27cc6f16 499 r = name_to_handle_at(fd, "..", &h.handle, &mount_id_parent, 0);
6feeeab0 500 if (r < 0)
21749924 501 if (errno == EOPNOTSUPP)
6feeeab0
ZJS
502 if (nosupp)
503 /* Neither parent nor child do name_to_handle_at()?
504 We have no choice but to fall back. */
505 goto fallback;
506 else
507 /* The parent can't do name_to_handle_at() but
508 * the directory we are interested in can?
509 * Or the other way around?
510 * If so, it must be a mount point. */
511 return 1;
512 else
513 return -errno;
514 else
515 return mount_id != mount_id_parent;
1640a0b6
LP
516
517fallback:
27cc6f16 518 r = fstatat(fd, "", &a, AT_EMPTY_PATH);
1640a0b6 519
8ac75493
MM
520 if (r < 0) {
521 if (errno == ENOENT)
522 return 0;
523
9eb977db 524 return -errno;
8ac75493 525 }
9eb977db 526
cde9cb34 527
27cc6f16 528 r = fstatat(fd, "..", &b, 0);
1640a0b6
LP
529 if (r < 0)
530 return -errno;
531
532 return a.st_dev != b.st_dev;
9eb977db
KS
533}
534
535int path_is_read_only_fs(const char *path) {
536 struct statvfs st;
537
538 assert(path);
539
540 if (statvfs(path, &st) < 0)
541 return -errno;
542
70421bdc
LP
543 if (st.f_flag & ST_RDONLY)
544 return true;
545
546 /* On NFS, statvfs() might not reflect whether we can actually
547 * write to the remote share. Let's try again with
548 * access(W_OK) which is more reliable, at least sometimes. */
549 if (access(path, W_OK) < 0 && errno == EROFS)
550 return true;
551
552 return false;
9eb977db 553}
66060897
LP
554
555int path_is_os_tree(const char *path) {
556 char *p;
557 int r;
558
5ae4d543 559 /* We use /usr/lib/os-release as flag file if something is an OS */
63c372cb 560 p = strjoina(path, "/usr/lib/os-release");
5ae4d543
LP
561 r = access(p, F_OK);
562
563 if (r >= 0)
564 return 1;
66060897 565
5ae4d543 566 /* Also check for the old location in /etc, just in case. */
63c372cb 567 p = strjoina(path, "/etc/os-release");
66060897
LP
568 r = access(p, F_OK);
569
5ae4d543 570 return r >= 0;
66060897 571}
c9d954b2 572
b63bd109 573int find_binary(const char *name, bool local, char **filename) {
c9d954b2 574 assert(name);
4087cb9e 575
571d0134 576 if (is_path(name)) {
b63bd109 577 if (local && access(name, X_OK) < 0)
b972115c
ZJS
578 return -errno;
579
eb66db55
MG
580 if (filename) {
581 char *p;
c9d954b2 582
b972115c 583 p = path_make_absolute_cwd(name);
eb66db55
MG
584 if (!p)
585 return -ENOMEM;
b972115c 586
eb66db55
MG
587 *filename = p;
588 }
c9d954b2 589
c9d954b2
ZJS
590 return 0;
591 } else {
592 const char *path;
a2a5291b 593 const char *word, *state;
c9d954b2
ZJS
594 size_t l;
595
596 /**
597 * Plain getenv, not secure_getenv, because we want
598 * to actually allow the user to pick the binary.
599 */
600 path = getenv("PATH");
601 if (!path)
602 path = DEFAULT_PATH;
603
a2a5291b 604 FOREACH_WORD_SEPARATOR(word, l, path, ":", state) {
eb66db55 605 _cleanup_free_ char *p = NULL;
c9d954b2 606
a2a5291b 607 if (asprintf(&p, "%.*s/%s", (int) l, word, name) < 0)
c9d954b2
ZJS
608 return -ENOMEM;
609
eb66db55 610 if (access(p, X_OK) < 0)
c9d954b2 611 continue;
c9d954b2 612
eb66db55 613 if (filename) {
dbb9401d 614 *filename = path_kill_slashes(p);
eb66db55
MG
615 p = NULL;
616 }
c9d954b2
ZJS
617
618 return 0;
619 }
620
621 return -ENOENT;
622 }
623}
8e184852 624
2ad8416d 625bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
8e184852 626 bool changed = false;
2ad8416d 627 const char* const* i;
8e184852 628
97f2d76d
TG
629 assert(timestamp);
630
8e184852 631 if (paths == NULL)
4087cb9e 632 return false;
8e184852 633
4087cb9e 634 STRV_FOREACH(i, paths) {
8e184852 635 struct stat stats;
4087cb9e 636 usec_t u;
8e184852 637
4087cb9e 638 if (stat(*i, &stats) < 0)
8e184852
TG
639 continue;
640
4087cb9e
LP
641 u = timespec_load(&stats.st_mtim);
642
97f2d76d 643 /* first check */
4087cb9e 644 if (*timestamp >= u)
8e184852
TG
645 continue;
646
9f6445e3 647 log_debug("timestamp of '%s' changed", *i);
8e184852
TG
648
649 /* update timestamp */
4087cb9e
LP
650 if (update) {
651 *timestamp = u;
652 changed = true;
653 } else
654 return true;
8e184852 655 }
4087cb9e 656
8e184852
TG
657 return changed;
658}
eb66db55
MG
659
660int fsck_exists(const char *fstype) {
571d0134 661 _cleanup_free_ char *p = NULL, *d = NULL;
eb66db55 662 const char *checker;
571d0134 663 int r;
eb66db55 664
63c372cb 665 checker = strjoina("fsck.", fstype);
571d0134 666
b63bd109 667 r = find_binary(checker, true, &p);
571d0134
LP
668 if (r < 0)
669 return r;
670
061df014 671 /* An fsck that is linked to /bin/true is a non-existent
571d0134
LP
672 * fsck */
673
674 r = readlink_malloc(p, &d);
675 if (r >= 0 &&
676 (path_equal(d, "/bin/true") ||
677 path_equal(d, "/usr/bin/true") ||
678 path_equal(d, "/dev/null")))
679 return -ENOENT;
680
681 return 0;
eb66db55 682}