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