chiark / gitweb /
path-util: do not fail in path_is_mountpoint() if path doesn't exist
[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 char *path_get_file_name(const char *p) {
49         char *r;
50
51         assert(p);
52
53         if ((r = strrchr(p, '/')))
54                 return r + 1;
55
56         return (char*) p;
57 }
58
59 int path_get_parent(const char *path, char **_r) {
60         const char *e, *a = NULL, *b = NULL, *p;
61         char *r;
62         bool slash = false;
63
64         assert(path);
65         assert(_r);
66
67         if (!*path)
68                 return -EINVAL;
69
70         for (e = path; *e; e++) {
71
72                 if (!slash && *e == '/') {
73                         a = b;
74                         b = e;
75                         slash = true;
76                 } else if (slash && *e != '/')
77                         slash = false;
78         }
79
80         if (*(e-1) == '/')
81                 p = a;
82         else
83                 p = b;
84
85         if (!p)
86                 return -EINVAL;
87
88         if (p == path)
89                 r = strdup("/");
90         else
91                 r = strndup(path, p-path);
92
93         if (!r)
94                 return -ENOMEM;
95
96         *_r = r;
97         return 0;
98 }
99
100 char **path_split_and_make_absolute(const char *p) {
101         char **l;
102         assert(p);
103
104         if (!(l = strv_split(p, ":")))
105                 return NULL;
106
107         if (!path_strv_make_absolute_cwd(l)) {
108                 strv_free(l);
109                 return NULL;
110         }
111
112         return l;
113 }
114
115 char *path_make_absolute(const char *p, const char *prefix) {
116         assert(p);
117
118         /* Makes every item in the list an absolute path by prepending
119          * the prefix, if specified and necessary */
120
121         if (path_is_absolute(p) || !prefix)
122                 return strdup(p);
123
124         return strjoin(prefix, "/", p, NULL);
125 }
126
127 char *path_make_absolute_cwd(const char *p) {
128         char *cwd, *r;
129
130         assert(p);
131
132         /* Similar to path_make_absolute(), but prefixes with the
133          * current working directory. */
134
135         if (path_is_absolute(p))
136                 return strdup(p);
137
138         if (!(cwd = get_current_dir_name()))
139                 return NULL;
140
141         r = path_make_absolute(p, cwd);
142         free(cwd);
143
144         return r;
145 }
146
147 char **path_strv_make_absolute_cwd(char **l) {
148         char **s;
149
150         /* Goes through every item in the string list and makes it
151          * absolute. This works in place and won't rollback any
152          * changes on failure. */
153
154         STRV_FOREACH(s, l) {
155                 char *t;
156
157                 if (!(t = path_make_absolute_cwd(*s)))
158                         return NULL;
159
160                 free(*s);
161                 *s = t;
162         }
163
164         return l;
165 }
166
167 char **path_strv_canonicalize(char **l) {
168         char **s;
169         unsigned k = 0;
170         bool enomem = false;
171
172         if (strv_isempty(l))
173                 return l;
174
175         /* Goes through every item in the string list and canonicalize
176          * the path. This works in place and won't rollback any
177          * changes on failure. */
178
179         STRV_FOREACH(s, l) {
180                 char *t, *u;
181
182                 t = path_make_absolute_cwd(*s);
183                 free(*s);
184
185                 if (!t) {
186                         enomem = true;
187                         continue;
188                 }
189
190                 errno = 0;
191                 u = canonicalize_file_name(t);
192                 free(t);
193
194                 if (!u) {
195                         if (errno == ENOMEM || !errno)
196                                 enomem = true;
197
198                         continue;
199                 }
200
201                 l[k++] = u;
202         }
203
204         l[k] = NULL;
205
206         if (enomem)
207                 return NULL;
208
209         return l;
210 }
211
212 char **path_strv_remove_empty(char **l) {
213         char **f, **t;
214
215         if (!l)
216                 return NULL;
217
218         for (f = t = l; *f; f++) {
219
220                 if (dir_is_empty(*f) > 0) {
221                         free(*f);
222                         continue;
223                 }
224
225                 *(t++) = *f;
226         }
227
228         *t = NULL;
229         return l;
230 }
231
232 char *path_kill_slashes(char *path) {
233         char *f, *t;
234         bool slash = false;
235
236         /* Removes redundant inner and trailing slashes. Modifies the
237          * passed string in-place.
238          *
239          * ///foo///bar/ becomes /foo/bar
240          */
241
242         for (f = path, t = path; *f; f++) {
243
244                 if (*f == '/') {
245                         slash = true;
246                         continue;
247                 }
248
249                 if (slash) {
250                         slash = false;
251                         *(t++) = '/';
252                 }
253
254                 *(t++) = *f;
255         }
256
257         /* Special rule, if we are talking of the root directory, a
258         trailing slash is good */
259
260         if (t == path && slash)
261                 *(t++) = '/';
262
263         *t = 0;
264         return path;
265 }
266
267 bool path_startswith(const char *path, const char *prefix) {
268         assert(path);
269         assert(prefix);
270
271         if ((path[0] == '/') != (prefix[0] == '/'))
272                 return false;
273
274         for (;;) {
275                 size_t a, b;
276
277                 path += strspn(path, "/");
278                 prefix += strspn(prefix, "/");
279
280                 if (*prefix == 0)
281                         return true;
282
283                 if (*path == 0)
284                         return false;
285
286                 a = strcspn(path, "/");
287                 b = strcspn(prefix, "/");
288
289                 if (a != b)
290                         return false;
291
292                 if (memcmp(path, prefix, a) != 0)
293                         return false;
294
295                 path += a;
296                 prefix += b;
297         }
298 }
299
300 bool path_equal(const char *a, const char *b) {
301         assert(a);
302         assert(b);
303
304         if ((a[0] == '/') != (b[0] == '/'))
305                 return false;
306
307         for (;;) {
308                 size_t j, k;
309
310                 a += strspn(a, "/");
311                 b += strspn(b, "/");
312
313                 if (*a == 0 && *b == 0)
314                         return true;
315
316                 if (*a == 0 || *b == 0)
317                         return false;
318
319                 j = strcspn(a, "/");
320                 k = strcspn(b, "/");
321
322                 if (j != k)
323                         return false;
324
325                 if (memcmp(a, b, j) != 0)
326                         return false;
327
328                 a += j;
329                 b += k;
330         }
331 }
332
333 int path_is_mount_point(const char *t, bool allow_symlink) {
334         char *parent;
335         int r;
336         struct file_handle *h;
337         int mount_id, mount_id_parent;
338         struct stat a, b;
339
340         /* We are not actually interested in the file handles, but
341          * name_to_handle_at() also passes us the mount ID, hence use
342          * it but throw the handle away */
343
344         if (path_equal(t, "/"))
345                 return 1;
346
347         h = alloca(MAX_HANDLE_SZ);
348         h->handle_bytes = MAX_HANDLE_SZ;
349
350         r = name_to_handle_at(AT_FDCWD, t, h, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
351         if (r < 0) {
352                 if (errno == ENOTSUP)
353                         /* This file system does not support
354                          * name_to_handle_at(), hence fallback to the
355                          * traditional stat() logic */
356                         goto fallback;
357
358                 if (errno == ENOENT)
359                         return 0;
360
361                 return -errno;
362         }
363
364         r = path_get_parent(t, &parent);
365         if (r < 0)
366                 return r;
367
368         h->handle_bytes = MAX_HANDLE_SZ;
369         r = name_to_handle_at(AT_FDCWD, parent, h, &mount_id_parent, 0);
370         free(parent);
371
372         if (r < 0) {
373                 /* The parent can't do name_to_handle_at() but the
374                  * directory we are interested in can? If so, it must
375                  * be a mount point */
376                 if (errno == ENOTSUP)
377                         return 1;
378
379                 return -errno;
380         }
381
382         return mount_id != mount_id_parent;
383
384 fallback:
385         if (allow_symlink)
386                 r = stat(t, &a);
387         else
388                 r = lstat(t, &a);
389
390         if (r < 0) {
391                 if (errno == ENOENT)
392                         return 0;
393
394                 return -errno;
395         }
396
397         r = path_get_parent(t, &parent);
398         if (r < 0)
399                 return r;
400
401         r = lstat(parent, &b);
402         free(parent);
403
404         if (r < 0)
405                 return -errno;
406
407         return a.st_dev != b.st_dev;
408 }
409
410 int path_is_read_only_fs(const char *path) {
411         struct statvfs st;
412
413         assert(path);
414
415         if (statvfs(path, &st) < 0)
416                 return -errno;
417
418         return !!(st.f_flag & ST_RDONLY);
419 }