chiark / gitweb /
basic: modernize conf-files.c a bit
[elogind.git] / src / basic / fs-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 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 "alloc-util.h"
23 #include "dirent-util.h"
24 #include "fd-util.h"
25 #include "fileio.h"
26 #include "fs-util.h"
27 #include "mkdir.h"
28 #include "parse-util.h"
29 #include "path-util.h"
30 #include "string-util.h"
31 #include "strv.h"
32 #include "user-util.h"
33 #include "util.h"
34
35 int unlink_noerrno(const char *path) {
36         PROTECT_ERRNO;
37         int r;
38
39         r = unlink(path);
40         if (r < 0)
41                 return -errno;
42
43         return 0;
44 }
45
46 #if 0 /// UNNEEDED by elogind
47 int rmdir_parents(const char *path, const char *stop) {
48         size_t l;
49         int r = 0;
50
51         assert(path);
52         assert(stop);
53
54         l = strlen(path);
55
56         /* Skip trailing slashes */
57         while (l > 0 && path[l-1] == '/')
58                 l--;
59
60         while (l > 0) {
61                 char *t;
62
63                 /* Skip last component */
64                 while (l > 0 && path[l-1] != '/')
65                         l--;
66
67                 /* Skip trailing slashes */
68                 while (l > 0 && path[l-1] == '/')
69                         l--;
70
71                 if (l <= 0)
72                         break;
73
74                 t = strndup(path, l);
75                 if (!t)
76                         return -ENOMEM;
77
78                 if (path_startswith(stop, t)) {
79                         free(t);
80                         return 0;
81                 }
82
83                 r = rmdir(t);
84                 free(t);
85
86                 if (r < 0)
87                         if (errno != ENOENT)
88                                 return -errno;
89         }
90
91         return 0;
92 }
93
94
95 int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
96         struct stat buf;
97         int ret;
98
99         ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE);
100         if (ret >= 0)
101                 return 0;
102
103         /* renameat2() exists since Linux 3.15, btrfs added support for it later.
104          * If it is not implemented, fallback to another method. */
105         if (!IN_SET(errno, EINVAL, ENOSYS))
106                 return -errno;
107
108         /* The link()/unlink() fallback does not work on directories. But
109          * renameat() without RENAME_NOREPLACE gives the same semantics on
110          * directories, except when newpath is an *empty* directory. This is
111          * good enough. */
112         ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW);
113         if (ret >= 0 && S_ISDIR(buf.st_mode)) {
114                 ret = renameat(olddirfd, oldpath, newdirfd, newpath);
115                 return ret >= 0 ? 0 : -errno;
116         }
117
118         /* If it is not a directory, use the link()/unlink() fallback. */
119         ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0);
120         if (ret < 0)
121                 return -errno;
122
123         ret = unlinkat(olddirfd, oldpath, 0);
124         if (ret < 0) {
125                 /* backup errno before the following unlinkat() alters it */
126                 ret = errno;
127                 (void) unlinkat(newdirfd, newpath, 0);
128                 errno = ret;
129                 return -errno;
130         }
131
132         return 0;
133 }
134 #endif // 0
135
136 int readlinkat_malloc(int fd, const char *p, char **ret) {
137         size_t l = 100;
138         int r;
139
140         assert(p);
141         assert(ret);
142
143         for (;;) {
144                 char *c;
145                 ssize_t n;
146
147                 c = new(char, l);
148                 if (!c)
149                         return -ENOMEM;
150
151                 n = readlinkat(fd, p, c, l-1);
152                 if (n < 0) {
153                         r = -errno;
154                         free(c);
155                         return r;
156                 }
157
158                 if ((size_t) n < l-1) {
159                         c[n] = 0;
160                         *ret = c;
161                         return 0;
162                 }
163
164                 free(c);
165                 l *= 2;
166         }
167 }
168
169 int readlink_malloc(const char *p, char **ret) {
170         return readlinkat_malloc(AT_FDCWD, p, ret);
171 }
172
173 #if 0 /// UNNEEDED by elogind
174 int readlink_value(const char *p, char **ret) {
175         _cleanup_free_ char *link = NULL;
176         char *value;
177         int r;
178
179         r = readlink_malloc(p, &link);
180         if (r < 0)
181                 return r;
182
183         value = basename(link);
184         if (!value)
185                 return -ENOENT;
186
187         value = strdup(value);
188         if (!value)
189                 return -ENOMEM;
190
191         *ret = value;
192
193         return 0;
194 }
195
196 int readlink_and_make_absolute(const char *p, char **r) {
197         _cleanup_free_ char *target = NULL;
198         char *k;
199         int j;
200
201         assert(p);
202         assert(r);
203
204         j = readlink_malloc(p, &target);
205         if (j < 0)
206                 return j;
207
208         k = file_in_same_dir(p, target);
209         if (!k)
210                 return -ENOMEM;
211
212         *r = k;
213         return 0;
214 }
215
216 int readlink_and_canonicalize(const char *p, char **r) {
217         char *t, *s;
218         int j;
219
220         assert(p);
221         assert(r);
222
223         j = readlink_and_make_absolute(p, &t);
224         if (j < 0)
225                 return j;
226
227         s = canonicalize_file_name(t);
228         if (s) {
229                 free(t);
230                 *r = s;
231         } else
232                 *r = t;
233
234         path_kill_slashes(*r);
235
236         return 0;
237 }
238
239 int readlink_and_make_absolute_root(const char *root, const char *path, char **ret) {
240         _cleanup_free_ char *target = NULL, *t = NULL;
241         const char *full;
242         int r;
243
244         full = prefix_roota(root, path);
245         r = readlink_malloc(full, &target);
246         if (r < 0)
247                 return r;
248
249         t = file_in_same_dir(path, target);
250         if (!t)
251                 return -ENOMEM;
252
253         *ret = t;
254         t = NULL;
255
256         return 0;
257 }
258 #endif // 0
259
260 int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
261         assert(path);
262
263         /* Under the assumption that we are running privileged we
264          * first change the access mode and only then hand out
265          * ownership to avoid a window where access is too open. */
266
267         if (mode != MODE_INVALID)
268                 if (chmod(path, mode) < 0)
269                         return -errno;
270
271         if (uid != UID_INVALID || gid != GID_INVALID)
272                 if (chown(path, uid, gid) < 0)
273                         return -errno;
274
275         return 0;
276 }
277
278 #if 0 /// UNNEEDED by elogind
279 int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid) {
280         assert(fd >= 0);
281
282         /* Under the assumption that we are running privileged we
283          * first change the access mode and only then hand out
284          * ownership to avoid a window where access is too open. */
285
286         if (mode != MODE_INVALID)
287                 if (fchmod(fd, mode) < 0)
288                         return -errno;
289
290         if (uid != UID_INVALID || gid != GID_INVALID)
291                 if (fchown(fd, uid, gid) < 0)
292                         return -errno;
293
294         return 0;
295 }
296 #endif // 0
297
298 int fchmod_umask(int fd, mode_t m) {
299         mode_t u;
300         int r;
301
302         u = umask(0777);
303         r = fchmod(fd, m & (~u)) < 0 ? -errno : 0;
304         umask(u);
305
306         return r;
307 }
308
309 int fd_warn_permissions(const char *path, int fd) {
310         struct stat st;
311
312         if (fstat(fd, &st) < 0)
313                 return -errno;
314
315         if (st.st_mode & 0111)
316                 log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path);
317
318         if (st.st_mode & 0002)
319                 log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
320
321         if (getpid() == 1 && (st.st_mode & 0044) != 0044)
322                 log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path);
323
324         return 0;
325 }
326
327 int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
328         _cleanup_close_ int fd;
329         int r;
330
331         assert(path);
332
333         if (parents)
334                 mkdir_parents(path, 0755);
335
336         fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode > 0 ? mode : 0644);
337         if (fd < 0)
338                 return -errno;
339
340         if (mode != MODE_INVALID) {
341                 r = fchmod(fd, mode);
342                 if (r < 0)
343                         return -errno;
344         }
345
346         if (uid != UID_INVALID || gid != GID_INVALID) {
347                 r = fchown(fd, uid, gid);
348                 if (r < 0)
349                         return -errno;
350         }
351
352         if (stamp != USEC_INFINITY) {
353                 struct timespec ts[2];
354
355                 timespec_store(&ts[0], stamp);
356                 ts[1] = ts[0];
357                 r = futimens(fd, ts);
358         } else
359                 r = futimens(fd, NULL);
360         if (r < 0)
361                 return -errno;
362
363         return 0;
364 }
365
366 int touch(const char *path) {
367         return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID);
368 }
369
370 #if 0 /// UNNEEDED by elogind
371 int symlink_idempotent(const char *from, const char *to) {
372         _cleanup_free_ char *p = NULL;
373         int r;
374
375         assert(from);
376         assert(to);
377
378         if (symlink(from, to) < 0) {
379                 if (errno != EEXIST)
380                         return -errno;
381
382                 r = readlink_malloc(to, &p);
383                 if (r < 0)
384                         return r;
385
386                 if (!streq(p, from))
387                         return -EINVAL;
388         }
389
390         return 0;
391 }
392
393 int symlink_atomic(const char *from, const char *to) {
394         _cleanup_free_ char *t = NULL;
395         int r;
396
397         assert(from);
398         assert(to);
399
400         r = tempfn_random(to, NULL, &t);
401         if (r < 0)
402                 return r;
403
404         if (symlink(from, t) < 0)
405                 return -errno;
406
407         if (rename(t, to) < 0) {
408                 unlink_noerrno(t);
409                 return -errno;
410         }
411
412         return 0;
413 }
414
415 int mknod_atomic(const char *path, mode_t mode, dev_t dev) {
416         _cleanup_free_ char *t = NULL;
417         int r;
418
419         assert(path);
420
421         r = tempfn_random(path, NULL, &t);
422         if (r < 0)
423                 return r;
424
425         if (mknod(t, mode, dev) < 0)
426                 return -errno;
427
428         if (rename(t, path) < 0) {
429                 unlink_noerrno(t);
430                 return -errno;
431         }
432
433         return 0;
434 }
435
436 int mkfifo_atomic(const char *path, mode_t mode) {
437         _cleanup_free_ char *t = NULL;
438         int r;
439
440         assert(path);
441
442         r = tempfn_random(path, NULL, &t);
443         if (r < 0)
444                 return r;
445
446         if (mkfifo(t, mode) < 0)
447                 return -errno;
448
449         if (rename(t, path) < 0) {
450                 unlink_noerrno(t);
451                 return -errno;
452         }
453
454         return 0;
455 }
456 #endif // 0
457
458 int get_files_in_directory(const char *path, char ***list) {
459         _cleanup_closedir_ DIR *d = NULL;
460         size_t bufsize = 0, n = 0;
461         _cleanup_strv_free_ char **l = NULL;
462
463         assert(path);
464
465         /* Returns all files in a directory in *list, and the number
466          * of files as return value. If list is NULL returns only the
467          * number. */
468
469         d = opendir(path);
470         if (!d)
471                 return -errno;
472
473         for (;;) {
474                 struct dirent *de;
475
476                 errno = 0;
477                 de = readdir(d);
478                 if (!de && errno != 0)
479                         return -errno;
480                 if (!de)
481                         break;
482
483                 dirent_ensure_type(d, de);
484
485                 if (!dirent_is_file(de))
486                         continue;
487
488                 if (list) {
489                         /* one extra slot is needed for the terminating NULL */
490                         if (!GREEDY_REALLOC(l, bufsize, n + 2))
491                                 return -ENOMEM;
492
493                         l[n] = strdup(de->d_name);
494                         if (!l[n])
495                                 return -ENOMEM;
496
497                         l[++n] = NULL;
498                 } else
499                         n++;
500         }
501
502         if (list) {
503                 *list = l;
504                 l = NULL; /* avoid freeing */
505         }
506
507         return n;
508 }