]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
9eb977db KS |
2 | /*** |
3 | This file is part of systemd. | |
4 | ||
5 | Copyright 2010-2012 Lennart Poettering | |
9eb977db KS |
6 | ***/ |
7 | ||
9eb977db | 8 | #include <errno.h> |
11c3a366 | 9 | #include <limits.h> |
07630cea LP |
10 | #include <stdio.h> |
11 | #include <stdlib.h> | |
12 | #include <string.h> | |
11c3a366 | 13 | #include <sys/stat.h> |
07630cea | 14 | #include <unistd.h> |
9eb977db | 15 | |
5f311f8c LP |
16 | /* When we include libgen.h because we need dirname() we immediately |
17 | * undefine basename() since libgen.h defines it as a macro to the | |
18 | * POSIX version which is really broken. We prefer GNU basename(). */ | |
19 | #include <libgen.h> | |
20 | #undef basename | |
21 | ||
b5efdb8a | 22 | #include "alloc-util.h" |
93cc7779 | 23 | #include "extract-word.h" |
f4f15635 | 24 | #include "fs-util.h" |
5a46d55f | 25 | #include "glob-util.h" |
9eb977db | 26 | #include "log.h" |
07630cea LP |
27 | #include "macro.h" |
28 | #include "missing.h" | |
5a46d55f | 29 | #include "parse-util.h" |
3ffd4af2 | 30 | #include "path-util.h" |
8fcde012 | 31 | #include "stat-util.h" |
07630cea | 32 | #include "string-util.h" |
9eb977db | 33 | #include "strv.h" |
93cc7779 | 34 | #include "time-util.h" |
9eb977db KS |
35 | |
36 | bool path_is_absolute(const char *p) { | |
37 | return p[0] == '/'; | |
38 | } | |
39 | ||
40 | bool is_path(const char *p) { | |
41 | return !!strchr(p, '/'); | |
42 | } | |
43 | ||
0f474365 | 44 | int path_split_and_make_absolute(const char *p, char ***ret) { |
9eb977db | 45 | char **l; |
0f474365 LP |
46 | int r; |
47 | ||
9eb977db | 48 | assert(p); |
0f474365 | 49 | assert(ret); |
9eb977db | 50 | |
116cc028 ZJS |
51 | l = strv_split(p, ":"); |
52 | if (!l) | |
ce9d6bcf | 53 | return -ENOMEM; |
9eb977db | 54 | |
0f474365 LP |
55 | r = path_strv_make_absolute_cwd(l); |
56 | if (r < 0) { | |
9eb977db | 57 | strv_free(l); |
0f474365 | 58 | return r; |
9eb977db KS |
59 | } |
60 | ||
0f474365 LP |
61 | *ret = l; |
62 | return r; | |
9eb977db KS |
63 | } |
64 | ||
65 | char *path_make_absolute(const char *p, const char *prefix) { | |
66 | assert(p); | |
67 | ||
68 | /* Makes every item in the list an absolute path by prepending | |
69 | * the prefix, if specified and necessary */ | |
70 | ||
81cce8de | 71 | if (path_is_absolute(p) || isempty(prefix)) |
9eb977db KS |
72 | return strdup(p); |
73 | ||
cddd2ce1 LP |
74 | if (endswith(prefix, "/")) |
75 | return strjoin(prefix, p); | |
76 | else | |
77 | return strjoin(prefix, "/", p); | |
9eb977db KS |
78 | } |
79 | ||
a2556d25 LP |
80 | int safe_getcwd(char **ret) { |
81 | char *cwd; | |
82 | ||
83 | cwd = get_current_dir_name(); | |
84 | if (!cwd) | |
85 | return negative_errno(); | |
86 | ||
87 | /* Let's make sure the directory is really absolute, to protect us from the logic behind | |
88 | * CVE-2018-1000001 */ | |
89 | if (cwd[0] != '/') { | |
90 | free(cwd); | |
91 | return -ENOMEDIUM; | |
92 | } | |
93 | ||
94 | *ret = cwd; | |
95 | return 0; | |
96 | } | |
97 | ||
0f474365 LP |
98 | int path_make_absolute_cwd(const char *p, char **ret) { |
99 | char *c; | |
d7249575 | 100 | int r; |
9eb977db KS |
101 | |
102 | assert(p); | |
0f474365 | 103 | assert(ret); |
9eb977db KS |
104 | |
105 | /* Similar to path_make_absolute(), but prefixes with the | |
106 | * current working directory. */ | |
107 | ||
108 | if (path_is_absolute(p)) | |
0f474365 LP |
109 | c = strdup(p); |
110 | else { | |
111 | _cleanup_free_ char *cwd = NULL; | |
9eb977db | 112 | |
d7249575 LP |
113 | r = safe_getcwd(&cwd); |
114 | if (r < 0) | |
115 | return r; | |
0f474365 | 116 | |
7aeeb313 LP |
117 | if (endswith(cwd, "/")) |
118 | c = strjoin(cwd, p); | |
119 | else | |
120 | c = strjoin(cwd, "/", p); | |
0f474365 LP |
121 | } |
122 | if (!c) | |
123 | return -ENOMEM; | |
9eb977db | 124 | |
0f474365 LP |
125 | *ret = c; |
126 | return 0; | |
9eb977db KS |
127 | } |
128 | ||
7cb9c51c TK |
129 | int path_make_relative(const char *from_dir, const char *to_path, char **_r) { |
130 | char *r, *p; | |
131 | unsigned n_parents; | |
7cb9c51c TK |
132 | |
133 | assert(from_dir); | |
134 | assert(to_path); | |
135 | assert(_r); | |
136 | ||
137 | /* Strips the common part, and adds ".." elements as necessary. */ | |
138 | ||
139 | if (!path_is_absolute(from_dir)) | |
140 | return -EINVAL; | |
141 | ||
142 | if (!path_is_absolute(to_path)) | |
143 | return -EINVAL; | |
144 | ||
145 | /* Skip the common part. */ | |
146 | for (;;) { | |
2a5beb66 | 147 | size_t a, b; |
7cb9c51c TK |
148 | |
149 | from_dir += strspn(from_dir, "/"); | |
150 | to_path += strspn(to_path, "/"); | |
151 | ||
152 | if (!*from_dir) { | |
153 | if (!*to_path) | |
154 | /* from_dir equals to_path. */ | |
155 | r = strdup("."); | |
156 | else | |
157 | /* from_dir is a parent directory of to_path. */ | |
158 | r = strdup(to_path); | |
7cb9c51c TK |
159 | if (!r) |
160 | return -ENOMEM; | |
161 | ||
5216f599 TK |
162 | path_kill_slashes(r); |
163 | ||
7cb9c51c TK |
164 | *_r = r; |
165 | return 0; | |
166 | } | |
167 | ||
168 | if (!*to_path) | |
169 | break; | |
170 | ||
171 | a = strcspn(from_dir, "/"); | |
172 | b = strcspn(to_path, "/"); | |
173 | ||
174 | if (a != b) | |
175 | break; | |
176 | ||
177 | if (memcmp(from_dir, to_path, a) != 0) | |
178 | break; | |
179 | ||
180 | from_dir += a; | |
181 | to_path += b; | |
182 | } | |
183 | ||
184 | /* If we're here, then "from_dir" has one or more elements that need to | |
185 | * be replaced with "..". */ | |
186 | ||
187 | /* Count the number of necessary ".." elements. */ | |
188 | for (n_parents = 0;;) { | |
2a5beb66 LP |
189 | size_t w; |
190 | ||
7cb9c51c TK |
191 | from_dir += strspn(from_dir, "/"); |
192 | ||
193 | if (!*from_dir) | |
194 | break; | |
195 | ||
2a5beb66 LP |
196 | w = strcspn(from_dir, "/"); |
197 | ||
198 | /* If this includes ".." we can't do a simple series of "..", refuse */ | |
199 | if (w == 2 && from_dir[0] == '.' && from_dir[1] == '.') | |
200 | return -EINVAL; | |
201 | ||
202 | /* Count number of elements, except if they are "." */ | |
203 | if (w != 1 || from_dir[0] != '.') | |
204 | n_parents++; | |
205 | ||
206 | from_dir += w; | |
7cb9c51c TK |
207 | } |
208 | ||
2a5beb66 | 209 | r = new(char, n_parents * 3 + strlen(to_path) + 1); |
7cb9c51c TK |
210 | if (!r) |
211 | return -ENOMEM; | |
212 | ||
2a5beb66 LP |
213 | for (p = r; n_parents > 0; n_parents--) |
214 | p = mempcpy(p, "../", 3); | |
7cb9c51c | 215 | |
5216f599 TK |
216 | strcpy(p, to_path); |
217 | path_kill_slashes(r); | |
7cb9c51c TK |
218 | |
219 | *_r = r; | |
220 | return 0; | |
221 | } | |
222 | ||
0f474365 | 223 | int path_strv_make_absolute_cwd(char **l) { |
9eb977db | 224 | char **s; |
0f474365 | 225 | int r; |
9eb977db KS |
226 | |
227 | /* Goes through every item in the string list and makes it | |
228 | * absolute. This works in place and won't rollback any | |
229 | * changes on failure. */ | |
230 | ||
231 | STRV_FOREACH(s, l) { | |
232 | char *t; | |
233 | ||
0f474365 LP |
234 | r = path_make_absolute_cwd(*s, &t); |
235 | if (r < 0) | |
236 | return r; | |
9eb977db | 237 | |
32a8f700 ZJS |
238 | path_kill_slashes(t); |
239 | free_and_replace(*s, t); | |
9eb977db KS |
240 | } |
241 | ||
0f474365 | 242 | return 0; |
9eb977db KS |
243 | } |
244 | ||
e1873695 | 245 | char **path_strv_resolve(char **l, const char *root) { |
9eb977db KS |
246 | char **s; |
247 | unsigned k = 0; | |
248 | bool enomem = false; | |
e1873695 | 249 | int r; |
9eb977db KS |
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) { | |
12ed81d9 | 259 | _cleanup_free_ char *orig = NULL; |
e1873695 | 260 | char *t, *u; |
9eb977db | 261 | |
12ed81d9 ZJS |
262 | if (!path_is_absolute(*s)) { |
263 | free(*s); | |
9eb977db | 264 | continue; |
12ed81d9 | 265 | } |
112cfb18 | 266 | |
e1873695 | 267 | if (root) { |
12ed81d9 | 268 | orig = *s; |
e1873695 | 269 | t = prefix_root(root, orig); |
112cfb18 MM |
270 | if (!t) { |
271 | enomem = true; | |
272 | continue; | |
273 | } | |
12ed81d9 | 274 | } else |
112cfb18 | 275 | t = *s; |
9eb977db | 276 | |
c4f4fce7 | 277 | r = chase_symlinks(t, root, 0, &u); |
e1873695 LP |
278 | if (r == -ENOENT) { |
279 | if (root) { | |
ae2a15bc | 280 | u = TAKE_PTR(orig); |
874310b7 | 281 | free(t); |
e1873695 LP |
282 | } else |
283 | u = t; | |
284 | } else if (r < 0) { | |
285 | free(t); | |
874310b7 | 286 | |
e1873695 LP |
287 | if (r == -ENOMEM) |
288 | enomem = true; | |
289 | ||
290 | continue; | |
291 | } else if (root) { | |
12ed81d9 ZJS |
292 | char *x; |
293 | ||
294 | free(t); | |
e1873695 | 295 | x = path_startswith(u, root); |
12ed81d9 ZJS |
296 | if (x) { |
297 | /* restore the slash if it was lost */ | |
298 | if (!startswith(x, "/")) | |
299 | *(--x) = '/'; | |
300 | ||
301 | t = strdup(x); | |
302 | free(u); | |
303 | if (!t) { | |
304 | enomem = true; | |
305 | continue; | |
306 | } | |
307 | u = t; | |
308 | } else { | |
309 | /* canonicalized path goes outside of | |
310 | * prefix, keep the original path instead */ | |
3b319885 | 311 | free_and_replace(u, orig); |
12ed81d9 | 312 | } |
91a6489d LP |
313 | } else |
314 | free(t); | |
9eb977db KS |
315 | |
316 | l[k++] = u; | |
317 | } | |
318 | ||
319 | l[k] = NULL; | |
320 | ||
321 | if (enomem) | |
322 | return NULL; | |
323 | ||
324 | return l; | |
325 | } | |
326 | ||
e1873695 | 327 | char **path_strv_resolve_uniq(char **l, const char *root) { |
112cfb18 | 328 | |
fabe5c0e LP |
329 | if (strv_isempty(l)) |
330 | return l; | |
331 | ||
e1873695 | 332 | if (!path_strv_resolve(l, root)) |
fabe5c0e LP |
333 | return NULL; |
334 | ||
335 | return strv_uniq(l); | |
336 | } | |
337 | ||
9eb977db KS |
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 | ||
424a19f8 | 373 | char* path_startswith(const char *path, const char *prefix) { |
9eb977db KS |
374 | assert(path); |
375 | assert(prefix); | |
376 | ||
0470289b ZJS |
377 | /* Returns a pointer to the start of the first component after the parts matched by |
378 | * the prefix, iff | |
379 | * - both paths are absolute or both paths are relative, | |
380 | * and | |
381 | * - each component in prefix in turn matches a component in path at the same position. | |
382 | * An empty string will be returned when the prefix and path are equivalent. | |
383 | * | |
384 | * Returns NULL otherwise. | |
385 | */ | |
386 | ||
9eb977db | 387 | if ((path[0] == '/') != (prefix[0] == '/')) |
424a19f8 | 388 | return NULL; |
9eb977db KS |
389 | |
390 | for (;;) { | |
391 | size_t a, b; | |
392 | ||
393 | path += strspn(path, "/"); | |
394 | prefix += strspn(prefix, "/"); | |
395 | ||
396 | if (*prefix == 0) | |
424a19f8 | 397 | return (char*) path; |
9eb977db KS |
398 | |
399 | if (*path == 0) | |
424a19f8 | 400 | return NULL; |
9eb977db KS |
401 | |
402 | a = strcspn(path, "/"); | |
403 | b = strcspn(prefix, "/"); | |
404 | ||
405 | if (a != b) | |
424a19f8 | 406 | return NULL; |
9eb977db KS |
407 | |
408 | if (memcmp(path, prefix, a) != 0) | |
424a19f8 | 409 | return NULL; |
9eb977db KS |
410 | |
411 | path += a; | |
412 | prefix += b; | |
413 | } | |
414 | } | |
415 | ||
2230852b MS |
416 | int path_compare(const char *a, const char *b) { |
417 | int d; | |
418 | ||
9eb977db KS |
419 | assert(a); |
420 | assert(b); | |
421 | ||
2230852b MS |
422 | /* A relative path and an abolute path must not compare as equal. |
423 | * Which one is sorted before the other does not really matter. | |
424 | * Here a relative path is ordered before an absolute path. */ | |
425 | d = (a[0] == '/') - (b[0] == '/'); | |
373cd63a | 426 | if (d != 0) |
2230852b | 427 | return d; |
9eb977db KS |
428 | |
429 | for (;;) { | |
430 | size_t j, k; | |
431 | ||
432 | a += strspn(a, "/"); | |
433 | b += strspn(b, "/"); | |
434 | ||
435 | if (*a == 0 && *b == 0) | |
2230852b | 436 | return 0; |
9eb977db | 437 | |
2230852b MS |
438 | /* Order prefixes first: "/foo" before "/foo/bar" */ |
439 | if (*a == 0) | |
440 | return -1; | |
441 | if (*b == 0) | |
442 | return 1; | |
9eb977db KS |
443 | |
444 | j = strcspn(a, "/"); | |
445 | k = strcspn(b, "/"); | |
446 | ||
2230852b MS |
447 | /* Alphabetical sort: "/foo/aaa" before "/foo/b" */ |
448 | d = memcmp(a, b, MIN(j, k)); | |
373cd63a | 449 | if (d != 0) |
2230852b | 450 | return (d > 0) - (d < 0); /* sign of d */ |
9eb977db | 451 | |
2230852b MS |
452 | /* Sort "/foo/a" before "/foo/aaa" */ |
453 | d = (j > k) - (j < k); /* sign of (j - k) */ | |
373cd63a | 454 | if (d != 0) |
2230852b | 455 | return d; |
9eb977db KS |
456 | |
457 | a += j; | |
458 | b += k; | |
459 | } | |
460 | } | |
461 | ||
2230852b MS |
462 | bool path_equal(const char *a, const char *b) { |
463 | return path_compare(a, b) == 0; | |
464 | } | |
465 | ||
e3f791a2 ZJS |
466 | bool path_equal_or_files_same(const char *a, const char *b, int flags) { |
467 | return path_equal(a, b) || files_same(a, b, flags) > 0; | |
c78e47a6 MS |
468 | } |
469 | ||
0c6ea3a4 ZJS |
470 | char* path_join(const char *root, const char *path, const char *rest) { |
471 | assert(path); | |
472 | ||
473 | if (!isempty(root)) | |
bc854dc7 | 474 | return strjoin(root, endswith(root, "/") ? "" : "/", |
0c6ea3a4 | 475 | path[0] == '/' ? path+1 : path, |
bc854dc7 | 476 | rest ? (endswith(path, "/") ? "" : "/") : NULL, |
605405c6 | 477 | rest && rest[0] == '/' ? rest+1 : rest); |
0c6ea3a4 ZJS |
478 | else |
479 | return strjoin(path, | |
bc854dc7 | 480 | rest ? (endswith(path, "/") ? "" : "/") : NULL, |
605405c6 | 481 | rest && rest[0] == '/' ? rest+1 : rest); |
0c6ea3a4 ZJS |
482 | } |
483 | ||
85eca92e LP |
484 | int find_binary(const char *name, char **ret) { |
485 | int last_error, r; | |
486 | const char *p; | |
487 | ||
c9d954b2 | 488 | assert(name); |
4087cb9e | 489 | |
571d0134 | 490 | if (is_path(name)) { |
85eca92e | 491 | if (access(name, X_OK) < 0) |
b972115c ZJS |
492 | return -errno; |
493 | ||
85eca92e | 494 | if (ret) { |
0f474365 LP |
495 | r = path_make_absolute_cwd(name, ret); |
496 | if (r < 0) | |
497 | return r; | |
eb66db55 | 498 | } |
c9d954b2 | 499 | |
c9d954b2 | 500 | return 0; |
85eca92e | 501 | } |
c9d954b2 | 502 | |
85eca92e LP |
503 | /** |
504 | * Plain getenv, not secure_getenv, because we want | |
505 | * to actually allow the user to pick the binary. | |
506 | */ | |
507 | p = getenv("PATH"); | |
508 | if (!p) | |
509 | p = DEFAULT_PATH; | |
510 | ||
511 | last_error = -ENOENT; | |
512 | ||
513 | for (;;) { | |
514 | _cleanup_free_ char *j = NULL, *element = NULL; | |
515 | ||
516 | r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS); | |
517 | if (r < 0) | |
518 | return r; | |
519 | if (r == 0) | |
520 | break; | |
521 | ||
0f474365 LP |
522 | if (!path_is_absolute(element)) |
523 | continue; | |
524 | ||
605405c6 | 525 | j = strjoin(element, "/", name); |
85eca92e LP |
526 | if (!j) |
527 | return -ENOMEM; | |
528 | ||
529 | if (access(j, X_OK) >= 0) { | |
530 | /* Found it! */ | |
c9d954b2 | 531 | |
85eca92e LP |
532 | if (ret) { |
533 | *ret = path_kill_slashes(j); | |
534 | j = NULL; | |
eb66db55 | 535 | } |
c9d954b2 ZJS |
536 | |
537 | return 0; | |
538 | } | |
539 | ||
85eca92e | 540 | last_error = -errno; |
c9d954b2 | 541 | } |
85eca92e LP |
542 | |
543 | return last_error; | |
c9d954b2 | 544 | } |
8e184852 | 545 | |
2ad8416d | 546 | bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) { |
8e184852 | 547 | bool changed = false; |
2ad8416d | 548 | const char* const* i; |
8e184852 | 549 | |
97f2d76d TG |
550 | assert(timestamp); |
551 | ||
234519ae | 552 | if (!paths) |
4087cb9e | 553 | return false; |
8e184852 | 554 | |
4087cb9e | 555 | STRV_FOREACH(i, paths) { |
8e184852 | 556 | struct stat stats; |
4087cb9e | 557 | usec_t u; |
8e184852 | 558 | |
4087cb9e | 559 | if (stat(*i, &stats) < 0) |
8e184852 TG |
560 | continue; |
561 | ||
4087cb9e LP |
562 | u = timespec_load(&stats.st_mtim); |
563 | ||
97f2d76d | 564 | /* first check */ |
4087cb9e | 565 | if (*timestamp >= u) |
8e184852 TG |
566 | continue; |
567 | ||
9f6445e3 | 568 | log_debug("timestamp of '%s' changed", *i); |
8e184852 TG |
569 | |
570 | /* update timestamp */ | |
4087cb9e LP |
571 | if (update) { |
572 | *timestamp = u; | |
573 | changed = true; | |
574 | } else | |
575 | return true; | |
8e184852 | 576 | } |
4087cb9e | 577 | |
8e184852 TG |
578 | return changed; |
579 | } | |
eb66db55 | 580 | |
5bcd08db | 581 | static int binary_is_good(const char *binary) { |
571d0134 | 582 | _cleanup_free_ char *p = NULL, *d = NULL; |
571d0134 | 583 | int r; |
eb66db55 | 584 | |
85eca92e LP |
585 | r = find_binary(binary, &p); |
586 | if (r == -ENOENT) | |
587 | return 0; | |
571d0134 LP |
588 | if (r < 0) |
589 | return r; | |
590 | ||
061df014 | 591 | /* An fsck that is linked to /bin/true is a non-existent |
571d0134 LP |
592 | * fsck */ |
593 | ||
594 | r = readlink_malloc(p, &d); | |
85eca92e LP |
595 | if (r == -EINVAL) /* not a symlink */ |
596 | return 1; | |
597 | if (r < 0) | |
598 | return r; | |
571d0134 | 599 | |
3ae5990c ZJS |
600 | return !PATH_IN_SET(d, "true" |
601 | "/bin/true", | |
602 | "/usr/bin/true", | |
603 | "/dev/null"); | |
eb66db55 | 604 | } |
1d13f648 | 605 | |
5bcd08db LP |
606 | int fsck_exists(const char *fstype) { |
607 | const char *checker; | |
608 | ||
85eca92e | 609 | assert(fstype); |
5bcd08db | 610 | |
85eca92e LP |
611 | if (streq(fstype, "auto")) |
612 | return -EINVAL; | |
613 | ||
614 | checker = strjoina("fsck.", fstype); | |
5bcd08db LP |
615 | return binary_is_good(checker); |
616 | } | |
617 | ||
618 | int mkfs_exists(const char *fstype) { | |
619 | const char *mkfs; | |
620 | ||
85eca92e | 621 | assert(fstype); |
5bcd08db | 622 | |
85eca92e LP |
623 | if (streq(fstype, "auto")) |
624 | return -EINVAL; | |
625 | ||
626 | mkfs = strjoina("mkfs.", fstype); | |
5bcd08db LP |
627 | return binary_is_good(mkfs); |
628 | } | |
629 | ||
1d13f648 LP |
630 | char *prefix_root(const char *root, const char *path) { |
631 | char *n, *p; | |
632 | size_t l; | |
633 | ||
634 | /* If root is passed, prefixes path with it. Otherwise returns | |
635 | * it as is. */ | |
636 | ||
637 | assert(path); | |
638 | ||
639 | /* First, drop duplicate prefixing slashes from the path */ | |
640 | while (path[0] == '/' && path[1] == '/') | |
641 | path++; | |
642 | ||
643 | if (isempty(root) || path_equal(root, "/")) | |
644 | return strdup(path); | |
645 | ||
646 | l = strlen(root) + 1 + strlen(path) + 1; | |
647 | ||
648 | n = new(char, l); | |
649 | if (!n) | |
650 | return NULL; | |
651 | ||
652 | p = stpcpy(n, root); | |
653 | ||
654 | while (p > n && p[-1] == '/') | |
655 | p--; | |
656 | ||
657 | if (path[0] != '/') | |
658 | *(p++) = '/'; | |
659 | ||
660 | strcpy(p, path); | |
661 | return n; | |
662 | } | |
0f03c2a4 LP |
663 | |
664 | int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) { | |
665 | char *p; | |
666 | int r; | |
667 | ||
668 | /* | |
669 | * This function is intended to be used in command line | |
670 | * parsers, to handle paths that are passed in. It makes the | |
671 | * path absolute, and reduces it to NULL if omitted or | |
672 | * root (the latter optionally). | |
673 | * | |
674 | * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON | |
675 | * SUCCESS! Hence, do not pass in uninitialized pointers. | |
676 | */ | |
677 | ||
678 | if (isempty(path)) { | |
679 | *arg = mfree(*arg); | |
680 | return 0; | |
681 | } | |
682 | ||
683 | r = path_make_absolute_cwd(path, &p); | |
684 | if (r < 0) | |
685 | return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path); | |
686 | ||
687 | path_kill_slashes(p); | |
688 | if (suppress_root && path_equal(p, "/")) | |
689 | p = mfree(p); | |
690 | ||
691 | free(*arg); | |
692 | *arg = p; | |
693 | return 0; | |
694 | } | |
5f311f8c LP |
695 | |
696 | char* dirname_malloc(const char *path) { | |
697 | char *d, *dir, *dir2; | |
698 | ||
699 | assert(path); | |
700 | ||
701 | d = strdup(path); | |
702 | if (!d) | |
703 | return NULL; | |
704 | ||
705 | dir = dirname(d); | |
706 | assert(dir); | |
707 | ||
708 | if (dir == d) | |
709 | return d; | |
710 | ||
711 | dir2 = strdup(dir); | |
712 | free(d); | |
713 | ||
714 | return dir2; | |
715 | } | |
bb15fafe | 716 | |
b12d25a8 ZJS |
717 | const char *last_path_component(const char *path) { |
718 | /* Finds the last component of the path, preserving the | |
719 | * optional trailing slash that signifies a directory. | |
720 | * a/b/c → c | |
721 | * a/b/c/ → c/ | |
722 | * / → / | |
723 | * // → / | |
724 | * /foo/a → a | |
725 | * /foo/a/ → a/ | |
726 | * This is different than basename, which returns "" when | |
727 | * a trailing slash is present. | |
728 | */ | |
729 | ||
730 | unsigned l, k; | |
731 | ||
732 | l = k = strlen(path); | |
69f9ccf1 ZJS |
733 | if (l == 0) /* special case — an empty string */ |
734 | return path; | |
735 | ||
b12d25a8 ZJS |
736 | while (k > 0 && path[k-1] == '/') |
737 | k--; | |
738 | ||
739 | if (k == 0) /* the root directory */ | |
740 | return path + l - 1; | |
741 | ||
742 | while (k > 0 && path[k-1] != '/') | |
743 | k--; | |
744 | ||
745 | return path + k; | |
746 | } | |
747 | ||
bb15fafe LP |
748 | bool filename_is_valid(const char *p) { |
749 | const char *e; | |
750 | ||
751 | if (isempty(p)) | |
752 | return false; | |
753 | ||
49bfc877 | 754 | if (dot_or_dot_dot(p)) |
bb15fafe LP |
755 | return false; |
756 | ||
757 | e = strchrnul(p, '/'); | |
758 | if (*e != 0) | |
759 | return false; | |
760 | ||
761 | if (e - p > FILENAME_MAX) | |
762 | return false; | |
763 | ||
764 | return true; | |
765 | } | |
766 | ||
99be45a4 | 767 | bool path_is_normalized(const char *p) { |
bb15fafe LP |
768 | |
769 | if (isempty(p)) | |
770 | return false; | |
771 | ||
49bfc877 LP |
772 | if (dot_or_dot_dot(p)) |
773 | return false; | |
774 | ||
775 | if (startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) | |
bb15fafe LP |
776 | return false; |
777 | ||
778 | if (strlen(p)+1 > PATH_MAX) | |
779 | return false; | |
780 | ||
49bfc877 | 781 | if (startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./")) |
bb15fafe LP |
782 | return false; |
783 | ||
784 | if (strstr(p, "//")) | |
785 | return false; | |
786 | ||
787 | return true; | |
788 | } | |
a0956174 LP |
789 | |
790 | char *file_in_same_dir(const char *path, const char *filename) { | |
791 | char *e, *ret; | |
792 | size_t k; | |
793 | ||
794 | assert(path); | |
795 | assert(filename); | |
796 | ||
797 | /* This removes the last component of path and appends | |
798 | * filename, unless the latter is absolute anyway or the | |
799 | * former isn't */ | |
800 | ||
801 | if (path_is_absolute(filename)) | |
802 | return strdup(filename); | |
803 | ||
804 | e = strrchr(path, '/'); | |
805 | if (!e) | |
806 | return strdup(filename); | |
807 | ||
808 | k = strlen(filename); | |
809 | ret = new(char, (e + 1 - path) + k + 1); | |
810 | if (!ret) | |
811 | return NULL; | |
812 | ||
813 | memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1); | |
814 | return ret; | |
815 | } | |
816 | ||
55cdd057 ZJS |
817 | bool hidden_or_backup_file(const char *filename) { |
818 | const char *p; | |
a0956174 | 819 | |
a0956174 LP |
820 | assert(filename); |
821 | ||
55cdd057 ZJS |
822 | if (filename[0] == '.' || |
823 | streq(filename, "lost+found") || | |
824 | streq(filename, "aquota.user") || | |
825 | streq(filename, "aquota.group") || | |
826 | endswith(filename, "~")) | |
a0956174 LP |
827 | return true; |
828 | ||
55cdd057 ZJS |
829 | p = strrchr(filename, '.'); |
830 | if (!p) | |
831 | return false; | |
832 | ||
941060bf LP |
833 | /* Please, let's not add more entries to the list below. If external projects think it's a good idea to come up |
834 | * with always new suffixes and that everybody else should just adjust to that, then it really should be on | |
835 | * them. Hence, in future, let's not add any more entries. Instead, let's ask those packages to instead adopt | |
836 | * one of the generic suffixes/prefixes for hidden files or backups, possibly augmented with an additional | |
837 | * string. Specifically: there's now: | |
838 | * | |
839 | * The generic suffixes "~" and ".bak" for backup files | |
840 | * The generic prefix "." for hidden files | |
841 | * | |
94a0ef6e ZJS |
842 | * Thus, if a new package manager "foopkg" wants its own set of ".foopkg-new", ".foopkg-old", ".foopkg-dist" |
843 | * or so registered, let's refuse that and ask them to use ".foopkg.new", ".foopkg.old" or ".foopkg~" instead. | |
941060bf LP |
844 | */ |
845 | ||
55cdd057 ZJS |
846 | return STR_IN_SET(p + 1, |
847 | "rpmnew", | |
848 | "rpmsave", | |
849 | "rpmorig", | |
850 | "dpkg-old", | |
851 | "dpkg-new", | |
852 | "dpkg-tmp", | |
853 | "dpkg-dist", | |
854 | "dpkg-bak", | |
855 | "dpkg-backup", | |
856 | "dpkg-remove", | |
857 | "ucf-new", | |
858 | "ucf-old", | |
859 | "ucf-dist", | |
941060bf | 860 | "swp", |
94a0ef6e ZJS |
861 | "bak", |
862 | "old", | |
863 | "new"); | |
a0956174 LP |
864 | } |
865 | ||
866 | bool is_device_path(const char *path) { | |
867 | ||
868 | /* Returns true on paths that refer to a device, either in | |
869 | * sysfs or in /dev */ | |
870 | ||
3ccb8862 ZJS |
871 | return path_startswith(path, "/dev/") || |
872 | path_startswith(path, "/sys/"); | |
873 | } | |
874 | ||
875 | bool is_deviceallow_pattern(const char *path) { | |
876 | return path_startswith(path, "/dev/") || | |
877 | startswith(path, "block-") || | |
878 | startswith(path, "char-"); | |
a0956174 | 879 | } |
5a46d55f ZJS |
880 | |
881 | int systemd_installation_has_version(const char *root, unsigned minimal_version) { | |
882 | const char *pattern; | |
883 | int r; | |
884 | ||
885 | /* Try to guess if systemd installation is later than the specified version. This | |
886 | * is hacky and likely to yield false negatives, particularly if the installation | |
887 | * is non-standard. False positives should be relatively rare. | |
888 | */ | |
889 | ||
890 | NULSTR_FOREACH(pattern, | |
891 | /* /lib works for systems without usr-merge, and for systems with a sane | |
892 | * usr-merge, where /lib is a symlink to /usr/lib. /usr/lib is necessary | |
893 | * for Gentoo which does a merge without making /lib a symlink. | |
894 | */ | |
895 | "lib/systemd/libsystemd-shared-*.so\0" | |
28faeda4 LP |
896 | "lib64/systemd/libsystemd-shared-*.so\0" |
897 | "usr/lib/systemd/libsystemd-shared-*.so\0" | |
898 | "usr/lib64/systemd/libsystemd-shared-*.so\0") { | |
5a46d55f ZJS |
899 | |
900 | _cleanup_strv_free_ char **names = NULL; | |
901 | _cleanup_free_ char *path = NULL; | |
902 | char *c, **name; | |
903 | ||
904 | path = prefix_root(root, pattern); | |
905 | if (!path) | |
906 | return -ENOMEM; | |
907 | ||
908 | r = glob_extend(&names, path); | |
909 | if (r == -ENOENT) | |
910 | continue; | |
911 | if (r < 0) | |
912 | return r; | |
913 | ||
914 | assert_se((c = endswith(path, "*.so"))); | |
915 | *c = '\0'; /* truncate the glob part */ | |
916 | ||
917 | STRV_FOREACH(name, names) { | |
918 | /* This is most likely to run only once, hence let's not optimize anything. */ | |
919 | char *t, *t2; | |
920 | unsigned version; | |
921 | ||
922 | t = startswith(*name, path); | |
923 | if (!t) | |
924 | continue; | |
925 | ||
926 | t2 = endswith(t, ".so"); | |
927 | if (!t2) | |
928 | continue; | |
929 | ||
930 | t2[0] = '\0'; /* truncate the suffix */ | |
931 | ||
932 | r = safe_atou(t, &version); | |
933 | if (r < 0) { | |
934 | log_debug_errno(r, "Found libsystemd shared at \"%s.so\", but failed to parse version: %m", *name); | |
935 | continue; | |
936 | } | |
937 | ||
938 | log_debug("Found libsystemd shared at \"%s.so\", version %u (%s).", | |
939 | *name, version, | |
940 | version >= minimal_version ? "OK" : "too old"); | |
941 | if (version >= minimal_version) | |
942 | return true; | |
943 | } | |
944 | } | |
945 | ||
946 | return false; | |
947 | } | |
49bfc877 LP |
948 | |
949 | bool dot_or_dot_dot(const char *path) { | |
950 | if (!path) | |
951 | return false; | |
952 | if (path[0] != '.') | |
953 | return false; | |
954 | if (path[1] == 0) | |
955 | return true; | |
956 | if (path[1] != '.') | |
957 | return false; | |
958 | ||
959 | return path[2] == 0; | |
960 | } |