]> git.ipfire.org Git - thirdparty/systemd.git/blob - 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
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 char **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
145 t = path_make_absolute_cwd(*s);
146 if (!t)
147 return NULL;
148
149 free(*s);
150 *s = t;
151 }
152
153 return l;
154 }
155
156 char **path_strv_canonicalize_absolute(char **l, const char *prefix) {
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
171 if (!path_is_absolute(*s))
172 continue;
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;
186 }
187
188 errno = 0;
189 u = canonicalize_file_name(t);
190 if (!u) {
191 if (errno == ENOENT)
192 u = t;
193 else {
194 free(t);
195 if (errno == ENOMEM || errno == 0)
196 enomem = true;
197
198 continue;
199 }
200 } else
201 free(t);
202
203 l[k++] = u;
204 }
205
206 l[k] = NULL;
207
208 if (enomem)
209 return NULL;
210
211 return l;
212 }
213
214 char **path_strv_canonicalize_absolute_uniq(char **l, const char *prefix) {
215
216 if (strv_isempty(l))
217 return l;
218
219 if (!path_strv_canonicalize_absolute(l, prefix))
220 return NULL;
221
222 return strv_uniq(l);
223 }
224
225 char *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
260 char* path_startswith(const char *path, const char *prefix) {
261 assert(path);
262 assert(prefix);
263
264 if ((path[0] == '/') != (prefix[0] == '/'))
265 return NULL;
266
267 for (;;) {
268 size_t a, b;
269
270 path += strspn(path, "/");
271 prefix += strspn(prefix, "/");
272
273 if (*prefix == 0)
274 return (char*) path;
275
276 if (*path == 0)
277 return NULL;
278
279 a = strcspn(path, "/");
280 b = strcspn(prefix, "/");
281
282 if (a != b)
283 return NULL;
284
285 if (memcmp(path, prefix, a) != 0)
286 return NULL;
287
288 path += a;
289 prefix += b;
290 }
291 }
292
293 bool 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
326 int path_is_mount_point(const char *t, bool allow_symlink) {
327
328 union file_handle_union h = {
329 .handle.handle_bytes = MAX_HANDLE_SZ
330 };
331
332 int mount_id, mount_id_parent;
333 char *parent;
334 struct stat a, b;
335 int r;
336
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;
343
344 r = name_to_handle_at(AT_FDCWD, t, &h.handle, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
345 if (r < 0) {
346 if (IN_SET(errno, ENOSYS, EOPNOTSUPP))
347 /* This kernel or file system does not support
348 * name_to_handle_at(), hence fallback to the
349 * traditional stat() logic */
350 goto fallback;
351
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
362 h.handle.handle_bytes = MAX_HANDLE_SZ;
363 r = name_to_handle_at(AT_FDCWD, parent, &h.handle, &mount_id_parent, 0);
364 free(parent);
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 */
369 if (errno == EOPNOTSUPP)
370 return 1;
371
372 return -errno;
373 }
374
375 return mount_id != mount_id_parent;
376
377 fallback:
378 if (allow_symlink)
379 r = stat(t, &a);
380 else
381 r = lstat(t, &a);
382
383 if (r < 0) {
384 if (errno == ENOENT)
385 return 0;
386
387 return -errno;
388 }
389
390 r = path_get_parent(t, &parent);
391 if (r < 0)
392 return r;
393
394 r = lstat(parent, &b);
395 free(parent);
396 if (r < 0)
397 return -errno;
398
399 return a.st_dev != b.st_dev;
400 }
401
402 int 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 }
412
413 int 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 }
424
425 int find_binary(const char *name, char **filename) {
426 assert(name);
427
428 if (strchr(name, '/')) {
429 if (access(name, X_OK) < 0)
430 return -errno;
431
432 if (filename) {
433 char *p;
434
435 p = path_make_absolute_cwd(name);
436 if (!p)
437 return -ENOMEM;
438
439 *filename = p;
440 }
441
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) {
457 _cleanup_free_ char *p = NULL;
458
459 if (asprintf(&p, "%.*s/%s", (int) l, w, name) < 0)
460 return -ENOMEM;
461
462 if (access(p, X_OK) < 0)
463 continue;
464
465 if (filename) {
466 *filename = path_kill_slashes(p);
467 p = NULL;
468 }
469
470 return 0;
471 }
472
473 return -ENOENT;
474 }
475 }
476
477 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
478 bool changed = false;
479 const char* const* i;
480
481 assert(timestamp);
482
483 if (paths == NULL)
484 return false;
485
486 STRV_FOREACH(i, paths) {
487 struct stat stats;
488 usec_t u;
489
490 if (stat(*i, &stats) < 0)
491 continue;
492
493 u = timespec_load(&stats.st_mtim);
494
495 /* first check */
496 if (*timestamp >= u)
497 continue;
498
499 log_debug("timestamp of '%s' changed", *i);
500
501 /* update timestamp */
502 if (update) {
503 *timestamp = u;
504 changed = true;
505 } else
506 return true;
507 }
508
509 return changed;
510 }
511
512 int fsck_exists(const char *fstype) {
513 const char *checker;
514
515 checker = strappenda("fsck.", fstype);
516 return find_binary(checker, NULL);
517 }