]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/path-util.c
Improve messages about user mode a bit
[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
9eb977db
KS
48int 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
89char **path_split_and_make_absolute(const char *p) {
90 char **l;
91 assert(p);
92
116cc028
ZJS
93 l = strv_split(p, ":");
94 if (!l)
9eb977db
KS
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
105char *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
b7def684 114 return strjoin(prefix, "/", p, NULL);
9eb977db
KS
115}
116
117char *path_make_absolute_cwd(const char *p) {
116cc028 118 _cleanup_free_ char *cwd = NULL;
9eb977db
KS
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
91a6489d
LP
128 cwd = get_current_dir_name();
129 if (!cwd)
9eb977db
KS
130 return NULL;
131
116cc028 132 return path_make_absolute(p, cwd);
9eb977db
KS
133}
134
135char **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
116cc028
ZJS
145 t = path_make_absolute_cwd(*s);
146 if (!t)
9eb977db
KS
147 return NULL;
148
149 free(*s);
150 *s = t;
151 }
152
153 return l;
154}
155
156char **path_strv_canonicalize(char **l) {
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 t = path_make_absolute_cwd(*s);
172 free(*s);
c9c7aef2 173 *s = NULL;
9eb977db
KS
174
175 if (!t) {
176 enomem = true;
177 continue;
178 }
179
180 errno = 0;
181 u = canonicalize_file_name(t);
9eb977db 182 if (!u) {
874310b7
ZJS
183 if (errno == ENOENT)
184 u = t;
185 else {
186 free(t);
187 if (errno == ENOMEM || !errno)
188 enomem = true;
189
190 continue;
191 }
91a6489d
LP
192 } else
193 free(t);
9eb977db
KS
194
195 l[k++] = u;
196 }
197
198 l[k] = NULL;
199
200 if (enomem)
201 return NULL;
202
203 return l;
204}
205
fabe5c0e
LP
206char **path_strv_canonicalize_uniq(char **l) {
207 if (strv_isempty(l))
208 return l;
209
210 if (!path_strv_canonicalize(l))
211 return NULL;
212
213 return strv_uniq(l);
214}
215
9eb977db
KS
216char *path_kill_slashes(char *path) {
217 char *f, *t;
218 bool slash = false;
219
220 /* Removes redundant inner and trailing slashes. Modifies the
221 * passed string in-place.
222 *
223 * ///foo///bar/ becomes /foo/bar
224 */
225
226 for (f = path, t = path; *f; f++) {
227
228 if (*f == '/') {
229 slash = true;
230 continue;
231 }
232
233 if (slash) {
234 slash = false;
235 *(t++) = '/';
236 }
237
238 *(t++) = *f;
239 }
240
241 /* Special rule, if we are talking of the root directory, a
242 trailing slash is good */
243
244 if (t == path && slash)
245 *(t++) = '/';
246
247 *t = 0;
248 return path;
249}
250
424a19f8 251char* path_startswith(const char *path, const char *prefix) {
9eb977db
KS
252 assert(path);
253 assert(prefix);
254
255 if ((path[0] == '/') != (prefix[0] == '/'))
424a19f8 256 return NULL;
9eb977db
KS
257
258 for (;;) {
259 size_t a, b;
260
261 path += strspn(path, "/");
262 prefix += strspn(prefix, "/");
263
264 if (*prefix == 0)
424a19f8 265 return (char*) path;
9eb977db
KS
266
267 if (*path == 0)
424a19f8 268 return NULL;
9eb977db
KS
269
270 a = strcspn(path, "/");
271 b = strcspn(prefix, "/");
272
273 if (a != b)
424a19f8 274 return NULL;
9eb977db
KS
275
276 if (memcmp(path, prefix, a) != 0)
424a19f8 277 return NULL;
9eb977db
KS
278
279 path += a;
280 prefix += b;
281 }
282}
283
284bool path_equal(const char *a, const char *b) {
285 assert(a);
286 assert(b);
287
288 if ((a[0] == '/') != (b[0] == '/'))
289 return false;
290
291 for (;;) {
292 size_t j, k;
293
294 a += strspn(a, "/");
295 b += strspn(b, "/");
296
297 if (*a == 0 && *b == 0)
298 return true;
299
300 if (*a == 0 || *b == 0)
301 return false;
302
303 j = strcspn(a, "/");
304 k = strcspn(b, "/");
305
306 if (j != k)
307 return false;
308
309 if (memcmp(a, b, j) != 0)
310 return false;
311
312 a += j;
313 b += k;
314 }
315}
316
317int path_is_mount_point(const char *t, bool allow_symlink) {
9eb977db
KS
318 char *parent;
319 int r;
cde9cb34
LP
320 struct file_handle *h;
321 int mount_id, mount_id_parent;
1640a0b6 322 struct stat a, b;
9eb977db 323
cde9cb34
LP
324 /* We are not actually interested in the file handles, but
325 * name_to_handle_at() also passes us the mount ID, hence use
326 * it but throw the handle away */
327
328 if (path_equal(t, "/"))
329 return 1;
9eb977db 330
cde9cb34
LP
331 h = alloca(MAX_HANDLE_SZ);
332 h->handle_bytes = MAX_HANDLE_SZ;
333
334 r = name_to_handle_at(AT_FDCWD, t, h, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
9eb977db 335 if (r < 0) {
fa125f4e
MM
336 if (errno == ENOSYS || errno == ENOTSUP)
337 /* This kernel or file system does not support
1640a0b6
LP
338 * name_to_handle_at(), hence fallback to the
339 * traditional stat() logic */
340 goto fallback;
341
9eb977db
KS
342 if (errno == ENOENT)
343 return 0;
344
345 return -errno;
346 }
347
348 r = path_get_parent(t, &parent);
349 if (r < 0)
350 return r;
351
cde9cb34
LP
352 h->handle_bytes = MAX_HANDLE_SZ;
353 r = name_to_handle_at(AT_FDCWD, parent, h, &mount_id_parent, 0);
9eb977db
KS
354 free(parent);
355
1640a0b6
LP
356 if (r < 0) {
357 /* The parent can't do name_to_handle_at() but the
358 * directory we are interested in can? If so, it must
359 * be a mount point */
360 if (errno == ENOTSUP)
361 return 1;
362
363 return -errno;
364 }
365
366 return mount_id != mount_id_parent;
367
368fallback:
369 if (allow_symlink)
370 r = stat(t, &a);
371 else
f408b8f1 372 r = lstat(t, &a);
1640a0b6 373
8ac75493
MM
374 if (r < 0) {
375 if (errno == ENOENT)
376 return 0;
377
9eb977db 378 return -errno;
8ac75493 379 }
9eb977db 380
1640a0b6
LP
381 r = path_get_parent(t, &parent);
382 if (r < 0)
383 return r;
cde9cb34 384
1640a0b6
LP
385 r = lstat(parent, &b);
386 free(parent);
387
388 if (r < 0)
389 return -errno;
390
391 return a.st_dev != b.st_dev;
9eb977db
KS
392}
393
394int path_is_read_only_fs(const char *path) {
395 struct statvfs st;
396
397 assert(path);
398
399 if (statvfs(path, &st) < 0)
400 return -errno;
401
402 return !!(st.f_flag & ST_RDONLY);
403}
66060897
LP
404
405int path_is_os_tree(const char *path) {
406 char *p;
407 int r;
408
409 /* We use /etc/os-release as flag file if something is an OS */
410
411 p = strappenda(path, "/etc/os-release");
412 r = access(p, F_OK);
413
414 return r < 0 ? 0 : 1;
415}
c9d954b2
ZJS
416
417int find_binary(const char *name, char **filename) {
418 assert(name);
4087cb9e
LP
419 assert(filename);
420
c9d954b2
ZJS
421 if (strchr(name, '/')) {
422 char *p;
423
424 if (path_is_absolute(name))
425 p = strdup(name);
426 else
427 p = path_make_absolute_cwd(name);
428 if (!p)
429 return -ENOMEM;
430
431 *filename = p;
432 return 0;
433 } else {
434 const char *path;
435 char *state, *w;
436 size_t l;
437
438 /**
439 * Plain getenv, not secure_getenv, because we want
440 * to actually allow the user to pick the binary.
441 */
442 path = getenv("PATH");
443 if (!path)
444 path = DEFAULT_PATH;
445
446 FOREACH_WORD_SEPARATOR(w, l, path, ":", state) {
447 char *p;
448
4bcc8c3c 449 if (asprintf(&p, "%.*s/%s", (int) l, w, name) < 0)
c9d954b2
ZJS
450 return -ENOMEM;
451
452 if (access(p, X_OK) < 0) {
453 free(p);
454 continue;
455 }
456
457 path_kill_slashes(p);
458 *filename = p;
459
460 return 0;
461 }
462
463 return -ENOENT;
464 }
465}
8e184852 466
4087cb9e 467bool paths_check_timestamp(char **paths, usec_t *timestamp, bool update) {
8e184852 468 bool changed = false;
4087cb9e 469 char **i;
8e184852 470
97f2d76d
TG
471 assert(timestamp);
472
8e184852 473 if (paths == NULL)
4087cb9e 474 return false;
8e184852 475
4087cb9e 476 STRV_FOREACH(i, paths) {
8e184852 477 struct stat stats;
4087cb9e 478 usec_t u;
8e184852 479
4087cb9e 480 if (stat(*i, &stats) < 0)
8e184852
TG
481 continue;
482
4087cb9e
LP
483 u = timespec_load(&stats.st_mtim);
484
97f2d76d 485 /* first check */
4087cb9e 486 if (*timestamp >= u)
8e184852
TG
487 continue;
488
9f6445e3 489 log_debug("timestamp of '%s' changed", *i);
8e184852
TG
490
491 /* update timestamp */
4087cb9e
LP
492 if (update) {
493 *timestamp = u;
494 changed = true;
495 } else
496 return true;
8e184852 497 }
4087cb9e 498
8e184852
TG
499 return changed;
500}