]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/path-util.c
Use %m instead of strerror(errno) where appropiate
[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;
138 size_t to_path_len;
139
140 assert(from_dir);
141 assert(to_path);
142 assert(_r);
143
144 /* Strips the common part, and adds ".." elements as necessary. */
145
146 if (!path_is_absolute(from_dir))
147 return -EINVAL;
148
149 if (!path_is_absolute(to_path))
150 return -EINVAL;
151
152 /* Skip the common part. */
153 for (;;) {
154 size_t a;
155 size_t b;
156
157 from_dir += strspn(from_dir, "/");
158 to_path += strspn(to_path, "/");
159
160 if (!*from_dir) {
161 if (!*to_path)
162 /* from_dir equals to_path. */
163 r = strdup(".");
164 else
165 /* from_dir is a parent directory of to_path. */
166 r = strdup(to_path);
167
168 if (!r)
169 return -ENOMEM;
170
171 *_r = r;
172 return 0;
173 }
174
175 if (!*to_path)
176 break;
177
178 a = strcspn(from_dir, "/");
179 b = strcspn(to_path, "/");
180
181 if (a != b)
182 break;
183
184 if (memcmp(from_dir, to_path, a) != 0)
185 break;
186
187 from_dir += a;
188 to_path += b;
189 }
190
191 /* If we're here, then "from_dir" has one or more elements that need to
192 * be replaced with "..". */
193
194 /* Count the number of necessary ".." elements. */
195 for (n_parents = 0;;) {
196 from_dir += strspn(from_dir, "/");
197
198 if (!*from_dir)
199 break;
200
201 from_dir += strcspn(from_dir, "/");
202 n_parents++;
203 }
204
205 to_path_len = strlen(to_path);
206
207 r = malloc(n_parents * 3 + to_path_len);
208 if (!r)
209 return -ENOMEM;
210
211 for (p = r; n_parents > 0; n_parents--, p += 3)
212 memcpy(p, "../", 3);
213
214 if (to_path_len > 0)
215 memcpy(p, to_path, to_path_len);
216 else
217 /* "to_path" is a parent directory of "from_dir". Let's remove
218 * the redundant slash from the end of the result. */
219 *(p - 1) = 0;
220
221 *_r = r;
222 return 0;
223}
224
9eb977db
KS
225char **path_strv_make_absolute_cwd(char **l) {
226 char **s;
227
228 /* Goes through every item in the string list and makes it
229 * absolute. This works in place and won't rollback any
230 * changes on failure. */
231
232 STRV_FOREACH(s, l) {
233 char *t;
234
116cc028
ZJS
235 t = path_make_absolute_cwd(*s);
236 if (!t)
9eb977db
KS
237 return NULL;
238
239 free(*s);
240 *s = t;
241 }
242
243 return l;
244}
245
112cfb18 246char **path_strv_canonicalize_absolute(char **l, const char *prefix) {
9eb977db
KS
247 char **s;
248 unsigned k = 0;
249 bool enomem = false;
250
251 if (strv_isempty(l))
252 return l;
253
254 /* Goes through every item in the string list and canonicalize
255 * the path. This works in place and won't rollback any
256 * changes on failure. */
257
258 STRV_FOREACH(s, l) {
259 char *t, *u;
12ed81d9 260 _cleanup_free_ char *orig = NULL;
9eb977db 261
12ed81d9
ZJS
262 if (!path_is_absolute(*s)) {
263 free(*s);
9eb977db 264 continue;
12ed81d9 265 }
112cfb18
MM
266
267 if (prefix) {
12ed81d9
ZJS
268 orig = *s;
269 t = strappend(prefix, orig);
112cfb18
MM
270 if (!t) {
271 enomem = true;
272 continue;
273 }
12ed81d9 274 } else
112cfb18 275 t = *s;
9eb977db
KS
276
277 errno = 0;
278 u = canonicalize_file_name(t);
9eb977db 279 if (!u) {
12ed81d9
ZJS
280 if (errno == ENOENT) {
281 if (prefix) {
282 u = orig;
283 orig = NULL;
284 free(t);
285 } else
286 u = t;
287 } else {
874310b7 288 free(t);
112cfb18 289 if (errno == ENOMEM || errno == 0)
874310b7
ZJS
290 enomem = true;
291
292 continue;
293 }
12ed81d9
ZJS
294 } else if (prefix) {
295 char *x;
296
297 free(t);
298 x = path_startswith(u, prefix);
299 if (x) {
300 /* restore the slash if it was lost */
301 if (!startswith(x, "/"))
302 *(--x) = '/';
303
304 t = strdup(x);
305 free(u);
306 if (!t) {
307 enomem = true;
308 continue;
309 }
310 u = t;
311 } else {
312 /* canonicalized path goes outside of
313 * prefix, keep the original path instead */
314 u = orig;
315 orig = NULL;
316 }
91a6489d
LP
317 } else
318 free(t);
9eb977db
KS
319
320 l[k++] = u;
321 }
322
323 l[k] = NULL;
324
325 if (enomem)
326 return NULL;
327
328 return l;
329}
330
112cfb18
MM
331char **path_strv_canonicalize_absolute_uniq(char **l, const char *prefix) {
332
fabe5c0e
LP
333 if (strv_isempty(l))
334 return l;
335
112cfb18 336 if (!path_strv_canonicalize_absolute(l, prefix))
fabe5c0e
LP
337 return NULL;
338
339 return strv_uniq(l);
340}
341
9eb977db
KS
342char *path_kill_slashes(char *path) {
343 char *f, *t;
344 bool slash = false;
345
346 /* Removes redundant inner and trailing slashes. Modifies the
347 * passed string in-place.
348 *
349 * ///foo///bar/ becomes /foo/bar
350 */
351
352 for (f = path, t = path; *f; f++) {
353
354 if (*f == '/') {
355 slash = true;
356 continue;
357 }
358
359 if (slash) {
360 slash = false;
361 *(t++) = '/';
362 }
363
364 *(t++) = *f;
365 }
366
367 /* Special rule, if we are talking of the root directory, a
368 trailing slash is good */
369
370 if (t == path && slash)
371 *(t++) = '/';
372
373 *t = 0;
374 return path;
375}
376
424a19f8 377char* path_startswith(const char *path, const char *prefix) {
9eb977db
KS
378 assert(path);
379 assert(prefix);
380
381 if ((path[0] == '/') != (prefix[0] == '/'))
424a19f8 382 return NULL;
9eb977db
KS
383
384 for (;;) {
385 size_t a, b;
386
387 path += strspn(path, "/");
388 prefix += strspn(prefix, "/");
389
390 if (*prefix == 0)
424a19f8 391 return (char*) path;
9eb977db
KS
392
393 if (*path == 0)
424a19f8 394 return NULL;
9eb977db
KS
395
396 a = strcspn(path, "/");
397 b = strcspn(prefix, "/");
398
399 if (a != b)
424a19f8 400 return NULL;
9eb977db
KS
401
402 if (memcmp(path, prefix, a) != 0)
424a19f8 403 return NULL;
9eb977db
KS
404
405 path += a;
406 prefix += b;
407 }
408}
409
410bool path_equal(const char *a, const char *b) {
411 assert(a);
412 assert(b);
413
414 if ((a[0] == '/') != (b[0] == '/'))
415 return false;
416
417 for (;;) {
418 size_t j, k;
419
420 a += strspn(a, "/");
421 b += strspn(b, "/");
422
423 if (*a == 0 && *b == 0)
424 return true;
425
426 if (*a == 0 || *b == 0)
427 return false;
428
429 j = strcspn(a, "/");
430 k = strcspn(b, "/");
431
432 if (j != k)
433 return false;
434
435 if (memcmp(a, b, j) != 0)
436 return false;
437
438 a += j;
439 b += k;
440 }
441}
442
443int path_is_mount_point(const char *t, bool allow_symlink) {
21749924
LP
444
445 union file_handle_union h = {
446 .handle.handle_bytes = MAX_HANDLE_SZ
447 };
448
cde9cb34 449 int mount_id, mount_id_parent;
21749924 450 char *parent;
1640a0b6 451 struct stat a, b;
21749924 452 int r;
9eb977db 453
cde9cb34
LP
454 /* We are not actually interested in the file handles, but
455 * name_to_handle_at() also passes us the mount ID, hence use
456 * it but throw the handle away */
457
458 if (path_equal(t, "/"))
459 return 1;
9eb977db 460
21749924 461 r = name_to_handle_at(AT_FDCWD, t, &h.handle, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
9eb977db 462 if (r < 0) {
21749924 463 if (IN_SET(errno, ENOSYS, EOPNOTSUPP))
fa125f4e 464 /* This kernel or file system does not support
1640a0b6
LP
465 * name_to_handle_at(), hence fallback to the
466 * traditional stat() logic */
467 goto fallback;
468
9eb977db
KS
469 if (errno == ENOENT)
470 return 0;
471
472 return -errno;
473 }
474
475 r = path_get_parent(t, &parent);
476 if (r < 0)
477 return r;
478
21749924
LP
479 h.handle.handle_bytes = MAX_HANDLE_SZ;
480 r = name_to_handle_at(AT_FDCWD, parent, &h.handle, &mount_id_parent, 0);
9eb977db 481 free(parent);
1640a0b6
LP
482 if (r < 0) {
483 /* The parent can't do name_to_handle_at() but the
484 * directory we are interested in can? If so, it must
485 * be a mount point */
21749924 486 if (errno == EOPNOTSUPP)
1640a0b6
LP
487 return 1;
488
489 return -errno;
490 }
491
492 return mount_id != mount_id_parent;
493
494fallback:
495 if (allow_symlink)
496 r = stat(t, &a);
497 else
f408b8f1 498 r = lstat(t, &a);
1640a0b6 499
8ac75493
MM
500 if (r < 0) {
501 if (errno == ENOENT)
502 return 0;
503
9eb977db 504 return -errno;
8ac75493 505 }
9eb977db 506
1640a0b6
LP
507 r = path_get_parent(t, &parent);
508 if (r < 0)
509 return r;
cde9cb34 510
1640a0b6
LP
511 r = lstat(parent, &b);
512 free(parent);
1640a0b6
LP
513 if (r < 0)
514 return -errno;
515
516 return a.st_dev != b.st_dev;
9eb977db
KS
517}
518
519int path_is_read_only_fs(const char *path) {
520 struct statvfs st;
521
522 assert(path);
523
524 if (statvfs(path, &st) < 0)
525 return -errno;
526
527 return !!(st.f_flag & ST_RDONLY);
528}
66060897
LP
529
530int path_is_os_tree(const char *path) {
531 char *p;
532 int r;
533
534 /* We use /etc/os-release as flag file if something is an OS */
535
536 p = strappenda(path, "/etc/os-release");
537 r = access(p, F_OK);
538
539 return r < 0 ? 0 : 1;
540}
c9d954b2
ZJS
541
542int find_binary(const char *name, char **filename) {
543 assert(name);
4087cb9e 544
c9d954b2 545 if (strchr(name, '/')) {
b972115c
ZJS
546 if (access(name, X_OK) < 0)
547 return -errno;
548
eb66db55
MG
549 if (filename) {
550 char *p;
c9d954b2 551
b972115c 552 p = path_make_absolute_cwd(name);
eb66db55
MG
553 if (!p)
554 return -ENOMEM;
b972115c 555
eb66db55
MG
556 *filename = p;
557 }
c9d954b2 558
c9d954b2
ZJS
559 return 0;
560 } else {
561 const char *path;
562 char *state, *w;
563 size_t l;
564
565 /**
566 * Plain getenv, not secure_getenv, because we want
567 * to actually allow the user to pick the binary.
568 */
569 path = getenv("PATH");
570 if (!path)
571 path = DEFAULT_PATH;
572
573 FOREACH_WORD_SEPARATOR(w, l, path, ":", state) {
eb66db55 574 _cleanup_free_ char *p = NULL;
c9d954b2 575
4bcc8c3c 576 if (asprintf(&p, "%.*s/%s", (int) l, w, name) < 0)
c9d954b2
ZJS
577 return -ENOMEM;
578
eb66db55 579 if (access(p, X_OK) < 0)
c9d954b2 580 continue;
c9d954b2 581
eb66db55 582 if (filename) {
dbb9401d 583 *filename = path_kill_slashes(p);
eb66db55
MG
584 p = NULL;
585 }
c9d954b2
ZJS
586
587 return 0;
588 }
589
590 return -ENOENT;
591 }
592}
8e184852 593
2ad8416d 594bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
8e184852 595 bool changed = false;
2ad8416d 596 const char* const* i;
8e184852 597
97f2d76d
TG
598 assert(timestamp);
599
8e184852 600 if (paths == NULL)
4087cb9e 601 return false;
8e184852 602
4087cb9e 603 STRV_FOREACH(i, paths) {
8e184852 604 struct stat stats;
4087cb9e 605 usec_t u;
8e184852 606
4087cb9e 607 if (stat(*i, &stats) < 0)
8e184852
TG
608 continue;
609
4087cb9e
LP
610 u = timespec_load(&stats.st_mtim);
611
97f2d76d 612 /* first check */
4087cb9e 613 if (*timestamp >= u)
8e184852
TG
614 continue;
615
9f6445e3 616 log_debug("timestamp of '%s' changed", *i);
8e184852
TG
617
618 /* update timestamp */
4087cb9e
LP
619 if (update) {
620 *timestamp = u;
621 changed = true;
622 } else
623 return true;
8e184852 624 }
4087cb9e 625
8e184852
TG
626 return changed;
627}
eb66db55
MG
628
629int fsck_exists(const char *fstype) {
630 const char *checker;
631
632 checker = strappenda("fsck.", fstype);
633 return find_binary(checker, NULL);
634}