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