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