]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/path-util.c
core: add new .slice unit type for partitioning systems
[thirdparty/systemd.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 }