]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/path-util.c
shared: rename path_strv_canonicalize_absolute functions
[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 path_make_absolute(p, cwd);
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 u = orig;
310 orig = NULL;
311 }
312 } else
313 free(t);
314
315 l[k++] = u;
316 }
317
318 l[k] = NULL;
319
320 if (enomem)
321 return NULL;
322
323 return l;
324 }
325
326 char **path_strv_resolve_uniq(char **l, const char *prefix) {
327
328 if (strv_isempty(l))
329 return l;
330
331 if (!path_strv_resolve(l, prefix))
332 return NULL;
333
334 return strv_uniq(l);
335 }
336
337 char *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
372 char* path_startswith(const char *path, const char *prefix) {
373 assert(path);
374 assert(prefix);
375
376 if ((path[0] == '/') != (prefix[0] == '/'))
377 return NULL;
378
379 for (;;) {
380 size_t a, b;
381
382 path += strspn(path, "/");
383 prefix += strspn(prefix, "/");
384
385 if (*prefix == 0)
386 return (char*) path;
387
388 if (*path == 0)
389 return NULL;
390
391 a = strcspn(path, "/");
392 b = strcspn(prefix, "/");
393
394 if (a != b)
395 return NULL;
396
397 if (memcmp(path, prefix, a) != 0)
398 return NULL;
399
400 path += a;
401 prefix += b;
402 }
403 }
404
405 bool 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
438 int path_is_mount_point(const char *t, bool allow_symlink) {
439
440 union file_handle_union h = {
441 .handle.handle_bytes = MAX_HANDLE_SZ
442 };
443
444 int mount_id, mount_id_parent;
445 char *parent;
446 struct stat a, b;
447 int r;
448
449 /* We are not actually interested in the file handles, but
450 * name_to_handle_at() also passes us the mount ID, hence use
451 * it but throw the handle away */
452
453 if (path_equal(t, "/"))
454 return 1;
455
456 r = name_to_handle_at(AT_FDCWD, t, &h.handle, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
457 if (r < 0) {
458 if (IN_SET(errno, ENOSYS, EOPNOTSUPP))
459 /* This kernel or file system does not support
460 * name_to_handle_at(), hence fallback to the
461 * traditional stat() logic */
462 goto fallback;
463
464 if (errno == ENOENT)
465 return 0;
466
467 return -errno;
468 }
469
470 r = path_get_parent(t, &parent);
471 if (r < 0)
472 return r;
473
474 h.handle.handle_bytes = MAX_HANDLE_SZ;
475 r = name_to_handle_at(AT_FDCWD, parent, &h.handle, &mount_id_parent, 0);
476 free(parent);
477 if (r < 0) {
478 /* The parent can't do name_to_handle_at() but the
479 * directory we are interested in can? If so, it must
480 * be a mount point */
481 if (errno == EOPNOTSUPP)
482 return 1;
483
484 return -errno;
485 }
486
487 return mount_id != mount_id_parent;
488
489 fallback:
490 if (allow_symlink)
491 r = stat(t, &a);
492 else
493 r = lstat(t, &a);
494
495 if (r < 0) {
496 if (errno == ENOENT)
497 return 0;
498
499 return -errno;
500 }
501
502 r = path_get_parent(t, &parent);
503 if (r < 0)
504 return r;
505
506 r = lstat(parent, &b);
507 free(parent);
508 if (r < 0)
509 return -errno;
510
511 return a.st_dev != b.st_dev;
512 }
513
514 int path_is_read_only_fs(const char *path) {
515 struct statvfs st;
516
517 assert(path);
518
519 if (statvfs(path, &st) < 0)
520 return -errno;
521
522 return !!(st.f_flag & ST_RDONLY);
523 }
524
525 int path_is_os_tree(const char *path) {
526 char *p;
527 int r;
528
529 /* We use /usr/lib/os-release as flag file if something is an OS */
530 p = strappenda(path, "/usr/lib/os-release");
531 r = access(p, F_OK);
532
533 if (r >= 0)
534 return 1;
535
536 /* Also check for the old location in /etc, just in case. */
537 p = strappenda(path, "/etc/os-release");
538 r = access(p, F_OK);
539
540 return r >= 0;
541 }
542
543 int find_binary(const char *name, char **filename) {
544 assert(name);
545
546 if (strchr(name, '/')) {
547 if (access(name, X_OK) < 0)
548 return -errno;
549
550 if (filename) {
551 char *p;
552
553 p = path_make_absolute_cwd(name);
554 if (!p)
555 return -ENOMEM;
556
557 *filename = p;
558 }
559
560 return 0;
561 } else {
562 const char *path;
563 char *state, *w;
564 size_t l;
565
566 /**
567 * Plain getenv, not secure_getenv, because we want
568 * to actually allow the user to pick the binary.
569 */
570 path = getenv("PATH");
571 if (!path)
572 path = DEFAULT_PATH;
573
574 FOREACH_WORD_SEPARATOR(w, l, path, ":", state) {
575 _cleanup_free_ char *p = NULL;
576
577 if (asprintf(&p, "%.*s/%s", (int) l, w, name) < 0)
578 return -ENOMEM;
579
580 if (access(p, X_OK) < 0)
581 continue;
582
583 if (filename) {
584 *filename = path_kill_slashes(p);
585 p = NULL;
586 }
587
588 return 0;
589 }
590
591 return -ENOENT;
592 }
593 }
594
595 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
596 bool changed = false;
597 const char* const* i;
598
599 assert(timestamp);
600
601 if (paths == NULL)
602 return false;
603
604 STRV_FOREACH(i, paths) {
605 struct stat stats;
606 usec_t u;
607
608 if (stat(*i, &stats) < 0)
609 continue;
610
611 u = timespec_load(&stats.st_mtim);
612
613 /* first check */
614 if (*timestamp >= u)
615 continue;
616
617 log_debug("timestamp of '%s' changed", *i);
618
619 /* update timestamp */
620 if (update) {
621 *timestamp = u;
622 changed = true;
623 } else
624 return true;
625 }
626
627 return changed;
628 }
629
630 int fsck_exists(const char *fstype) {
631 const char *checker;
632
633 checker = strappenda("fsck.", fstype);
634 return find_binary(checker, NULL);
635 }