chiark / gitweb /
core, shared: in deserializing, match same files reached via different paths
[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 strjoin(cwd, "/", p, NULL);
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                                 free(u);
310                                 u = orig;
311                                 orig = NULL;
312                         }
313                 } else
314                         free(t);
315
316                 l[k++] = u;
317         }
318
319         l[k] = NULL;
320
321         if (enomem)
322                 return NULL;
323
324         return l;
325 }
326
327 char **path_strv_resolve_uniq(char **l, const char *prefix) {
328
329         if (strv_isempty(l))
330                 return l;
331
332         if (!path_strv_resolve(l, prefix))
333                 return NULL;
334
335         return strv_uniq(l);
336 }
337
338 char *path_kill_slashes(char *path) {
339         char *f, *t;
340         bool slash = false;
341
342         /* Removes redundant inner and trailing slashes. Modifies the
343          * passed string in-place.
344          *
345          * ///foo///bar/ becomes /foo/bar
346          */
347
348         for (f = path, t = path; *f; f++) {
349
350                 if (*f == '/') {
351                         slash = true;
352                         continue;
353                 }
354
355                 if (slash) {
356                         slash = false;
357                         *(t++) = '/';
358                 }
359
360                 *(t++) = *f;
361         }
362
363         /* Special rule, if we are talking of the root directory, a
364         trailing slash is good */
365
366         if (t == path && slash)
367                 *(t++) = '/';
368
369         *t = 0;
370         return path;
371 }
372
373 char* path_startswith(const char *path, const char *prefix) {
374         assert(path);
375         assert(prefix);
376
377         if ((path[0] == '/') != (prefix[0] == '/'))
378                 return NULL;
379
380         for (;;) {
381                 size_t a, b;
382
383                 path += strspn(path, "/");
384                 prefix += strspn(prefix, "/");
385
386                 if (*prefix == 0)
387                         return (char*) path;
388
389                 if (*path == 0)
390                         return NULL;
391
392                 a = strcspn(path, "/");
393                 b = strcspn(prefix, "/");
394
395                 if (a != b)
396                         return NULL;
397
398                 if (memcmp(path, prefix, a) != 0)
399                         return NULL;
400
401                 path += a;
402                 prefix += b;
403         }
404 }
405
406 bool path_equal(const char *a, const char *b) {
407         assert(a);
408         assert(b);
409
410         if ((a[0] == '/') != (b[0] == '/'))
411                 return false;
412
413         for (;;) {
414                 size_t j, k;
415
416                 a += strspn(a, "/");
417                 b += strspn(b, "/");
418
419                 if (*a == 0 && *b == 0)
420                         return true;
421
422                 if (*a == 0 || *b == 0)
423                         return false;
424
425                 j = strcspn(a, "/");
426                 k = strcspn(b, "/");
427
428                 if (j != k)
429                         return false;
430
431                 if (memcmp(a, b, j) != 0)
432                         return false;
433
434                 a += j;
435                 b += k;
436         }
437 }
438
439 bool path_equal_or_files_same(const char *a, const char *b) {
440         return path_equal(a, b) || files_same(a, b) > 0;
441 }
442
443 char* path_join(const char *root, const char *path, const char *rest) {
444         assert(path);
445
446         if (!isempty(root))
447                 return strjoin(root, endswith(root, "/") ? "" : "/",
448                                path[0] == '/' ? path+1 : path,
449                                rest ? (endswith(path, "/") ? "" : "/") : NULL,
450                                rest && rest[0] == '/' ? rest+1 : rest,
451                                NULL);
452         else
453                 return strjoin(path,
454                                rest ? (endswith(path, "/") ? "" : "/") : NULL,
455                                rest && rest[0] == '/' ? rest+1 : rest,
456                                NULL);
457 }
458
459 int path_is_mount_point(const char *t, bool allow_symlink) {
460
461         union file_handle_union h = FILE_HANDLE_INIT;
462         int mount_id = -1, mount_id_parent = -1;
463         _cleanup_free_ char *parent = NULL;
464         struct stat a, b;
465         int r;
466         bool nosupp = false;
467
468         /* We are not actually interested in the file handles, but
469          * name_to_handle_at() also passes us the mount ID, hence use
470          * it but throw the handle away */
471
472         if (path_equal(t, "/"))
473                 return 1;
474
475         r = name_to_handle_at(AT_FDCWD, t, &h.handle, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
476         if (r < 0) {
477                 if (errno == ENOSYS)
478                         /* This kernel does not support name_to_handle_at()
479                          * fall back to the traditional stat() logic. */
480                         goto fallback;
481                 else if (errno == EOPNOTSUPP)
482                         /* This kernel or file system does not support
483                          * name_to_handle_at(), hence fallback to the
484                          * traditional stat() logic */
485                         nosupp = true;
486                 else if (errno == ENOENT)
487                         return 0;
488                 else
489                         return -errno;
490         }
491
492         r = path_get_parent(t, &parent);
493         if (r < 0)
494                 return r;
495
496         h.handle.handle_bytes = MAX_HANDLE_SZ;
497         r = name_to_handle_at(AT_FDCWD, parent, &h.handle, &mount_id_parent, AT_SYMLINK_FOLLOW);
498         if (r < 0)
499                 if (errno == EOPNOTSUPP)
500                         if (nosupp)
501                                 /* Neither parent nor child do name_to_handle_at()?
502                                    We have no choice but to fall back. */
503                                 goto fallback;
504                         else
505                                 /* The parent can't do name_to_handle_at() but
506                                  * the directory we are interested in can?
507                                  * Or the other way around?
508                                  * If so, it must be a mount point. */
509                                 return 1;
510                 else
511                         return -errno;
512         else
513                 return mount_id != mount_id_parent;
514
515 fallback:
516         if (allow_symlink)
517                 r = stat(t, &a);
518         else
519                 r = lstat(t, &a);
520
521         if (r < 0) {
522                 if (errno == ENOENT)
523                         return 0;
524
525                 return -errno;
526         }
527
528         free(parent);
529         parent = NULL;
530
531         r = path_get_parent(t, &parent);
532         if (r < 0)
533                 return r;
534
535         r = stat(parent, &b);
536         if (r < 0)
537                 return -errno;
538
539         return a.st_dev != b.st_dev;
540 }
541
542 int path_is_read_only_fs(const char *path) {
543         struct statvfs st;
544
545         assert(path);
546
547         if (statvfs(path, &st) < 0)
548                 return -errno;
549
550         if (st.f_flag & ST_RDONLY)
551                 return true;
552
553         /* On NFS, statvfs() might not reflect whether we can actually
554          * write to the remote share. Let's try again with
555          * access(W_OK) which is more reliable, at least sometimes. */
556         if (access(path, W_OK) < 0 && errno == EROFS)
557                 return true;
558
559         return false;
560 }
561
562 int path_is_os_tree(const char *path) {
563         char *p;
564         int r;
565
566         /* We use /usr/lib/os-release as flag file if something is an OS */
567         p = strjoina(path, "/usr/lib/os-release");
568         r = access(p, F_OK);
569
570         if (r >= 0)
571                 return 1;
572
573         /* Also check for the old location in /etc, just in case. */
574         p = strjoina(path, "/etc/os-release");
575         r = access(p, F_OK);
576
577         return r >= 0;
578 }
579
580 int find_binary(const char *name, bool local, char **filename) {
581         assert(name);
582
583         if (is_path(name)) {
584                 if (local && access(name, X_OK) < 0)
585                         return -errno;
586
587                 if (filename) {
588                         char *p;
589
590                         p = path_make_absolute_cwd(name);
591                         if (!p)
592                                 return -ENOMEM;
593
594                         *filename = p;
595                 }
596
597                 return 0;
598         } else {
599                 const char *path;
600                 const char *word, *state;
601                 size_t l;
602
603                 /**
604                  * Plain getenv, not secure_getenv, because we want
605                  * to actually allow the user to pick the binary.
606                  */
607                 path = getenv("PATH");
608                 if (!path)
609                         path = DEFAULT_PATH;
610
611                 FOREACH_WORD_SEPARATOR(word, l, path, ":", state) {
612                         _cleanup_free_ char *p = NULL;
613
614                         if (asprintf(&p, "%.*s/%s", (int) l, word, name) < 0)
615                                 return -ENOMEM;
616
617                         if (access(p, X_OK) < 0)
618                                 continue;
619
620                         if (filename) {
621                                 *filename = path_kill_slashes(p);
622                                 p = NULL;
623                         }
624
625                         return 0;
626                 }
627
628                 return -ENOENT;
629         }
630 }
631
632 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
633         bool changed = false;
634         const char* const* i;
635
636         assert(timestamp);
637
638         if (paths == NULL)
639                 return false;
640
641         STRV_FOREACH(i, paths) {
642                 struct stat stats;
643                 usec_t u;
644
645                 if (stat(*i, &stats) < 0)
646                         continue;
647
648                 u = timespec_load(&stats.st_mtim);
649
650                 /* first check */
651                 if (*timestamp >= u)
652                         continue;
653
654                 log_debug("timestamp of '%s' changed", *i);
655
656                 /* update timestamp */
657                 if (update) {
658                         *timestamp = u;
659                         changed = true;
660                 } else
661                         return true;
662         }
663
664         return changed;
665 }
666
667 int fsck_exists(const char *fstype) {
668         _cleanup_free_ char *p = NULL, *d = NULL;
669         const char *checker;
670         int r;
671
672         checker = strjoina("fsck.", fstype);
673
674         r = find_binary(checker, true, &p);
675         if (r < 0)
676                 return r;
677
678         /* An fsck that is linked to /bin/true is a non-existent
679          * fsck */
680
681         r = readlink_malloc(p, &d);
682         if (r >= 0 &&
683             (path_equal(d, "/bin/true") ||
684              path_equal(d, "/usr/bin/true") ||
685              path_equal(d, "/dev/null")))
686                 return -ENOENT;
687
688         return 0;
689 }