chiark / gitweb /
shared: path-util - memory leak
[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 char* path_join(const char *root, const char *path, const char *rest) {
440         assert(path);
441
442         if (!isempty(root))
443                 return strjoin(root, endswith(root, "/") ? "" : "/",
444                                path[0] == '/' ? path+1 : path,
445                                rest ? (endswith(path, "/") ? "" : "/") : NULL,
446                                rest && rest[0] == '/' ? rest+1 : rest,
447                                NULL);
448         else
449                 return strjoin(path,
450                                rest ? (endswith(path, "/") ? "" : "/") : NULL,
451                                rest && rest[0] == '/' ? rest+1 : rest,
452                                NULL);
453 }
454
455 int path_is_mount_point(const char *t, bool allow_symlink) {
456
457         union file_handle_union h = {
458                 .handle.handle_bytes = MAX_HANDLE_SZ
459         };
460
461         int mount_id, mount_id_parent;
462         _cleanup_free_ char *parent = NULL;
463         struct stat a, b;
464         int r;
465
466         /* We are not actually interested in the file handles, but
467          * name_to_handle_at() also passes us the mount ID, hence use
468          * it but throw the handle away */
469
470         if (path_equal(t, "/"))
471                 return 1;
472
473         r = name_to_handle_at(AT_FDCWD, t, &h.handle, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
474         if (r < 0) {
475                 if (IN_SET(errno, ENOSYS, EOPNOTSUPP))
476                         /* This kernel or file system does not support
477                          * name_to_handle_at(), hence fallback to the
478                          * traditional stat() logic */
479                         goto fallback;
480
481                 if (errno == ENOENT)
482                         return 0;
483
484                 return -errno;
485         }
486
487         r = path_get_parent(t, &parent);
488         if (r < 0)
489                 return r;
490
491         h.handle.handle_bytes = MAX_HANDLE_SZ;
492         r = name_to_handle_at(AT_FDCWD, parent, &h.handle, &mount_id_parent, 0);
493         if (r < 0) {
494                 /* The parent can't do name_to_handle_at() but the
495                  * directory we are interested in can? If so, it must
496                  * be a mount point */
497                 if (errno == EOPNOTSUPP)
498                         return 1;
499
500                 return -errno;
501         }
502
503         return mount_id != mount_id_parent;
504
505 fallback:
506         if (allow_symlink)
507                 r = stat(t, &a);
508         else
509                 r = lstat(t, &a);
510
511         if (r < 0) {
512                 if (errno == ENOENT)
513                         return 0;
514
515                 return -errno;
516         }
517
518         r = path_get_parent(t, &parent);
519         if (r < 0)
520                 return r;
521
522         r = lstat(parent, &b);
523         if (r < 0)
524                 return -errno;
525
526         return a.st_dev != b.st_dev;
527 }
528
529 int path_is_read_only_fs(const char *path) {
530         struct statvfs st;
531
532         assert(path);
533
534         if (statvfs(path, &st) < 0)
535                 return -errno;
536
537         if (st.f_flag & ST_RDONLY)
538                 return true;
539
540         /* On NFS, statvfs() might not reflect whether we can actually
541          * write to the remote share. Let's try again with
542          * access(W_OK) which is more reliable, at least sometimes. */
543         if (access(path, W_OK) < 0 && errno == EROFS)
544                 return true;
545
546         return false;
547 }
548
549 int path_is_os_tree(const char *path) {
550         char *p;
551         int r;
552
553         /* We use /usr/lib/os-release as flag file if something is an OS */
554         p = strappenda(path, "/usr/lib/os-release");
555         r = access(p, F_OK);
556
557         if (r >= 0)
558                 return 1;
559
560         /* Also check for the old location in /etc, just in case. */
561         p = strappenda(path, "/etc/os-release");
562         r = access(p, F_OK);
563
564         return r >= 0;
565 }
566
567 int find_binary(const char *name, bool local, char **filename) {
568         assert(name);
569
570         if (is_path(name)) {
571                 if (local && access(name, X_OK) < 0)
572                         return -errno;
573
574                 if (filename) {
575                         char *p;
576
577                         p = path_make_absolute_cwd(name);
578                         if (!p)
579                                 return -ENOMEM;
580
581                         *filename = p;
582                 }
583
584                 return 0;
585         } else {
586                 const char *path;
587                 const char *word, *state;
588                 size_t l;
589
590                 /**
591                  * Plain getenv, not secure_getenv, because we want
592                  * to actually allow the user to pick the binary.
593                  */
594                 path = getenv("PATH");
595                 if (!path)
596                         path = DEFAULT_PATH;
597
598                 FOREACH_WORD_SEPARATOR(word, l, path, ":", state) {
599                         _cleanup_free_ char *p = NULL;
600
601                         if (asprintf(&p, "%.*s/%s", (int) l, word, name) < 0)
602                                 return -ENOMEM;
603
604                         if (access(p, X_OK) < 0)
605                                 continue;
606
607                         if (filename) {
608                                 *filename = path_kill_slashes(p);
609                                 p = NULL;
610                         }
611
612                         return 0;
613                 }
614
615                 return -ENOENT;
616         }
617 }
618
619 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
620         bool changed = false;
621         const char* const* i;
622
623         assert(timestamp);
624
625         if (paths == NULL)
626                 return false;
627
628         STRV_FOREACH(i, paths) {
629                 struct stat stats;
630                 usec_t u;
631
632                 if (stat(*i, &stats) < 0)
633                         continue;
634
635                 u = timespec_load(&stats.st_mtim);
636
637                 /* first check */
638                 if (*timestamp >= u)
639                         continue;
640
641                 log_debug("timestamp of '%s' changed", *i);
642
643                 /* update timestamp */
644                 if (update) {
645                         *timestamp = u;
646                         changed = true;
647                 } else
648                         return true;
649         }
650
651         return changed;
652 }
653
654 int fsck_exists(const char *fstype) {
655         _cleanup_free_ char *p = NULL, *d = NULL;
656         const char *checker;
657         int r;
658
659         checker = strappenda("fsck.", fstype);
660
661         r = find_binary(checker, true, &p);
662         if (r < 0)
663                 return r;
664
665         /* An fsck that is linked to /bin/true is a non-existent
666          * fsck */
667
668         r = readlink_malloc(p, &d);
669         if (r >= 0 &&
670             (path_equal(d, "/bin/true") ||
671              path_equal(d, "/usr/bin/true") ||
672              path_equal(d, "/dev/null")))
673                 return -ENOENT;
674
675         return 0;
676 }