chiark / gitweb /
conf-parser: minor optimization in config_parse_string()
[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 char **path_strv_make_absolute_cwd(char **l) {
136         char **s;
137
138         /* Goes through every item in the string list and makes it
139          * absolute. This works in place and won't rollback any
140          * changes on failure. */
141
142         STRV_FOREACH(s, l) {
143                 char *t;
144
145                 t = path_make_absolute_cwd(*s);
146                 if (!t)
147                         return NULL;
148
149                 free(*s);
150                 *s = t;
151         }
152
153         return l;
154 }
155
156 char **path_strv_canonicalize_absolute(char **l, const char *prefix) {
157         char **s;
158         unsigned k = 0;
159         bool enomem = false;
160
161         if (strv_isempty(l))
162                 return l;
163
164         /* Goes through every item in the string list and canonicalize
165          * the path. This works in place and won't rollback any
166          * changes on failure. */
167
168         STRV_FOREACH(s, l) {
169                 char *t, *u;
170
171                 if (!path_is_absolute(*s))
172                         continue;
173
174                 if (prefix) {
175                         t = strappend(prefix, *s);
176                         free(*s);
177                         *s = NULL;
178
179                         if (!t) {
180                                 enomem = true;
181                                 continue;
182                         }
183                 } else {
184                         t = *s;
185                         *s = NULL;
186                 }
187
188                 errno = 0;
189                 u = canonicalize_file_name(t);
190                 if (!u) {
191                         if (errno == ENOENT)
192                                 u = t;
193                         else {
194                                 free(t);
195                                 if (errno == ENOMEM || errno == 0)
196                                         enomem = true;
197
198                                 continue;
199                         }
200                 } else
201                         free(t);
202
203                 l[k++] = u;
204         }
205
206         l[k] = NULL;
207
208         if (enomem)
209                 return NULL;
210
211         return l;
212 }
213
214 char **path_strv_canonicalize_absolute_uniq(char **l, const char *prefix) {
215
216         if (strv_isempty(l))
217                 return l;
218
219         if (!path_strv_canonicalize_absolute(l, prefix))
220                 return NULL;
221
222         return strv_uniq(l);
223 }
224
225 char *path_kill_slashes(char *path) {
226         char *f, *t;
227         bool slash = false;
228
229         /* Removes redundant inner and trailing slashes. Modifies the
230          * passed string in-place.
231          *
232          * ///foo///bar/ becomes /foo/bar
233          */
234
235         for (f = path, t = path; *f; f++) {
236
237                 if (*f == '/') {
238                         slash = true;
239                         continue;
240                 }
241
242                 if (slash) {
243                         slash = false;
244                         *(t++) = '/';
245                 }
246
247                 *(t++) = *f;
248         }
249
250         /* Special rule, if we are talking of the root directory, a
251         trailing slash is good */
252
253         if (t == path && slash)
254                 *(t++) = '/';
255
256         *t = 0;
257         return path;
258 }
259
260 char* path_startswith(const char *path, const char *prefix) {
261         assert(path);
262         assert(prefix);
263
264         if ((path[0] == '/') != (prefix[0] == '/'))
265                 return NULL;
266
267         for (;;) {
268                 size_t a, b;
269
270                 path += strspn(path, "/");
271                 prefix += strspn(prefix, "/");
272
273                 if (*prefix == 0)
274                         return (char*) path;
275
276                 if (*path == 0)
277                         return NULL;
278
279                 a = strcspn(path, "/");
280                 b = strcspn(prefix, "/");
281
282                 if (a != b)
283                         return NULL;
284
285                 if (memcmp(path, prefix, a) != 0)
286                         return NULL;
287
288                 path += a;
289                 prefix += b;
290         }
291 }
292
293 bool path_equal(const char *a, const char *b) {
294         assert(a);
295         assert(b);
296
297         if ((a[0] == '/') != (b[0] == '/'))
298                 return false;
299
300         for (;;) {
301                 size_t j, k;
302
303                 a += strspn(a, "/");
304                 b += strspn(b, "/");
305
306                 if (*a == 0 && *b == 0)
307                         return true;
308
309                 if (*a == 0 || *b == 0)
310                         return false;
311
312                 j = strcspn(a, "/");
313                 k = strcspn(b, "/");
314
315                 if (j != k)
316                         return false;
317
318                 if (memcmp(a, b, j) != 0)
319                         return false;
320
321                 a += j;
322                 b += k;
323         }
324 }
325
326 int path_is_mount_point(const char *t, bool allow_symlink) {
327         char *parent;
328         int r;
329         struct file_handle *h;
330         int mount_id, mount_id_parent;
331         struct stat a, b;
332
333         /* We are not actually interested in the file handles, but
334          * name_to_handle_at() also passes us the mount ID, hence use
335          * it but throw the handle away */
336
337         if (path_equal(t, "/"))
338                 return 1;
339
340         h = alloca(MAX_HANDLE_SZ);
341         h->handle_bytes = MAX_HANDLE_SZ;
342
343         r = name_to_handle_at(AT_FDCWD, t, h, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
344         if (r < 0) {
345                 if (errno == ENOSYS || errno == ENOTSUP)
346                         /* This kernel or file system does not support
347                          * name_to_handle_at(), hence fallback to the
348                          * traditional stat() logic */
349                         goto fallback;
350
351                 if (errno == ENOENT)
352                         return 0;
353
354                 return -errno;
355         }
356
357         r = path_get_parent(t, &parent);
358         if (r < 0)
359                 return r;
360
361         h->handle_bytes = MAX_HANDLE_SZ;
362         r = name_to_handle_at(AT_FDCWD, parent, h, &mount_id_parent, 0);
363         free(parent);
364
365         if (r < 0) {
366                 /* The parent can't do name_to_handle_at() but the
367                  * directory we are interested in can? If so, it must
368                  * be a mount point */
369                 if (errno == ENOTSUP)
370                         return 1;
371
372                 return -errno;
373         }
374
375         return mount_id != mount_id_parent;
376
377 fallback:
378         if (allow_symlink)
379                 r = stat(t, &a);
380         else
381                 r = lstat(t, &a);
382
383         if (r < 0) {
384                 if (errno == ENOENT)
385                         return 0;
386
387                 return -errno;
388         }
389
390         r = path_get_parent(t, &parent);
391         if (r < 0)
392                 return r;
393
394         r = lstat(parent, &b);
395         free(parent);
396
397         if (r < 0)
398                 return -errno;
399
400         return a.st_dev != b.st_dev;
401 }
402
403 int path_is_read_only_fs(const char *path) {
404         struct statvfs st;
405
406         assert(path);
407
408         if (statvfs(path, &st) < 0)
409                 return -errno;
410
411         return !!(st.f_flag & ST_RDONLY);
412 }
413
414 int path_is_os_tree(const char *path) {
415         char *p;
416         int r;
417
418         /* We use /etc/os-release as flag file if something is an OS */
419
420         p = strappenda(path, "/etc/os-release");
421         r = access(p, F_OK);
422
423         return r < 0 ? 0 : 1;
424 }
425
426 int find_binary(const char *name, char **filename) {
427         assert(name);
428         assert(filename);
429
430         if (strchr(name, '/')) {
431                 char *p;
432
433                 if (path_is_absolute(name))
434                         p = strdup(name);
435                 else
436                         p = path_make_absolute_cwd(name);
437                 if (!p)
438                         return -ENOMEM;
439
440                 *filename = p;
441                 return 0;
442         } else {
443                 const char *path;
444                 char *state, *w;
445                 size_t l;
446
447                 /**
448                  * Plain getenv, not secure_getenv, because we want
449                  * to actually allow the user to pick the binary.
450                  */
451                 path = getenv("PATH");
452                 if (!path)
453                         path = DEFAULT_PATH;
454
455                 FOREACH_WORD_SEPARATOR(w, l, path, ":", state) {
456                         char *p;
457
458                         if (asprintf(&p, "%.*s/%s", (int) l, w, name) < 0)
459                                 return -ENOMEM;
460
461                         if (access(p, X_OK) < 0) {
462                                 free(p);
463                                 continue;
464                         }
465
466                         path_kill_slashes(p);
467                         *filename = p;
468
469                         return 0;
470                 }
471
472                 return -ENOENT;
473         }
474 }
475
476 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
477         bool changed = false;
478         const char* const* i;
479
480         assert(timestamp);
481
482         if (paths == NULL)
483                 return false;
484
485         STRV_FOREACH(i, paths) {
486                 struct stat stats;
487                 usec_t u;
488
489                 if (stat(*i, &stats) < 0)
490                         continue;
491
492                 u = timespec_load(&stats.st_mtim);
493
494                 /* first check */
495                 if (*timestamp >= u)
496                         continue;
497
498                 log_debug("timestamp of '%s' changed", *i);
499
500                 /* update timestamp */
501                 if (update) {
502                         *timestamp = u;
503                         changed = true;
504                 } else
505                         return true;
506         }
507
508         return changed;
509 }