]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/path-util.c
core, shared: in deserializing, match same files reached via different paths
[thirdparty/systemd.git] / src / shared / path-util.c
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"
38 #include "missing.h"
39
40 bool path_is_absolute(const char *p) {
41 return p[0] == '/';
42 }
43
44 bool is_path(const char *p) {
45 return !!strchr(p, '/');
46 }
47
48 int 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
89 char **path_split_and_make_absolute(const char *p) {
90 char **l;
91 assert(p);
92
93 l = strv_split(p, ":");
94 if (!l)
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
105 char *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
114 return strjoin(prefix, "/", p, NULL);
115 }
116
117 char *path_make_absolute_cwd(const char *p) {
118 _cleanup_free_ char *cwd = NULL;
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
128 cwd = get_current_dir_name();
129 if (!cwd)
130 return NULL;
131
132 return strjoin(cwd, "/", p, NULL);
133 }
134
135 int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
136 char *r, *p;
137 unsigned n_parents;
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
170 path_kill_slashes(r);
171
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
206 r = malloc(n_parents * 3 + strlen(to_path) + 1);
207 if (!r)
208 return -ENOMEM;
209
210 for (p = r; n_parents > 0; n_parents--, p += 3)
211 memcpy(p, "../", 3);
212
213 strcpy(p, to_path);
214 path_kill_slashes(r);
215
216 *_r = r;
217 return 0;
218 }
219
220 char **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
230 t = path_make_absolute_cwd(*s);
231 if (!t)
232 return NULL;
233
234 free(*s);
235 *s = t;
236 }
237
238 return l;
239 }
240
241 char **path_strv_resolve(char **l, const char *prefix) {
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;
255 _cleanup_free_ char *orig = NULL;
256
257 if (!path_is_absolute(*s)) {
258 free(*s);
259 continue;
260 }
261
262 if (prefix) {
263 orig = *s;
264 t = strappend(prefix, orig);
265 if (!t) {
266 enomem = true;
267 continue;
268 }
269 } else
270 t = *s;
271
272 errno = 0;
273 u = canonicalize_file_name(t);
274 if (!u) {
275 if (errno == ENOENT) {
276 if (prefix) {
277 u = orig;
278 orig = NULL;
279 free(t);
280 } else
281 u = t;
282 } else {
283 free(t);
284 if (errno == ENOMEM || errno == 0)
285 enomem = true;
286
287 continue;
288 }
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 free(u);
310 u = orig;
311 orig = NULL;
312 }
313 } else
314 free(t);
315
316 l[k++] = u;
317 }
318
319 l[k] = NULL;
320
321 if (enomem)
322 return NULL;
323
324 return l;
325 }
326
327 char **path_strv_resolve_uniq(char **l, const char *prefix) {
328
329 if (strv_isempty(l))
330 return l;
331
332 if (!path_strv_resolve(l, prefix))
333 return NULL;
334
335 return strv_uniq(l);
336 }
337
338 char *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
373 char* path_startswith(const char *path, const char *prefix) {
374 assert(path);
375 assert(prefix);
376
377 if ((path[0] == '/') != (prefix[0] == '/'))
378 return NULL;
379
380 for (;;) {
381 size_t a, b;
382
383 path += strspn(path, "/");
384 prefix += strspn(prefix, "/");
385
386 if (*prefix == 0)
387 return (char*) path;
388
389 if (*path == 0)
390 return NULL;
391
392 a = strcspn(path, "/");
393 b = strcspn(prefix, "/");
394
395 if (a != b)
396 return NULL;
397
398 if (memcmp(path, prefix, a) != 0)
399 return NULL;
400
401 path += a;
402 prefix += b;
403 }
404 }
405
406 bool 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
439 bool path_equal_or_files_same(const char *a, const char *b) {
440 return path_equal(a, b) || files_same(a, b) > 0;
441 }
442
443 char* path_join(const char *root, const char *path, const char *rest) {
444 assert(path);
445
446 if (!isempty(root))
447 return strjoin(root, endswith(root, "/") ? "" : "/",
448 path[0] == '/' ? path+1 : path,
449 rest ? (endswith(path, "/") ? "" : "/") : NULL,
450 rest && rest[0] == '/' ? rest+1 : rest,
451 NULL);
452 else
453 return strjoin(path,
454 rest ? (endswith(path, "/") ? "" : "/") : NULL,
455 rest && rest[0] == '/' ? rest+1 : rest,
456 NULL);
457 }
458
459 int path_is_mount_point(const char *t, bool allow_symlink) {
460
461 union file_handle_union h = FILE_HANDLE_INIT;
462 int mount_id = -1, mount_id_parent = -1;
463 _cleanup_free_ char *parent = NULL;
464 struct stat a, b;
465 int r;
466 bool nosupp = false;
467
468 /* We are not actually interested in the file handles, but
469 * name_to_handle_at() also passes us the mount ID, hence use
470 * it but throw the handle away */
471
472 if (path_equal(t, "/"))
473 return 1;
474
475 r = name_to_handle_at(AT_FDCWD, t, &h.handle, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
476 if (r < 0) {
477 if (errno == ENOSYS)
478 /* This kernel does not support name_to_handle_at()
479 * fall back to the traditional stat() logic. */
480 goto fallback;
481 else if (errno == EOPNOTSUPP)
482 /* This kernel or file system does not support
483 * name_to_handle_at(), hence fallback to the
484 * traditional stat() logic */
485 nosupp = true;
486 else if (errno == ENOENT)
487 return 0;
488 else
489 return -errno;
490 }
491
492 r = path_get_parent(t, &parent);
493 if (r < 0)
494 return r;
495
496 h.handle.handle_bytes = MAX_HANDLE_SZ;
497 r = name_to_handle_at(AT_FDCWD, parent, &h.handle, &mount_id_parent, AT_SYMLINK_FOLLOW);
498 if (r < 0)
499 if (errno == EOPNOTSUPP)
500 if (nosupp)
501 /* Neither parent nor child do name_to_handle_at()?
502 We have no choice but to fall back. */
503 goto fallback;
504 else
505 /* The parent can't do name_to_handle_at() but
506 * the directory we are interested in can?
507 * Or the other way around?
508 * If so, it must be a mount point. */
509 return 1;
510 else
511 return -errno;
512 else
513 return mount_id != mount_id_parent;
514
515 fallback:
516 if (allow_symlink)
517 r = stat(t, &a);
518 else
519 r = lstat(t, &a);
520
521 if (r < 0) {
522 if (errno == ENOENT)
523 return 0;
524
525 return -errno;
526 }
527
528 free(parent);
529 parent = NULL;
530
531 r = path_get_parent(t, &parent);
532 if (r < 0)
533 return r;
534
535 r = stat(parent, &b);
536 if (r < 0)
537 return -errno;
538
539 return a.st_dev != b.st_dev;
540 }
541
542 int path_is_read_only_fs(const char *path) {
543 struct statvfs st;
544
545 assert(path);
546
547 if (statvfs(path, &st) < 0)
548 return -errno;
549
550 if (st.f_flag & ST_RDONLY)
551 return true;
552
553 /* On NFS, statvfs() might not reflect whether we can actually
554 * write to the remote share. Let's try again with
555 * access(W_OK) which is more reliable, at least sometimes. */
556 if (access(path, W_OK) < 0 && errno == EROFS)
557 return true;
558
559 return false;
560 }
561
562 int path_is_os_tree(const char *path) {
563 char *p;
564 int r;
565
566 /* We use /usr/lib/os-release as flag file if something is an OS */
567 p = strjoina(path, "/usr/lib/os-release");
568 r = access(p, F_OK);
569
570 if (r >= 0)
571 return 1;
572
573 /* Also check for the old location in /etc, just in case. */
574 p = strjoina(path, "/etc/os-release");
575 r = access(p, F_OK);
576
577 return r >= 0;
578 }
579
580 int find_binary(const char *name, bool local, char **filename) {
581 assert(name);
582
583 if (is_path(name)) {
584 if (local && access(name, X_OK) < 0)
585 return -errno;
586
587 if (filename) {
588 char *p;
589
590 p = path_make_absolute_cwd(name);
591 if (!p)
592 return -ENOMEM;
593
594 *filename = p;
595 }
596
597 return 0;
598 } else {
599 const char *path;
600 const char *word, *state;
601 size_t l;
602
603 /**
604 * Plain getenv, not secure_getenv, because we want
605 * to actually allow the user to pick the binary.
606 */
607 path = getenv("PATH");
608 if (!path)
609 path = DEFAULT_PATH;
610
611 FOREACH_WORD_SEPARATOR(word, l, path, ":", state) {
612 _cleanup_free_ char *p = NULL;
613
614 if (asprintf(&p, "%.*s/%s", (int) l, word, name) < 0)
615 return -ENOMEM;
616
617 if (access(p, X_OK) < 0)
618 continue;
619
620 if (filename) {
621 *filename = path_kill_slashes(p);
622 p = NULL;
623 }
624
625 return 0;
626 }
627
628 return -ENOENT;
629 }
630 }
631
632 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
633 bool changed = false;
634 const char* const* i;
635
636 assert(timestamp);
637
638 if (paths == NULL)
639 return false;
640
641 STRV_FOREACH(i, paths) {
642 struct stat stats;
643 usec_t u;
644
645 if (stat(*i, &stats) < 0)
646 continue;
647
648 u = timespec_load(&stats.st_mtim);
649
650 /* first check */
651 if (*timestamp >= u)
652 continue;
653
654 log_debug("timestamp of '%s' changed", *i);
655
656 /* update timestamp */
657 if (update) {
658 *timestamp = u;
659 changed = true;
660 } else
661 return true;
662 }
663
664 return changed;
665 }
666
667 int fsck_exists(const char *fstype) {
668 _cleanup_free_ char *p = NULL, *d = NULL;
669 const char *checker;
670 int r;
671
672 checker = strjoina("fsck.", fstype);
673
674 r = find_binary(checker, true, &p);
675 if (r < 0)
676 return r;
677
678 /* An fsck that is linked to /bin/true is a non-existent
679 * fsck */
680
681 r = readlink_malloc(p, &d);
682 if (r >= 0 &&
683 (path_equal(d, "/bin/true") ||
684 path_equal(d, "/usr/bin/true") ||
685 path_equal(d, "/dev/null")))
686 return -ENOENT;
687
688 return 0;
689 }