chiark / gitweb /
util: try to be a bit more NFS compatible when checking whether an FS is writable
[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         if (st.f_flag & ST_RDONLY)
537                 return true;
538
539         /* On NFS, statvfs() might not reflect whether we can actually
540          * write to the remote share. Let's try again with
541          * access(W_OK) which is more reliable, at least sometimes. */
542         if (access(path, W_OK) < 0 && errno == EROFS)
543                 return true;
544
545         return false;
546 }
547
548 int path_is_os_tree(const char *path) {
549         char *p;
550         int r;
551
552         /* We use /usr/lib/os-release as flag file if something is an OS */
553         p = strappenda(path, "/usr/lib/os-release");
554         r = access(p, F_OK);
555
556         if (r >= 0)
557                 return 1;
558
559         /* Also check for the old location in /etc, just in case. */
560         p = strappenda(path, "/etc/os-release");
561         r = access(p, F_OK);
562
563         return r >= 0;
564 }
565
566 int find_binary(const char *name, char **filename) {
567         assert(name);
568
569         if (is_path(name)) {
570                 if (access(name, X_OK) < 0)
571                         return -errno;
572
573                 if (filename) {
574                         char *p;
575
576                         p = path_make_absolute_cwd(name);
577                         if (!p)
578                                 return -ENOMEM;
579
580                         *filename = p;
581                 }
582
583                 return 0;
584         } else {
585                 const char *path;
586                 const char *word, *state;
587                 size_t l;
588
589                 /**
590                  * Plain getenv, not secure_getenv, because we want
591                  * to actually allow the user to pick the binary.
592                  */
593                 path = getenv("PATH");
594                 if (!path)
595                         path = DEFAULT_PATH;
596
597                 FOREACH_WORD_SEPARATOR(word, l, path, ":", state) {
598                         _cleanup_free_ char *p = NULL;
599
600                         if (asprintf(&p, "%.*s/%s", (int) l, word, name) < 0)
601                                 return -ENOMEM;
602
603                         if (access(p, X_OK) < 0)
604                                 continue;
605
606                         if (filename) {
607                                 *filename = path_kill_slashes(p);
608                                 p = NULL;
609                         }
610
611                         return 0;
612                 }
613
614                 return -ENOENT;
615         }
616 }
617
618 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
619         bool changed = false;
620         const char* const* i;
621
622         assert(timestamp);
623
624         if (paths == NULL)
625                 return false;
626
627         STRV_FOREACH(i, paths) {
628                 struct stat stats;
629                 usec_t u;
630
631                 if (stat(*i, &stats) < 0)
632                         continue;
633
634                 u = timespec_load(&stats.st_mtim);
635
636                 /* first check */
637                 if (*timestamp >= u)
638                         continue;
639
640                 log_debug("timestamp of '%s' changed", *i);
641
642                 /* update timestamp */
643                 if (update) {
644                         *timestamp = u;
645                         changed = true;
646                 } else
647                         return true;
648         }
649
650         return changed;
651 }
652
653 int fsck_exists(const char *fstype) {
654         _cleanup_free_ char *p = NULL, *d = NULL;
655         const char *checker;
656         int r;
657
658         checker = strappenda("fsck.", fstype);
659
660         r = find_binary(checker, &p);
661         if (r < 0)
662                 return r;
663
664         /* An fsck that is linked to /bin/true is a non-existent
665          * fsck */
666
667         r = readlink_malloc(p, &d);
668         if (r >= 0 &&
669             (path_equal(d, "/bin/true") ||
670              path_equal(d, "/usr/bin/true") ||
671              path_equal(d, "/dev/null")))
672                 return -ENOENT;
673
674         return 0;
675 }