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