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