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