]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/path-util.c
test-strv: add test which shows access to random memory
[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
116cc028 132 return path_make_absolute(p, cwd);
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
536 return !!(st.f_flag & ST_RDONLY);
537}
66060897
LP
538
539int path_is_os_tree(const char *path) {
540 char *p;
541 int r;
542
5ae4d543
LP
543 /* We use /usr/lib/os-release as flag file if something is an OS */
544 p = strappenda(path, "/usr/lib/os-release");
545 r = access(p, F_OK);
546
547 if (r >= 0)
548 return 1;
66060897 549
5ae4d543 550 /* Also check for the old location in /etc, just in case. */
66060897
LP
551 p = strappenda(path, "/etc/os-release");
552 r = access(p, F_OK);
553
5ae4d543 554 return r >= 0;
66060897 555}
c9d954b2
ZJS
556
557int find_binary(const char *name, char **filename) {
558 assert(name);
4087cb9e 559
571d0134 560 if (is_path(name)) {
b972115c
ZJS
561 if (access(name, X_OK) < 0)
562 return -errno;
563
eb66db55
MG
564 if (filename) {
565 char *p;
c9d954b2 566
b972115c 567 p = path_make_absolute_cwd(name);
eb66db55
MG
568 if (!p)
569 return -ENOMEM;
b972115c 570
eb66db55
MG
571 *filename = p;
572 }
c9d954b2 573
c9d954b2
ZJS
574 return 0;
575 } else {
576 const char *path;
577 char *state, *w;
578 size_t l;
579
580 /**
581 * Plain getenv, not secure_getenv, because we want
582 * to actually allow the user to pick the binary.
583 */
584 path = getenv("PATH");
585 if (!path)
586 path = DEFAULT_PATH;
587
588 FOREACH_WORD_SEPARATOR(w, l, path, ":", state) {
eb66db55 589 _cleanup_free_ char *p = NULL;
c9d954b2 590
4bcc8c3c 591 if (asprintf(&p, "%.*s/%s", (int) l, w, name) < 0)
c9d954b2
ZJS
592 return -ENOMEM;
593
eb66db55 594 if (access(p, X_OK) < 0)
c9d954b2 595 continue;
c9d954b2 596
eb66db55 597 if (filename) {
dbb9401d 598 *filename = path_kill_slashes(p);
eb66db55
MG
599 p = NULL;
600 }
c9d954b2
ZJS
601
602 return 0;
603 }
604
605 return -ENOENT;
606 }
607}
8e184852 608
2ad8416d 609bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
8e184852 610 bool changed = false;
2ad8416d 611 const char* const* i;
8e184852 612
97f2d76d
TG
613 assert(timestamp);
614
8e184852 615 if (paths == NULL)
4087cb9e 616 return false;
8e184852 617
4087cb9e 618 STRV_FOREACH(i, paths) {
8e184852 619 struct stat stats;
4087cb9e 620 usec_t u;
8e184852 621
4087cb9e 622 if (stat(*i, &stats) < 0)
8e184852
TG
623 continue;
624
4087cb9e
LP
625 u = timespec_load(&stats.st_mtim);
626
97f2d76d 627 /* first check */
4087cb9e 628 if (*timestamp >= u)
8e184852
TG
629 continue;
630
9f6445e3 631 log_debug("timestamp of '%s' changed", *i);
8e184852
TG
632
633 /* update timestamp */
4087cb9e
LP
634 if (update) {
635 *timestamp = u;
636 changed = true;
637 } else
638 return true;
8e184852 639 }
4087cb9e 640
8e184852
TG
641 return changed;
642}
eb66db55
MG
643
644int fsck_exists(const char *fstype) {
571d0134 645 _cleanup_free_ char *p = NULL, *d = NULL;
eb66db55 646 const char *checker;
571d0134 647 int r;
eb66db55
MG
648
649 checker = strappenda("fsck.", fstype);
571d0134
LP
650
651 r = find_binary(checker, &p);
652 if (r < 0)
653 return r;
654
061df014 655 /* An fsck that is linked to /bin/true is a non-existent
571d0134
LP
656 * fsck */
657
658 r = readlink_malloc(p, &d);
659 if (r >= 0 &&
660 (path_equal(d, "/bin/true") ||
661 path_equal(d, "/usr/bin/true") ||
662 path_equal(d, "/dev/null")))
663 return -ENOENT;
664
665 return 0;
eb66db55 666}