File: | build-scan/../src/basic/fs-util.c |
Warning: | line 804, column 47 Dereference of null pointer (loaded from variable 'done') |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ | ||||
2 | |||||
3 | #include <errno(*__errno_location ()).h> | ||||
4 | #include <stddef.h> | ||||
5 | #include <stdio.h> | ||||
6 | #include <stdlib.h> | ||||
7 | #include <string.h> | ||||
8 | #include <sys/stat.h> | ||||
9 | #include <linux1/magic.h> | ||||
10 | #include <time.h> | ||||
11 | #include <unistd.h> | ||||
12 | |||||
13 | #include "alloc-util.h" | ||||
14 | #include "dirent-util.h" | ||||
15 | #include "fd-util.h" | ||||
16 | #include "fileio.h" | ||||
17 | #include "fs-util.h" | ||||
18 | #include "log.h" | ||||
19 | #include "macro.h" | ||||
20 | #include "missing.h" | ||||
21 | #include "mkdir.h" | ||||
22 | #include "parse-util.h" | ||||
23 | #include "path-util.h" | ||||
24 | #include "process-util.h" | ||||
25 | #include "stat-util.h" | ||||
26 | #include "stdio-util.h" | ||||
27 | #include "string-util.h" | ||||
28 | #include "strv.h" | ||||
29 | #include "time-util.h" | ||||
30 | #include "user-util.h" | ||||
31 | #include "util.h" | ||||
32 | |||||
33 | int unlink_noerrno(const char *path) { | ||||
34 | PROTECT_ERRNO__attribute__((cleanup(_reset_errno_))) __attribute__((unused )) int _saved_errno_ = (*__errno_location ()); | ||||
35 | int r; | ||||
36 | |||||
37 | r = unlink(path); | ||||
38 | if (r < 0) | ||||
39 | return -errno(*__errno_location ()); | ||||
40 | |||||
41 | return 0; | ||||
42 | } | ||||
43 | |||||
44 | int rmdir_parents(const char *path, const char *stop) { | ||||
45 | size_t l; | ||||
46 | int r = 0; | ||||
47 | |||||
48 | assert(path)do { if ((__builtin_expect(!!(!(path)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("path"), "../src/basic/fs-util.c", 48, __PRETTY_FUNCTION__ ); } while (0); | ||||
49 | assert(stop)do { if ((__builtin_expect(!!(!(stop)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("stop"), "../src/basic/fs-util.c", 49, __PRETTY_FUNCTION__ ); } while (0); | ||||
50 | |||||
51 | l = strlen(path); | ||||
52 | |||||
53 | /* Skip trailing slashes */ | ||||
54 | while (l > 0 && path[l-1] == '/') | ||||
55 | l--; | ||||
56 | |||||
57 | while (l > 0) { | ||||
58 | char *t; | ||||
59 | |||||
60 | /* Skip last component */ | ||||
61 | while (l > 0 && path[l-1] != '/') | ||||
62 | l--; | ||||
63 | |||||
64 | /* Skip trailing slashes */ | ||||
65 | while (l > 0 && path[l-1] == '/') | ||||
66 | l--; | ||||
67 | |||||
68 | if (l <= 0) | ||||
69 | break; | ||||
70 | |||||
71 | t = strndup(path, l); | ||||
72 | if (!t) | ||||
73 | return -ENOMEM12; | ||||
74 | |||||
75 | if (path_startswith(stop, t)) { | ||||
76 | free(t); | ||||
77 | return 0; | ||||
78 | } | ||||
79 | |||||
80 | r = rmdir(t); | ||||
81 | free(t); | ||||
82 | |||||
83 | if (r < 0) | ||||
84 | if (errno(*__errno_location ()) != ENOENT2) | ||||
85 | return -errno(*__errno_location ()); | ||||
86 | } | ||||
87 | |||||
88 | return 0; | ||||
89 | } | ||||
90 | |||||
91 | int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { | ||||
92 | struct stat buf; | ||||
93 | int ret; | ||||
94 | |||||
95 | ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE(1 << 0)); | ||||
96 | if (ret >= 0) | ||||
97 | return 0; | ||||
98 | |||||
99 | /* renameat2() exists since Linux 3.15, btrfs added support for it later. | ||||
100 | * If it is not implemented, fallback to another method. */ | ||||
101 | if (!IN_SET(errno, EINVAL, ENOSYS)({ _Bool _found = 0; static __attribute__ ((unused)) char _static_assert__macros_need_to_be_extended [20 - sizeof((int[]){22, 38})/sizeof(int)]; switch((*__errno_location ())) { case 22: case 38: _found = 1; break; default: break; } _found; })) | ||||
102 | return -errno(*__errno_location ()); | ||||
103 | |||||
104 | /* The link()/unlink() fallback does not work on directories. But | ||||
105 | * renameat() without RENAME_NOREPLACE gives the same semantics on | ||||
106 | * directories, except when newpath is an *empty* directory. This is | ||||
107 | * good enough. */ | ||||
108 | ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW0x100); | ||||
109 | if (ret >= 0 && S_ISDIR(buf.st_mode)((((buf.st_mode)) & 0170000) == (0040000))) { | ||||
110 | ret = renameat(olddirfd, oldpath, newdirfd, newpath); | ||||
111 | return ret >= 0 ? 0 : -errno(*__errno_location ()); | ||||
112 | } | ||||
113 | |||||
114 | /* If it is not a directory, use the link()/unlink() fallback. */ | ||||
115 | ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0); | ||||
116 | if (ret < 0) | ||||
117 | return -errno(*__errno_location ()); | ||||
118 | |||||
119 | ret = unlinkat(olddirfd, oldpath, 0); | ||||
120 | if (ret < 0) { | ||||
121 | /* backup errno before the following unlinkat() alters it */ | ||||
122 | ret = errno(*__errno_location ()); | ||||
123 | (void) unlinkat(newdirfd, newpath, 0); | ||||
124 | errno(*__errno_location ()) = ret; | ||||
125 | return -errno(*__errno_location ()); | ||||
126 | } | ||||
127 | |||||
128 | return 0; | ||||
129 | } | ||||
130 | |||||
131 | int readlinkat_malloc(int fd, const char *p, char **ret) { | ||||
132 | size_t l = 100; | ||||
133 | int r; | ||||
134 | |||||
135 | assert(p)do { if ((__builtin_expect(!!(!(p)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("p"), "../src/basic/fs-util.c", 135, __PRETTY_FUNCTION__ ); } while (0); | ||||
136 | assert(ret)do { if ((__builtin_expect(!!(!(ret)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("ret"), "../src/basic/fs-util.c", 136, __PRETTY_FUNCTION__ ); } while (0); | ||||
137 | |||||
138 | for (;;) { | ||||
139 | char *c; | ||||
140 | ssize_t n; | ||||
141 | |||||
142 | c = new(char, l)((char*) malloc_multiply(sizeof(char), (l))); | ||||
143 | if (!c) | ||||
144 | return -ENOMEM12; | ||||
145 | |||||
146 | n = readlinkat(fd, p, c, l-1); | ||||
147 | if (n < 0) { | ||||
148 | r = -errno(*__errno_location ()); | ||||
149 | free(c); | ||||
150 | return r; | ||||
151 | } | ||||
152 | |||||
153 | if ((size_t) n < l-1) { | ||||
154 | c[n] = 0; | ||||
155 | *ret = c; | ||||
156 | return 0; | ||||
157 | } | ||||
158 | |||||
159 | free(c); | ||||
160 | l *= 2; | ||||
161 | } | ||||
162 | } | ||||
163 | |||||
164 | int readlink_malloc(const char *p, char **ret) { | ||||
165 | return readlinkat_malloc(AT_FDCWD-100, p, ret); | ||||
166 | } | ||||
167 | |||||
168 | int readlink_value(const char *p, char **ret) { | ||||
169 | _cleanup_free___attribute__((cleanup(freep))) char *link = NULL((void*)0); | ||||
170 | char *value; | ||||
171 | int r; | ||||
172 | |||||
173 | r = readlink_malloc(p, &link); | ||||
174 | if (r < 0) | ||||
175 | return r; | ||||
176 | |||||
177 | value = basename(link); | ||||
178 | if (!value) | ||||
179 | return -ENOENT2; | ||||
180 | |||||
181 | value = strdup(value); | ||||
182 | if (!value) | ||||
183 | return -ENOMEM12; | ||||
184 | |||||
185 | *ret = value; | ||||
186 | |||||
187 | return 0; | ||||
188 | } | ||||
189 | |||||
190 | int readlink_and_make_absolute(const char *p, char **r) { | ||||
191 | _cleanup_free___attribute__((cleanup(freep))) char *target = NULL((void*)0); | ||||
192 | char *k; | ||||
193 | int j; | ||||
194 | |||||
195 | assert(p)do { if ((__builtin_expect(!!(!(p)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("p"), "../src/basic/fs-util.c", 195, __PRETTY_FUNCTION__ ); } while (0); | ||||
196 | assert(r)do { if ((__builtin_expect(!!(!(r)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("r"), "../src/basic/fs-util.c", 196, __PRETTY_FUNCTION__ ); } while (0); | ||||
197 | |||||
198 | j = readlink_malloc(p, &target); | ||||
199 | if (j < 0) | ||||
200 | return j; | ||||
201 | |||||
202 | k = file_in_same_dir(p, target); | ||||
203 | if (!k) | ||||
204 | return -ENOMEM12; | ||||
205 | |||||
206 | *r = k; | ||||
207 | return 0; | ||||
208 | } | ||||
209 | |||||
210 | int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { | ||||
211 | assert(path)do { if ((__builtin_expect(!!(!(path)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("path"), "../src/basic/fs-util.c", 211, __PRETTY_FUNCTION__ ); } while (0); | ||||
212 | |||||
213 | /* Under the assumption that we are running privileged we | ||||
214 | * first change the access mode and only then hand out | ||||
215 | * ownership to avoid a window where access is too open. */ | ||||
216 | |||||
217 | if (mode != MODE_INVALID((mode_t) -1)) | ||||
218 | if (chmod(path, mode) < 0) | ||||
219 | return -errno(*__errno_location ()); | ||||
220 | |||||
221 | if (uid != UID_INVALID((uid_t) -1) || gid != GID_INVALID((gid_t) -1)) | ||||
222 | if (chown(path, uid, gid) < 0) | ||||
223 | return -errno(*__errno_location ()); | ||||
224 | |||||
225 | return 0; | ||||
226 | } | ||||
227 | |||||
228 | int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) { | ||||
229 | /* Under the assumption that we are running privileged we | ||||
230 | * first change the access mode and only then hand out | ||||
231 | * ownership to avoid a window where access is too open. */ | ||||
232 | |||||
233 | if (mode != MODE_INVALID((mode_t) -1)) | ||||
234 | if (fchmod(fd, mode) < 0) | ||||
235 | return -errno(*__errno_location ()); | ||||
236 | |||||
237 | if (uid != UID_INVALID((uid_t) -1) || gid != GID_INVALID((gid_t) -1)) | ||||
238 | if (fchown(fd, uid, gid) < 0) | ||||
239 | return -errno(*__errno_location ()); | ||||
240 | |||||
241 | return 0; | ||||
242 | } | ||||
243 | |||||
244 | int fchmod_umask(int fd, mode_t m) { | ||||
245 | mode_t u; | ||||
246 | int r; | ||||
247 | |||||
248 | u = umask(0777); | ||||
249 | r = fchmod(fd, m & (~u)) < 0 ? -errno(*__errno_location ()) : 0; | ||||
250 | umask(u); | ||||
251 | |||||
252 | return r; | ||||
253 | } | ||||
254 | |||||
255 | int fchmod_opath(int fd, mode_t m) { | ||||
256 | char procfs_path[STRLEN("/proc/self/fd/")(sizeof("""/proc/self/fd/""") - 1) + DECIMAL_STR_MAX(int)(2+(sizeof(int) <= 1 ? 3 : sizeof(int) <= 2 ? 5 : sizeof (int) <= 4 ? 10 : sizeof(int) <= 8 ? 20 : sizeof(int[-2 *(sizeof(int) > 8)])))]; | ||||
257 | |||||
258 | /* This function operates also on fd that might have been opened with | ||||
259 | * O_PATH. Indeed fchmodat() doesn't have the AT_EMPTY_PATH flag like | ||||
260 | * fchownat() does. */ | ||||
261 | |||||
262 | xsprintf(procfs_path, "/proc/self/fd/%i", fd)do { if ((__builtin_expect(!!(!(((size_t) snprintf(procfs_path , __extension__ (__builtin_choose_expr( !__builtin_types_compatible_p (typeof(procfs_path), typeof(&*(procfs_path))), sizeof(procfs_path )/sizeof((procfs_path)[0]), ((void)0))), "/proc/self/fd/%i", fd ) < (__extension__ (__builtin_choose_expr( !__builtin_types_compatible_p (typeof(procfs_path), typeof(&*(procfs_path))), sizeof(procfs_path )/sizeof((procfs_path)[0]), ((void)0))))))),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("xsprintf: " "procfs_path" "[] must be big enough" ), "../src/basic/fs-util.c", 262, __PRETTY_FUNCTION__); } while (0); | ||||
263 | |||||
264 | if (chmod(procfs_path, m) < 0) | ||||
265 | return -errno(*__errno_location ()); | ||||
266 | |||||
267 | return 0; | ||||
268 | } | ||||
269 | |||||
270 | int fd_warn_permissions(const char *path, int fd) { | ||||
271 | struct stat st; | ||||
272 | |||||
273 | if (fstat(fd, &st) < 0) | ||||
274 | return -errno(*__errno_location ()); | ||||
275 | |||||
276 | if (st.st_mode & 0111) | ||||
277 | log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path)({ int _level = (((4))), _e = ((0)), _realm = (LOG_REALM_SYSTEMD ); (log_get_max_level_realm(_realm) >= ((_level) & 0x07 )) ? log_internal_realm(((_realm) << 10 | (_level)), _e , "../src/basic/fs-util.c", 277, __func__, "Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway." , path) : -abs(_e); }); | ||||
278 | |||||
279 | if (st.st_mode & 0002) | ||||
280 | log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path)({ int _level = (((4))), _e = ((0)), _realm = (LOG_REALM_SYSTEMD ); (log_get_max_level_realm(_realm) >= ((_level) & 0x07 )) ? log_internal_realm(((_realm) << 10 | (_level)), _e , "../src/basic/fs-util.c", 280, __func__, "Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway." , path) : -abs(_e); }); | ||||
281 | |||||
282 | if (getpid_cached() == 1 && (st.st_mode & 0044) != 0044) | ||||
283 | 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)({ int _level = (((4))), _e = ((0)), _realm = (LOG_REALM_SYSTEMD ); (log_get_max_level_realm(_realm) >= ((_level) & 0x07 )) ? log_internal_realm(((_realm) << 10 | (_level)), _e , "../src/basic/fs-util.c", 283, __func__, "Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway." , path) : -abs(_e); }); | ||||
284 | |||||
285 | return 0; | ||||
286 | } | ||||
287 | |||||
288 | int touch_file(const char *path, bool_Bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) { | ||||
289 | char fdpath[STRLEN("/proc/self/fd/")(sizeof("""/proc/self/fd/""") - 1) + DECIMAL_STR_MAX(int)(2+(sizeof(int) <= 1 ? 3 : sizeof(int) <= 2 ? 5 : sizeof (int) <= 4 ? 10 : sizeof(int) <= 8 ? 20 : sizeof(int[-2 *(sizeof(int) > 8)])))]; | ||||
290 | _cleanup_close___attribute__((cleanup(closep))) int fd = -1; | ||||
291 | int r, ret = 0; | ||||
292 | |||||
293 | assert(path)do { if ((__builtin_expect(!!(!(path)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("path"), "../src/basic/fs-util.c", 293, __PRETTY_FUNCTION__ ); } while (0); | ||||
294 | |||||
295 | /* Note that touch_file() does not follow symlinks: if invoked on an existing symlink, then it is the symlink | ||||
296 | * itself which is updated, not its target | ||||
297 | * | ||||
298 | * Returns the first error we encounter, but tries to apply as much as possible. */ | ||||
299 | |||||
300 | if (parents) | ||||
301 | (void) mkdir_parents(path, 0755); | ||||
302 | |||||
303 | /* Initially, we try to open the node with O_PATH, so that we get a reference to the node. This is useful in | ||||
304 | * case the path refers to an existing device or socket node, as we can open it successfully in all cases, and | ||||
305 | * won't trigger any driver magic or so. */ | ||||
306 | fd = open(path, O_PATH010000000|O_CLOEXEC02000000|O_NOFOLLOW0400000); | ||||
307 | if (fd < 0) { | ||||
308 | if (errno(*__errno_location ()) != ENOENT2) | ||||
309 | return -errno(*__errno_location ()); | ||||
310 | |||||
311 | /* if the node doesn't exist yet, we create it, but with O_EXCL, so that we only create a regular file | ||||
312 | * here, and nothing else */ | ||||
313 | fd = open(path, O_WRONLY01|O_CREAT0100|O_EXCL0200|O_CLOEXEC02000000, IN_SET(mode, 0, MODE_INVALID)({ _Bool _found = 0; static __attribute__ ((unused)) char _static_assert__macros_need_to_be_extended [20 - sizeof((int[]){0, ((mode_t) -1)})/sizeof(int)]; switch( mode) { case 0: case ((mode_t) -1): _found = 1; break; default : break; } _found; }) ? 0644 : mode); | ||||
314 | if (fd < 0) | ||||
315 | return -errno(*__errno_location ()); | ||||
316 | } | ||||
317 | |||||
318 | /* Let's make a path from the fd, and operate on that. With this logic, we can adjust the access mode, | ||||
319 | * ownership and time of the file node in all cases, even if the fd refers to an O_PATH object — which is | ||||
320 | * something fchown(), fchmod(), futimensat() don't allow. */ | ||||
321 | xsprintf(fdpath, "/proc/self/fd/%i", fd)do { if ((__builtin_expect(!!(!(((size_t) snprintf(fdpath, __extension__ (__builtin_choose_expr( !__builtin_types_compatible_p(typeof (fdpath), typeof(&*(fdpath))), sizeof(fdpath)/sizeof((fdpath )[0]), ((void)0))), "/proc/self/fd/%i", fd) < (__extension__ (__builtin_choose_expr( !__builtin_types_compatible_p(typeof (fdpath), typeof(&*(fdpath))), sizeof(fdpath)/sizeof((fdpath )[0]), ((void)0))))))),0))) log_assert_failed_realm(LOG_REALM_SYSTEMD , ("xsprintf: " "fdpath" "[] must be big enough"), "../src/basic/fs-util.c" , 321, __PRETTY_FUNCTION__); } while (0); | ||||
322 | |||||
323 | if (mode != MODE_INVALID((mode_t) -1)) | ||||
324 | if (chmod(fdpath, mode) < 0) | ||||
325 | ret = -errno(*__errno_location ()); | ||||
326 | |||||
327 | if (uid_is_valid(uid) || gid_is_valid(gid)) | ||||
328 | if (chown(fdpath, uid, gid) < 0 && ret >= 0) | ||||
329 | ret = -errno(*__errno_location ()); | ||||
330 | |||||
331 | if (stamp != USEC_INFINITY((usec_t) -1)) { | ||||
332 | struct timespec ts[2]; | ||||
333 | |||||
334 | timespec_store(&ts[0], stamp); | ||||
335 | ts[1] = ts[0]; | ||||
336 | r = utimensat(AT_FDCWD-100, fdpath, ts, 0); | ||||
337 | } else | ||||
338 | r = utimensat(AT_FDCWD-100, fdpath, NULL((void*)0), 0); | ||||
339 | if (r < 0 && ret >= 0) | ||||
340 | return -errno(*__errno_location ()); | ||||
341 | |||||
342 | return ret; | ||||
343 | } | ||||
344 | |||||
345 | int touch(const char *path) { | ||||
346 | return touch_file(path, false0, USEC_INFINITY((usec_t) -1), UID_INVALID((uid_t) -1), GID_INVALID((gid_t) -1), MODE_INVALID((mode_t) -1)); | ||||
347 | } | ||||
348 | |||||
349 | int symlink_idempotent(const char *from, const char *to) { | ||||
350 | int r; | ||||
351 | |||||
352 | assert(from)do { if ((__builtin_expect(!!(!(from)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("from"), "../src/basic/fs-util.c", 352, __PRETTY_FUNCTION__ ); } while (0); | ||||
353 | assert(to)do { if ((__builtin_expect(!!(!(to)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("to"), "../src/basic/fs-util.c", 353, __PRETTY_FUNCTION__ ); } while (0); | ||||
354 | |||||
355 | if (symlink(from, to) < 0) { | ||||
356 | _cleanup_free___attribute__((cleanup(freep))) char *p = NULL((void*)0); | ||||
357 | |||||
358 | if (errno(*__errno_location ()) != EEXIST17) | ||||
359 | return -errno(*__errno_location ()); | ||||
360 | |||||
361 | r = readlink_malloc(to, &p); | ||||
362 | if (r == -EINVAL22) /* Not a symlink? In that case return the original error we encountered: -EEXIST */ | ||||
363 | return -EEXIST17; | ||||
364 | if (r < 0) /* Any other error? In that case propagate it as is */ | ||||
365 | return r; | ||||
366 | |||||
367 | if (!streq(p, from)(strcmp((p),(from)) == 0)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */ | ||||
368 | return -EEXIST17; | ||||
369 | } | ||||
370 | |||||
371 | return 0; | ||||
372 | } | ||||
373 | |||||
374 | int symlink_atomic(const char *from, const char *to) { | ||||
375 | _cleanup_free___attribute__((cleanup(freep))) char *t = NULL((void*)0); | ||||
376 | int r; | ||||
377 | |||||
378 | assert(from)do { if ((__builtin_expect(!!(!(from)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("from"), "../src/basic/fs-util.c", 378, __PRETTY_FUNCTION__ ); } while (0); | ||||
379 | assert(to)do { if ((__builtin_expect(!!(!(to)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("to"), "../src/basic/fs-util.c", 379, __PRETTY_FUNCTION__ ); } while (0); | ||||
380 | |||||
381 | r = tempfn_random(to, NULL((void*)0), &t); | ||||
382 | if (r < 0) | ||||
383 | return r; | ||||
384 | |||||
385 | if (symlink(from, t) < 0) | ||||
386 | return -errno(*__errno_location ()); | ||||
387 | |||||
388 | if (rename(t, to) < 0) { | ||||
389 | unlink_noerrno(t); | ||||
390 | return -errno(*__errno_location ()); | ||||
391 | } | ||||
392 | |||||
393 | return 0; | ||||
394 | } | ||||
395 | |||||
396 | int mknod_atomic(const char *path, mode_t mode, dev_t dev) { | ||||
397 | _cleanup_free___attribute__((cleanup(freep))) char *t = NULL((void*)0); | ||||
398 | int r; | ||||
399 | |||||
400 | assert(path)do { if ((__builtin_expect(!!(!(path)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("path"), "../src/basic/fs-util.c", 400, __PRETTY_FUNCTION__ ); } while (0); | ||||
401 | |||||
402 | r = tempfn_random(path, NULL((void*)0), &t); | ||||
403 | if (r < 0) | ||||
404 | return r; | ||||
405 | |||||
406 | if (mknod(t, mode, dev) < 0) | ||||
407 | return -errno(*__errno_location ()); | ||||
408 | |||||
409 | if (rename(t, path) < 0) { | ||||
410 | unlink_noerrno(t); | ||||
411 | return -errno(*__errno_location ()); | ||||
412 | } | ||||
413 | |||||
414 | return 0; | ||||
415 | } | ||||
416 | |||||
417 | int mkfifo_atomic(const char *path, mode_t mode) { | ||||
418 | _cleanup_free___attribute__((cleanup(freep))) char *t = NULL((void*)0); | ||||
419 | int r; | ||||
420 | |||||
421 | assert(path)do { if ((__builtin_expect(!!(!(path)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("path"), "../src/basic/fs-util.c", 421, __PRETTY_FUNCTION__ ); } while (0); | ||||
422 | |||||
423 | r = tempfn_random(path, NULL((void*)0), &t); | ||||
424 | if (r < 0) | ||||
425 | return r; | ||||
426 | |||||
427 | if (mkfifo(t, mode) < 0) | ||||
428 | return -errno(*__errno_location ()); | ||||
429 | |||||
430 | if (rename(t, path) < 0) { | ||||
431 | unlink_noerrno(t); | ||||
432 | return -errno(*__errno_location ()); | ||||
433 | } | ||||
434 | |||||
435 | return 0; | ||||
436 | } | ||||
437 | |||||
438 | int get_files_in_directory(const char *path, char ***list) { | ||||
439 | _cleanup_closedir___attribute__((cleanup(closedirp))) DIR *d = NULL((void*)0); | ||||
440 | struct dirent *de; | ||||
441 | size_t bufsize = 0, n = 0; | ||||
442 | _cleanup_strv_free___attribute__((cleanup(strv_freep))) char **l = NULL((void*)0); | ||||
443 | |||||
444 | assert(path)do { if ((__builtin_expect(!!(!(path)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("path"), "../src/basic/fs-util.c", 444, __PRETTY_FUNCTION__ ); } while (0); | ||||
445 | |||||
446 | /* Returns all files in a directory in *list, and the number | ||||
447 | * of files as return value. If list is NULL returns only the | ||||
448 | * number. */ | ||||
449 | |||||
450 | d = opendir(path); | ||||
451 | if (!d) | ||||
452 | return -errno(*__errno_location ()); | ||||
453 | |||||
454 | FOREACH_DIRENT_ALL(de, d, return -errno)for ((*__errno_location ()) = 0, de = readdir(d);; (*__errno_location ()) = 0, de = readdir(d)) if (!de) { if ((*__errno_location ( )) > 0) { return -(*__errno_location ()); } break; } else { | ||||
455 | dirent_ensure_type(d, de); | ||||
456 | |||||
457 | if (!dirent_is_file(de)) | ||||
458 | continue; | ||||
459 | |||||
460 | if (list) { | ||||
461 | /* one extra slot is needed for the terminating NULL */ | ||||
462 | if (!GREEDY_REALLOC(l, bufsize, n + 2)greedy_realloc((void**) &(l), &(bufsize), (n + 2), sizeof ((l)[0]))) | ||||
463 | return -ENOMEM12; | ||||
464 | |||||
465 | l[n] = strdup(de->d_name); | ||||
466 | if (!l[n]) | ||||
467 | return -ENOMEM12; | ||||
468 | |||||
469 | l[++n] = NULL((void*)0); | ||||
470 | } else | ||||
471 | n++; | ||||
472 | } | ||||
473 | |||||
474 | if (list) | ||||
475 | *list = TAKE_PTR(l)({ typeof(l) _ptr_ = (l); (l) = ((void*)0); _ptr_; }); | ||||
476 | |||||
477 | return n; | ||||
478 | } | ||||
479 | |||||
480 | static int getenv_tmp_dir(const char **ret_path) { | ||||
481 | const char *n; | ||||
482 | int r, ret = 0; | ||||
483 | |||||
484 | assert(ret_path)do { if ((__builtin_expect(!!(!(ret_path)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("ret_path"), "../src/basic/fs-util.c", 484 , __PRETTY_FUNCTION__); } while (0); | ||||
485 | |||||
486 | /* We use the same order of environment variables python uses in tempfile.gettempdir(): | ||||
487 | * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */ | ||||
488 | FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP")for (char **_l = ({ char **_ll = ((char**) ((const char*[]) { "TMPDIR", "TEMP", "TMP", ((void*)0) })); n = _ll ? _ll[0] : ( (void*)0); _ll; }); _l && *_l; n = ({ _l ++; _l[0]; } )) { | ||||
489 | const char *e; | ||||
490 | |||||
491 | e = secure_getenv(n); | ||||
492 | if (!e) | ||||
493 | continue; | ||||
494 | if (!path_is_absolute(e)) { | ||||
495 | r = -ENOTDIR20; | ||||
496 | goto next; | ||||
497 | } | ||||
498 | if (!path_is_normalized(e)) { | ||||
499 | r = -EPERM1; | ||||
500 | goto next; | ||||
501 | } | ||||
502 | |||||
503 | r = is_dir(e, true1); | ||||
504 | if (r < 0) | ||||
505 | goto next; | ||||
506 | if (r == 0) { | ||||
507 | r = -ENOTDIR20; | ||||
508 | goto next; | ||||
509 | } | ||||
510 | |||||
511 | *ret_path = e; | ||||
512 | return 1; | ||||
513 | |||||
514 | next: | ||||
515 | /* Remember first error, to make this more debuggable */ | ||||
516 | if (ret >= 0) | ||||
517 | ret = r; | ||||
518 | } | ||||
519 | |||||
520 | if (ret < 0) | ||||
521 | return ret; | ||||
522 | |||||
523 | *ret_path = NULL((void*)0); | ||||
524 | return ret; | ||||
525 | } | ||||
526 | |||||
527 | static int tmp_dir_internal(const char *def, const char **ret) { | ||||
528 | const char *e; | ||||
529 | int r, k; | ||||
530 | |||||
531 | assert(def)do { if ((__builtin_expect(!!(!(def)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("def"), "../src/basic/fs-util.c", 531, __PRETTY_FUNCTION__ ); } while (0); | ||||
532 | assert(ret)do { if ((__builtin_expect(!!(!(ret)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("ret"), "../src/basic/fs-util.c", 532, __PRETTY_FUNCTION__ ); } while (0); | ||||
533 | |||||
534 | r = getenv_tmp_dir(&e); | ||||
535 | if (r > 0) { | ||||
536 | *ret = e; | ||||
537 | return 0; | ||||
538 | } | ||||
539 | |||||
540 | k = is_dir(def, true1); | ||||
541 | if (k == 0) | ||||
542 | k = -ENOTDIR20; | ||||
543 | if (k < 0) | ||||
544 | return r < 0 ? r : k; | ||||
545 | |||||
546 | *ret = def; | ||||
547 | return 0; | ||||
548 | } | ||||
549 | |||||
550 | int var_tmp_dir(const char **ret) { | ||||
551 | |||||
552 | /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus | ||||
553 | * even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is | ||||
554 | * returned preferably however. Note that both this function and tmp_dir() below are affected by $TMPDIR, | ||||
555 | * making it a variable that overrides all temporary file storage locations. */ | ||||
556 | |||||
557 | return tmp_dir_internal("/var/tmp", ret); | ||||
558 | } | ||||
559 | |||||
560 | int tmp_dir(const char **ret) { | ||||
561 | |||||
562 | /* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually | ||||
563 | * backed by an in-memory file system: /tmp. */ | ||||
564 | |||||
565 | return tmp_dir_internal("/tmp", ret); | ||||
566 | } | ||||
567 | |||||
568 | int unlink_or_warn(const char *filename) { | ||||
569 | if (unlink(filename) < 0 && errno(*__errno_location ()) != ENOENT2) | ||||
570 | /* If the file doesn't exist and the fs simply was read-only (in which | ||||
571 | * case unlink() returns EROFS even if the file doesn't exist), don't | ||||
572 | * complain */ | ||||
573 | if (errno(*__errno_location ()) != EROFS30 || access(filename, F_OK0) >= 0) | ||||
574 | return log_error_errno(errno, "Failed to remove \"%s\": %m", filename)({ int _level = ((3)), _e = (((*__errno_location ()))), _realm = (LOG_REALM_SYSTEMD); (log_get_max_level_realm(_realm) >= ((_level) & 0x07)) ? log_internal_realm(((_realm) << 10 | (_level)), _e, "../src/basic/fs-util.c", 574, __func__, "Failed to remove \"%s\": %m", filename) : -abs(_e); }); | ||||
575 | |||||
576 | return 0; | ||||
577 | } | ||||
578 | |||||
579 | int inotify_add_watch_fd(int fd, int what, uint32_t mask) { | ||||
580 | char path[STRLEN("/proc/self/fd/")(sizeof("""/proc/self/fd/""") - 1) + DECIMAL_STR_MAX(int)(2+(sizeof(int) <= 1 ? 3 : sizeof(int) <= 2 ? 5 : sizeof (int) <= 4 ? 10 : sizeof(int) <= 8 ? 20 : sizeof(int[-2 *(sizeof(int) > 8)]))) + 1]; | ||||
581 | int r; | ||||
582 | |||||
583 | /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */ | ||||
584 | xsprintf(path, "/proc/self/fd/%i", what)do { if ((__builtin_expect(!!(!(((size_t) snprintf(path, __extension__ (__builtin_choose_expr( !__builtin_types_compatible_p(typeof (path), typeof(&*(path))), sizeof(path)/sizeof((path)[0]) , ((void)0))), "/proc/self/fd/%i", what) < (__extension__ ( __builtin_choose_expr( !__builtin_types_compatible_p(typeof(path ), typeof(&*(path))), sizeof(path)/sizeof((path)[0]), ((void )0))))))),0))) log_assert_failed_realm(LOG_REALM_SYSTEMD, ("xsprintf: " "path" "[] must be big enough"), "../src/basic/fs-util.c", 584 , __PRETTY_FUNCTION__); } while (0); | ||||
585 | |||||
586 | r = inotify_add_watch(fd, path, mask); | ||||
587 | if (r < 0) | ||||
588 | return -errno(*__errno_location ()); | ||||
589 | |||||
590 | return r; | ||||
591 | } | ||||
592 | |||||
593 | static bool_Bool safe_transition(const struct stat *a, const struct stat *b) { | ||||
594 | /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to | ||||
595 | * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files | ||||
596 | * making us believe we read something safe even though it isn't safe in the specific context we open it in. */ | ||||
597 | |||||
598 | if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */ | ||||
599 | return true1; | ||||
600 | |||||
601 | return a->st_uid == b->st_uid; /* Otherwise we need to stay within the same UID */ | ||||
602 | } | ||||
603 | |||||
604 | int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) { | ||||
605 | _cleanup_free___attribute__((cleanup(freep))) char *buffer = NULL((void*)0), *done = NULL((void*)0), *root = NULL((void*)0); | ||||
| |||||
606 | _cleanup_close___attribute__((cleanup(closep))) int fd = -1; | ||||
607 | unsigned max_follow = CHASE_SYMLINKS_MAX32; /* how many symlinks to follow before giving up and returning ELOOP */ | ||||
608 | struct stat previous_stat; | ||||
609 | bool_Bool exists = true1; | ||||
610 | char *todo; | ||||
611 | int r; | ||||
612 | |||||
613 | assert(path)do { if ((__builtin_expect(!!(!(path)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("path"), "../src/basic/fs-util.c", 613, __PRETTY_FUNCTION__ ); } while (0); | ||||
614 | |||||
615 | /* Either the file may be missing, or we return an fd to the final object, but both make no sense */ | ||||
616 | if (FLAGS_SET(flags, CHASE_NONEXISTENT | CHASE_OPEN)(((flags) & (CHASE_NONEXISTENT | CHASE_OPEN)) == (CHASE_NONEXISTENT | CHASE_OPEN))) | ||||
617 | return -EINVAL22; | ||||
618 | |||||
619 | if (FLAGS_SET(flags, CHASE_STEP | CHASE_OPEN)(((flags) & (CHASE_STEP | CHASE_OPEN)) == (CHASE_STEP | CHASE_OPEN ))) | ||||
620 | return -EINVAL22; | ||||
621 | |||||
622 | if (isempty(path)) | ||||
623 | return -EINVAL22; | ||||
624 | |||||
625 | /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following | ||||
626 | * symlinks relative to a root directory, instead of the root of the host. | ||||
627 | * | ||||
628 | * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following | ||||
629 | * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is | ||||
630 | * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first | ||||
631 | * prefixed accordingly. | ||||
632 | * | ||||
633 | * Algorithmically this operates on two path buffers: "done" are the components of the path we already | ||||
634 | * processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to | ||||
635 | * process. On each iteration, we move one component from "todo" to "done", processing it's special meaning | ||||
636 | * each time. The "todo" path always starts with at least one slash, the "done" path always ends in no | ||||
637 | * slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races | ||||
638 | * at a minimum. | ||||
639 | * | ||||
640 | * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got | ||||
641 | * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this | ||||
642 | * function what to do when encountering a symlink with an absolute path as directory: prefix it by the | ||||
643 | * specified path. | ||||
644 | * | ||||
645 | * There are three ways to invoke this function: | ||||
646 | * | ||||
647 | * 1. Without CHASE_STEP or CHASE_OPEN: in this case the path is resolved and the normalized path is returned | ||||
648 | * in `ret`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set 0 is returned if the file | ||||
649 | * doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set >= 0 is returned if the destination was | ||||
650 | * found, -ENOENT if it doesn't. | ||||
651 | * | ||||
652 | * 2. With CHASE_OPEN: in this case the destination is opened after chasing it as O_PATH and this file | ||||
653 | * descriptor is returned as return value. This is useful to open files relative to some root | ||||
654 | * directory. Note that the returned O_PATH file descriptors must be converted into a regular one (using | ||||
655 | * fd_reopen() or such) before it can be used for reading/writing. CHASE_OPEN may not be combined with | ||||
656 | * CHASE_NONEXISTENT. | ||||
657 | * | ||||
658 | * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only the first | ||||
659 | * symlink or ".." component of the path is resolved, and the resulting path is returned. This is useful if | ||||
660 | * a caller wants to trace the a path through the file system verbosely. Returns < 0 on error, > 0 if the | ||||
661 | * path is fully normalized, and == 0 for each normalization step. This may be combined with | ||||
662 | * CHASE_NONEXISTENT, in which case 1 is returned when a component is not found. | ||||
663 | * | ||||
664 | * */ | ||||
665 | |||||
666 | /* A root directory of "/" or "" is identical to none */ | ||||
667 | if (empty_or_root(original_root)) | ||||
668 | original_root = NULL((void*)0); | ||||
669 | |||||
670 | if (!original_root && !ret && (flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_OPEN|CHASE_STEP)) == CHASE_OPEN) { | ||||
671 | /* Shortcut the CHASE_OPEN case if the caller isn't interested in the actual path and has no root set | ||||
672 | * and doesn't care about any of the other special features we provide either. */ | ||||
673 | r = open(path, O_PATH010000000|O_CLOEXEC02000000); | ||||
674 | if (r < 0) | ||||
675 | return -errno(*__errno_location ()); | ||||
676 | |||||
677 | return r; | ||||
678 | } | ||||
679 | |||||
680 | if (original_root
| ||||
681 | r = path_make_absolute_cwd(original_root, &root); | ||||
682 | if (r < 0) | ||||
683 | return r; | ||||
684 | |||||
685 | if (flags & CHASE_PREFIX_ROOT) { | ||||
686 | |||||
687 | /* We don't support relative paths in combination with a root directory */ | ||||
688 | if (!path_is_absolute(path)) | ||||
689 | return -EINVAL22; | ||||
690 | |||||
691 | path = prefix_roota(root, path)({ const char* _path = (path), *_root = (root), *_ret; char * _p, *_n; size_t _l; while (_path[0] == '/' && _path[1 ] == '/') _path ++; if (empty_or_root(_root)) _ret = _path; else { _l = strlen(_root) + 1 + strlen(_path) + 1; _n = __builtin_alloca (_l); _p = stpcpy(_n, _root); while (_p > _n && _p [-1] == '/') _p--; if (_path[0] != '/') *(_p++) = '/'; strcpy (_p, _path); _ret = _n; } _ret; }); | ||||
692 | } | ||||
693 | } | ||||
694 | |||||
695 | r = path_make_absolute_cwd(path, &buffer); | ||||
696 | if (r < 0) | ||||
697 | return r; | ||||
698 | |||||
699 | fd = open("/", O_CLOEXEC02000000|O_NOFOLLOW0400000|O_PATH010000000); | ||||
700 | if (fd < 0) | ||||
701 | return -errno(*__errno_location ()); | ||||
702 | |||||
703 | if (flags & CHASE_SAFE) { | ||||
704 | if (fstat(fd, &previous_stat) < 0) | ||||
705 | return -errno(*__errno_location ()); | ||||
706 | } | ||||
707 | |||||
708 | todo = buffer; | ||||
709 | for (;;) { | ||||
710 | _cleanup_free___attribute__((cleanup(freep))) char *first = NULL((void*)0); | ||||
711 | _cleanup_close___attribute__((cleanup(closep))) int child = -1; | ||||
712 | struct stat st; | ||||
713 | size_t n, m; | ||||
714 | |||||
715 | /* Determine length of first component in the path */ | ||||
716 | n = strspn(todo, "/"); /* The slashes */ | ||||
717 | m = n + strcspn(todo + n, "/"); /* The entire length of the component */ | ||||
718 | |||||
719 | /* Extract the first component. */ | ||||
720 | first = strndup(todo, m); | ||||
721 | if (!first) | ||||
722 | return -ENOMEM12; | ||||
723 | |||||
724 | todo += m; | ||||
725 | |||||
726 | /* Empty? Then we reached the end. */ | ||||
727 | if (isempty(first)) | ||||
728 | break; | ||||
729 | |||||
730 | /* Just a single slash? Then we reached the end. */ | ||||
731 | if (path_equal(first, "/")) { | ||||
732 | /* Preserve the trailing slash */ | ||||
733 | |||||
734 | if (flags & CHASE_TRAIL_SLASH) | ||||
735 | if (!strextend(&done, "/", NULL)strextend_with_separator(&done, ((void*)0), "/", ((void*) 0))) | ||||
736 | return -ENOMEM12; | ||||
737 | |||||
738 | break; | ||||
739 | } | ||||
740 | |||||
741 | /* Just a dot? Then let's eat this up. */ | ||||
742 | if (path_equal(first, "/.")) | ||||
743 | continue; | ||||
744 | |||||
745 | /* Two dots? Then chop off the last bit of what we already found out. */ | ||||
746 | if (path_equal(first, "/..")) { | ||||
747 | _cleanup_free___attribute__((cleanup(freep))) char *parent = NULL((void*)0); | ||||
748 | _cleanup_close___attribute__((cleanup(closep))) int fd_parent = -1; | ||||
749 | |||||
750 | /* If we already are at the top, then going up will not change anything. This is in-line with | ||||
751 | * how the kernel handles this. */ | ||||
752 | if (empty_or_root(done)) | ||||
753 | continue; | ||||
754 | |||||
755 | parent = dirname_malloc(done); | ||||
756 | if (!parent) | ||||
757 | return -ENOMEM12; | ||||
758 | |||||
759 | /* Don't allow this to leave the root dir. */ | ||||
760 | if (root && | ||||
761 | path_startswith(done, root) && | ||||
762 | !path_startswith(parent, root)) | ||||
763 | continue; | ||||
764 | |||||
765 | free_and_replace(done, parent)({ free(done); (done) = (parent); (parent) = ((void*)0); 0; } ); | ||||
766 | |||||
767 | if (flags & CHASE_STEP) | ||||
768 | goto chased_one; | ||||
769 | |||||
770 | fd_parent = openat(fd, "..", O_CLOEXEC02000000|O_NOFOLLOW0400000|O_PATH010000000); | ||||
771 | if (fd_parent < 0) | ||||
772 | return -errno(*__errno_location ()); | ||||
773 | |||||
774 | if (flags & CHASE_SAFE) { | ||||
775 | if (fstat(fd_parent, &st) < 0) | ||||
776 | return -errno(*__errno_location ()); | ||||
777 | |||||
778 | if (!safe_transition(&previous_stat, &st)) | ||||
779 | return -EPERM1; | ||||
780 | |||||
781 | previous_stat = st; | ||||
782 | } | ||||
783 | |||||
784 | safe_close(fd); | ||||
785 | fd = TAKE_FD(fd_parent)({ int _fd_ = (fd_parent); (fd_parent) = -1; _fd_; }); | ||||
786 | |||||
787 | continue; | ||||
788 | } | ||||
789 | |||||
790 | /* Otherwise let's see what this is. */ | ||||
791 | child = openat(fd, first + n, O_CLOEXEC02000000|O_NOFOLLOW0400000|O_PATH010000000); | ||||
792 | if (child < 0) { | ||||
793 | |||||
794 | if (errno(*__errno_location ()) == ENOENT2 && | ||||
795 | (flags & CHASE_NONEXISTENT) && | ||||
796 | (isempty(todo) || path_is_normalized(todo))) { | ||||
797 | |||||
798 | /* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return | ||||
799 | * what we got so far. But don't allow this if the remaining path contains "../ or "./" | ||||
800 | * or something else weird. */ | ||||
801 | |||||
802 | /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */ | ||||
803 | if (streq_ptr(done, "/")) | ||||
804 | *done = '\0'; | ||||
| |||||
805 | |||||
806 | if (!strextend(&done, first, todo, NULL)strextend_with_separator(&done, ((void*)0), first, todo, ( (void*)0))) | ||||
807 | return -ENOMEM12; | ||||
808 | |||||
809 | exists = false0; | ||||
810 | break; | ||||
811 | } | ||||
812 | |||||
813 | return -errno(*__errno_location ()); | ||||
814 | } | ||||
815 | |||||
816 | if (fstat(child, &st) < 0) | ||||
817 | return -errno(*__errno_location ()); | ||||
818 | if ((flags & CHASE_SAFE) && | ||||
819 | !safe_transition(&previous_stat, &st)) | ||||
820 | return -EPERM1; | ||||
821 | |||||
822 | previous_stat = st; | ||||
823 | |||||
824 | if ((flags & CHASE_NO_AUTOFS) && | ||||
825 | fd_is_fs_type(child, AUTOFS_SUPER_MAGIC0x0187) > 0) | ||||
826 | return -EREMOTE66; | ||||
827 | |||||
828 | if (S_ISLNK(st.st_mode)((((st.st_mode)) & 0170000) == (0120000))) { | ||||
829 | char *joined; | ||||
830 | |||||
831 | _cleanup_free___attribute__((cleanup(freep))) char *destination = NULL((void*)0); | ||||
832 | |||||
833 | /* This is a symlink, in this case read the destination. But let's make sure we don't follow | ||||
834 | * symlinks without bounds. */ | ||||
835 | if (--max_follow <= 0) | ||||
836 | return -ELOOP40; | ||||
837 | |||||
838 | r = readlinkat_malloc(fd, first + n, &destination); | ||||
839 | if (r < 0) | ||||
840 | return r; | ||||
841 | if (isempty(destination)) | ||||
842 | return -EINVAL22; | ||||
843 | |||||
844 | if (path_is_absolute(destination)) { | ||||
845 | |||||
846 | /* An absolute destination. Start the loop from the beginning, but use the root | ||||
847 | * directory as base. */ | ||||
848 | |||||
849 | safe_close(fd); | ||||
850 | fd = open(root ?: "/", O_CLOEXEC02000000|O_NOFOLLOW0400000|O_PATH010000000); | ||||
851 | if (fd < 0) | ||||
852 | return -errno(*__errno_location ()); | ||||
853 | |||||
854 | if (flags & CHASE_SAFE) { | ||||
855 | if (fstat(fd, &st) < 0) | ||||
856 | return -errno(*__errno_location ()); | ||||
857 | |||||
858 | if (!safe_transition(&previous_stat, &st)) | ||||
859 | return -EPERM1; | ||||
860 | |||||
861 | previous_stat = st; | ||||
862 | } | ||||
863 | |||||
864 | free(done); | ||||
865 | |||||
866 | /* Note that we do not revalidate the root, we take it as is. */ | ||||
867 | if (isempty(root)) | ||||
868 | done = NULL((void*)0); | ||||
869 | else { | ||||
870 | done = strdup(root); | ||||
871 | if (!done) | ||||
872 | return -ENOMEM12; | ||||
873 | } | ||||
874 | |||||
875 | /* Prefix what's left to do with what we just read, and start the loop again, but | ||||
876 | * remain in the current directory. */ | ||||
877 | joined = strjoin(destination, todo)strjoin_real((destination), todo, ((void*)0)); | ||||
878 | } else | ||||
879 | joined = strjoin("/", destination, todo)strjoin_real(("/"), destination, todo, ((void*)0)); | ||||
880 | if (!joined) | ||||
881 | return -ENOMEM12; | ||||
882 | |||||
883 | free(buffer); | ||||
884 | todo = buffer = joined; | ||||
885 | |||||
886 | if (flags & CHASE_STEP) | ||||
887 | goto chased_one; | ||||
888 | |||||
889 | continue; | ||||
890 | } | ||||
891 | |||||
892 | /* If this is not a symlink, then let's just add the name we read to what we already verified. */ | ||||
893 | if (!done) | ||||
894 | done = TAKE_PTR(first)({ typeof(first) _ptr_ = (first); (first) = ((void*)0); _ptr_ ; }); | ||||
895 | else { | ||||
896 | /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */ | ||||
897 | if (streq(done, "/")(strcmp((done),("/")) == 0)) | ||||
898 | *done = '\0'; | ||||
899 | |||||
900 | if (!strextend(&done, first, NULL)strextend_with_separator(&done, ((void*)0), first, ((void *)0))) | ||||
901 | return -ENOMEM12; | ||||
902 | } | ||||
903 | |||||
904 | /* And iterate again, but go one directory further down. */ | ||||
905 | safe_close(fd); | ||||
906 | fd = TAKE_FD(child)({ int _fd_ = (child); (child) = -1; _fd_; }); | ||||
907 | } | ||||
908 | |||||
909 | if (!done) { | ||||
910 | /* Special case, turn the empty string into "/", to indicate the root directory. */ | ||||
911 | done = strdup("/"); | ||||
912 | if (!done) | ||||
913 | return -ENOMEM12; | ||||
914 | } | ||||
915 | |||||
916 | if (ret) | ||||
917 | *ret = TAKE_PTR(done)({ typeof(done) _ptr_ = (done); (done) = ((void*)0); _ptr_; } ); | ||||
918 | |||||
919 | if (flags & CHASE_OPEN) { | ||||
920 | /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a proper fd by | ||||
921 | * opening /proc/self/fd/xyz. */ | ||||
922 | |||||
923 | assert(fd >= 0)do { if ((__builtin_expect(!!(!(fd >= 0)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("fd >= 0"), "../src/basic/fs-util.c", 923, __PRETTY_FUNCTION__); } while (0); | ||||
924 | return TAKE_FD(fd)({ int _fd_ = (fd); (fd) = -1; _fd_; }); | ||||
925 | } | ||||
926 | |||||
927 | if (flags & CHASE_STEP) | ||||
928 | return 1; | ||||
929 | |||||
930 | return exists; | ||||
931 | |||||
932 | chased_one: | ||||
933 | if (ret) { | ||||
934 | char *c; | ||||
935 | |||||
936 | c = strjoin(strempty(done), todo)strjoin_real((strempty(done)), todo, ((void*)0)); | ||||
937 | if (!c) | ||||
938 | return -ENOMEM12; | ||||
939 | |||||
940 | *ret = c; | ||||
941 | } | ||||
942 | |||||
943 | return 0; | ||||
944 | } | ||||
945 | |||||
946 | int chase_symlinks_and_open( | ||||
947 | const char *path, | ||||
948 | const char *root, | ||||
949 | unsigned chase_flags, | ||||
950 | int open_flags, | ||||
951 | char **ret_path) { | ||||
952 | |||||
953 | _cleanup_close___attribute__((cleanup(closep))) int path_fd = -1; | ||||
954 | _cleanup_free___attribute__((cleanup(freep))) char *p = NULL((void*)0); | ||||
955 | int r; | ||||
956 | |||||
957 | if (chase_flags & CHASE_NONEXISTENT) | ||||
958 | return -EINVAL22; | ||||
959 | |||||
960 | if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { | ||||
961 | /* Shortcut this call if none of the special features of this call are requested */ | ||||
962 | r = open(path, open_flags); | ||||
963 | if (r < 0) | ||||
964 | return -errno(*__errno_location ()); | ||||
965 | |||||
966 | return r; | ||||
967 | } | ||||
968 | |||||
969 | path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL((void*)0)); | ||||
970 | if (path_fd < 0) | ||||
971 | return path_fd; | ||||
972 | |||||
973 | r = fd_reopen(path_fd, open_flags); | ||||
974 | if (r < 0) | ||||
975 | return r; | ||||
976 | |||||
977 | if (ret_path) | ||||
978 | *ret_path = TAKE_PTR(p)({ typeof(p) _ptr_ = (p); (p) = ((void*)0); _ptr_; }); | ||||
979 | |||||
980 | return r; | ||||
981 | } | ||||
982 | |||||
983 | int chase_symlinks_and_opendir( | ||||
984 | const char *path, | ||||
985 | const char *root, | ||||
986 | unsigned chase_flags, | ||||
987 | char **ret_path, | ||||
988 | DIR **ret_dir) { | ||||
989 | |||||
990 | char procfs_path[STRLEN("/proc/self/fd/")(sizeof("""/proc/self/fd/""") - 1) + DECIMAL_STR_MAX(int)(2+(sizeof(int) <= 1 ? 3 : sizeof(int) <= 2 ? 5 : sizeof (int) <= 4 ? 10 : sizeof(int) <= 8 ? 20 : sizeof(int[-2 *(sizeof(int) > 8)])))]; | ||||
991 | _cleanup_close___attribute__((cleanup(closep))) int path_fd = -1; | ||||
992 | _cleanup_free___attribute__((cleanup(freep))) char *p = NULL((void*)0); | ||||
993 | DIR *d; | ||||
994 | |||||
995 | if (!ret_dir) | ||||
996 | return -EINVAL22; | ||||
997 | if (chase_flags & CHASE_NONEXISTENT) | ||||
998 | return -EINVAL22; | ||||
999 | |||||
1000 | if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { | ||||
1001 | /* Shortcut this call if none of the special features of this call are requested */ | ||||
1002 | d = opendir(path); | ||||
1003 | if (!d) | ||||
1004 | return -errno(*__errno_location ()); | ||||
1005 | |||||
1006 | *ret_dir = d; | ||||
1007 | return 0; | ||||
1008 | } | ||||
1009 | |||||
1010 | path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL((void*)0)); | ||||
1011 | if (path_fd < 0) | ||||
1012 | return path_fd; | ||||
1013 | |||||
1014 | xsprintf(procfs_path, "/proc/self/fd/%i", path_fd)do { if ((__builtin_expect(!!(!(((size_t) snprintf(procfs_path , __extension__ (__builtin_choose_expr( !__builtin_types_compatible_p (typeof(procfs_path), typeof(&*(procfs_path))), sizeof(procfs_path )/sizeof((procfs_path)[0]), ((void)0))), "/proc/self/fd/%i", path_fd ) < (__extension__ (__builtin_choose_expr( !__builtin_types_compatible_p (typeof(procfs_path), typeof(&*(procfs_path))), sizeof(procfs_path )/sizeof((procfs_path)[0]), ((void)0))))))),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("xsprintf: " "procfs_path" "[] must be big enough" ), "../src/basic/fs-util.c", 1014, __PRETTY_FUNCTION__); } while (0); | ||||
1015 | d = opendir(procfs_path); | ||||
1016 | if (!d) | ||||
1017 | return -errno(*__errno_location ()); | ||||
1018 | |||||
1019 | if (ret_path) | ||||
1020 | *ret_path = TAKE_PTR(p)({ typeof(p) _ptr_ = (p); (p) = ((void*)0); _ptr_; }); | ||||
1021 | |||||
1022 | *ret_dir = d; | ||||
1023 | return 0; | ||||
1024 | } | ||||
1025 | |||||
1026 | int chase_symlinks_and_stat( | ||||
1027 | const char *path, | ||||
1028 | const char *root, | ||||
1029 | unsigned chase_flags, | ||||
1030 | char **ret_path, | ||||
1031 | struct stat *ret_stat) { | ||||
1032 | |||||
1033 | _cleanup_close___attribute__((cleanup(closep))) int path_fd = -1; | ||||
1034 | _cleanup_free___attribute__((cleanup(freep))) char *p = NULL((void*)0); | ||||
1035 | |||||
1036 | assert(path)do { if ((__builtin_expect(!!(!(path)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("path"), "../src/basic/fs-util.c", 1036, __PRETTY_FUNCTION__); } while (0); | ||||
1037 | assert(ret_stat)do { if ((__builtin_expect(!!(!(ret_stat)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("ret_stat"), "../src/basic/fs-util.c", 1037 , __PRETTY_FUNCTION__); } while (0); | ||||
1038 | |||||
1039 | if (chase_flags & CHASE_NONEXISTENT) | ||||
1040 | return -EINVAL22; | ||||
1041 | |||||
1042 | if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { | ||||
1043 | /* Shortcut this call if none of the special features of this call are requested */ | ||||
1044 | if (stat(path, ret_stat) < 0) | ||||
1045 | return -errno(*__errno_location ()); | ||||
1046 | |||||
1047 | return 1; | ||||
1048 | } | ||||
1049 | |||||
1050 | path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL((void*)0)); | ||||
1051 | if (path_fd < 0) | ||||
1052 | return path_fd; | ||||
1053 | |||||
1054 | if (fstat(path_fd, ret_stat) < 0) | ||||
1055 | return -errno(*__errno_location ()); | ||||
1056 | |||||
1057 | if (ret_path) | ||||
1058 | *ret_path = TAKE_PTR(p)({ typeof(p) _ptr_ = (p); (p) = ((void*)0); _ptr_; }); | ||||
1059 | |||||
1060 | if (chase_flags & CHASE_OPEN) | ||||
1061 | return TAKE_FD(path_fd)({ int _fd_ = (path_fd); (path_fd) = -1; _fd_; }); | ||||
1062 | |||||
1063 | return 1; | ||||
1064 | } | ||||
1065 | |||||
1066 | int access_fd(int fd, int mode) { | ||||
1067 | char p[STRLEN("/proc/self/fd/")(sizeof("""/proc/self/fd/""") - 1) + DECIMAL_STR_MAX(fd)(2+(sizeof(fd) <= 1 ? 3 : sizeof(fd) <= 2 ? 5 : sizeof( fd) <= 4 ? 10 : sizeof(fd) <= 8 ? 20 : sizeof(int[-2*(sizeof (fd) > 8)]))) + 1]; | ||||
1068 | int r; | ||||
1069 | |||||
1070 | /* Like access() but operates on an already open fd */ | ||||
1071 | |||||
1072 | xsprintf(p, "/proc/self/fd/%i", fd)do { if ((__builtin_expect(!!(!(((size_t) snprintf(p, __extension__ (__builtin_choose_expr( !__builtin_types_compatible_p(typeof (p), typeof(&*(p))), sizeof(p)/sizeof((p)[0]), ((void)0)) ), "/proc/self/fd/%i", fd) < (__extension__ (__builtin_choose_expr ( !__builtin_types_compatible_p(typeof(p), typeof(&*(p))) , sizeof(p)/sizeof((p)[0]), ((void)0))))))),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("xsprintf: " "p" "[] must be big enough" ), "../src/basic/fs-util.c", 1072, __PRETTY_FUNCTION__); } while (0); | ||||
1073 | r = access(p, mode); | ||||
1074 | if (r < 0) | ||||
1075 | return -errno(*__errno_location ()); | ||||
1076 | |||||
1077 | return r; | ||||
1078 | } | ||||
1079 | |||||
1080 | void unlink_tempfilep(char (*p)[]) { | ||||
1081 | /* If the file is created with mkstemp(), it will (almost always) | ||||
1082 | * change the suffix. Treat this as a sign that the file was | ||||
1083 | * successfully created. We ignore both the rare case where the | ||||
1084 | * original suffix is used and unlink failures. */ | ||||
1085 | if (!endswith(*p, ".XXXXXX")) | ||||
1086 | (void) unlink_noerrno(*p); | ||||
1087 | } | ||||
1088 | |||||
1089 | int unlinkat_deallocate(int fd, const char *name, int flags) { | ||||
1090 | _cleanup_close___attribute__((cleanup(closep))) int truncate_fd = -1; | ||||
1091 | struct stat st; | ||||
1092 | off_t l, bs; | ||||
1093 | |||||
1094 | /* Operates like unlinkat() but also deallocates the file contents if it is a regular file and there's no other | ||||
1095 | * link to it. This is useful to ensure that other processes that might have the file open for reading won't be | ||||
1096 | * able to keep the data pinned on disk forever. This call is particular useful whenever we execute clean-up | ||||
1097 | * jobs ("vacuuming"), where we want to make sure the data is really gone and the disk space released and | ||||
1098 | * returned to the free pool. | ||||
1099 | * | ||||
1100 | * Deallocation is preferably done by FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE (👊) if supported, which means | ||||
1101 | * the file won't change size. That's a good thing since we shouldn't needlessly trigger SIGBUS in other | ||||
1102 | * programs that have mmap()ed the file. (The assumption here is that changing file contents to all zeroes | ||||
1103 | * underneath those programs is the better choice than simply triggering SIGBUS in them which truncation does.) | ||||
1104 | * However if hole punching is not implemented in the kernel or file system we'll fall back to normal file | ||||
1105 | * truncation (🔪), as our goal of deallocating the data space trumps our goal of being nice to readers (💐). | ||||
1106 | * | ||||
1107 | * Note that we attempt deallocation, but failure to succeed with that is not considered fatal, as long as the | ||||
1108 | * primary job – to delete the file – is accomplished. */ | ||||
1109 | |||||
1110 | if ((flags & AT_REMOVEDIR0x200) == 0) { | ||||
1111 | truncate_fd = openat(fd, name, O_WRONLY01|O_CLOEXEC02000000|O_NOCTTY0400|O_NOFOLLOW0400000|O_NONBLOCK04000); | ||||
1112 | if (truncate_fd < 0) { | ||||
1113 | |||||
1114 | /* If this failed because the file doesn't exist propagate the error right-away. Also, | ||||
1115 | * AT_REMOVEDIR wasn't set, and we tried to open the file for writing, which means EISDIR is | ||||
1116 | * returned when this is a directory but we are not supposed to delete those, hence propagate | ||||
1117 | * the error right-away too. */ | ||||
1118 | if (IN_SET(errno, ENOENT, EISDIR)({ _Bool _found = 0; static __attribute__ ((unused)) char _static_assert__macros_need_to_be_extended [20 - sizeof((int[]){2, 21})/sizeof(int)]; switch((*__errno_location ())) { case 2: case 21: _found = 1; break; default: break; } _found; })) | ||||
1119 | return -errno(*__errno_location ()); | ||||
1120 | |||||
1121 | if (errno(*__errno_location ()) != ELOOP40) /* don't complain if this is a symlink */ | ||||
1122 | log_debug_errno(errno, "Failed to open file '%s' for deallocation, ignoring: %m", name)({ int _level = ((7)), _e = (((*__errno_location ()))), _realm = (LOG_REALM_SYSTEMD); (log_get_max_level_realm(_realm) >= ((_level) & 0x07)) ? log_internal_realm(((_realm) << 10 | (_level)), _e, "../src/basic/fs-util.c", 1122, __func__ , "Failed to open file '%s' for deallocation, ignoring: %m", name ) : -abs(_e); }); | ||||
1123 | } | ||||
1124 | } | ||||
1125 | |||||
1126 | if (unlinkat(fd, name, flags) < 0) | ||||
1127 | return -errno(*__errno_location ()); | ||||
1128 | |||||
1129 | if (truncate_fd < 0) /* Don't have a file handle, can't do more ☹️ */ | ||||
1130 | return 0; | ||||
1131 | |||||
1132 | if (fstat(truncate_fd, &st) < 0) { | ||||
1133 | log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring.", name)({ int _level = ((7)), _e = (((*__errno_location ()))), _realm = (LOG_REALM_SYSTEMD); (log_get_max_level_realm(_realm) >= ((_level) & 0x07)) ? log_internal_realm(((_realm) << 10 | (_level)), _e, "../src/basic/fs-util.c", 1133, __func__ , "Failed to stat file '%s' for deallocation, ignoring.", name ) : -abs(_e); }); | ||||
1134 | return 0; | ||||
1135 | } | ||||
1136 | |||||
1137 | if (!S_ISREG(st.st_mode)((((st.st_mode)) & 0170000) == (0100000)) || st.st_blocks == 0 || st.st_nlink > 0) | ||||
1138 | return 0; | ||||
1139 | |||||
1140 | /* If this is a regular file, it actually took up space on disk and there are no other links it's time to | ||||
1141 | * punch-hole/truncate this to release the disk space. */ | ||||
1142 | |||||
1143 | bs = MAX(st.st_blksize, 512)__extension__ ({ const typeof((st.st_blksize)) __unique_prefix_A17 = ((st.st_blksize)); const typeof((512)) __unique_prefix_B18 = ((512)); __unique_prefix_A17 > __unique_prefix_B18 ? __unique_prefix_A17 : __unique_prefix_B18; }); | ||||
1144 | l = DIV_ROUND_UP(st.st_size, bs)({ const typeof((st.st_size)) __unique_prefix_X19 = ((st.st_size )); const typeof((bs)) __unique_prefix_Y20 = ((bs)); (__unique_prefix_X19 / __unique_prefix_Y20 + !!(__unique_prefix_X19 % __unique_prefix_Y20 )); }) * bs; /* Round up to next block size */ | ||||
1145 | |||||
1146 | if (fallocate(truncate_fd, FALLOC_FL_PUNCH_HOLE0x02|FALLOC_FL_KEEP_SIZE0x01, 0, l) >= 0) | ||||
1147 | return 0; /* Successfully punched a hole! 😊 */ | ||||
1148 | |||||
1149 | /* Fall back to truncation */ | ||||
1150 | if (ftruncate(truncate_fd, 0) < 0) { | ||||
1151 | log_debug_errno(errno, "Failed to truncate file to 0, ignoring: %m")({ int _level = ((7)), _e = (((*__errno_location ()))), _realm = (LOG_REALM_SYSTEMD); (log_get_max_level_realm(_realm) >= ((_level) & 0x07)) ? log_internal_realm(((_realm) << 10 | (_level)), _e, "../src/basic/fs-util.c", 1151, __func__ , "Failed to truncate file to 0, ignoring: %m") : -abs(_e); } ); | ||||
1152 | return 0; | ||||
1153 | } | ||||
1154 | |||||
1155 | return 0; | ||||
1156 | } | ||||
1157 | |||||
1158 | int fsync_directory_of_file(int fd) { | ||||
1159 | _cleanup_free___attribute__((cleanup(freep))) char *path = NULL((void*)0), *dn = NULL((void*)0); | ||||
1160 | _cleanup_close___attribute__((cleanup(closep))) int dfd = -1; | ||||
1161 | int r; | ||||
1162 | |||||
1163 | r = fd_verify_regular(fd); | ||||
1164 | if (r < 0) | ||||
1165 | return r; | ||||
1166 | |||||
1167 | r = fd_get_path(fd, &path); | ||||
1168 | if (r < 0) { | ||||
1169 | log_debug_errno(r, "Failed to query /proc/self/fd/%d%s: %m",({ int _level = ((7)), _e = ((r)), _realm = (LOG_REALM_SYSTEMD ); (log_get_max_level_realm(_realm) >= ((_level) & 0x07 )) ? log_internal_realm(((_realm) << 10 | (_level)), _e , "../src/basic/fs-util.c", 1171, __func__, "Failed to query /proc/self/fd/%d%s: %m" , fd, r == -95 ? ", ignoring" : "") : -abs(_e); }) | ||||
1170 | fd,({ int _level = ((7)), _e = ((r)), _realm = (LOG_REALM_SYSTEMD ); (log_get_max_level_realm(_realm) >= ((_level) & 0x07 )) ? log_internal_realm(((_realm) << 10 | (_level)), _e , "../src/basic/fs-util.c", 1171, __func__, "Failed to query /proc/self/fd/%d%s: %m" , fd, r == -95 ? ", ignoring" : "") : -abs(_e); }) | ||||
1171 | r == -EOPNOTSUPP ? ", ignoring" : "")({ int _level = ((7)), _e = ((r)), _realm = (LOG_REALM_SYSTEMD ); (log_get_max_level_realm(_realm) >= ((_level) & 0x07 )) ? log_internal_realm(((_realm) << 10 | (_level)), _e , "../src/basic/fs-util.c", 1171, __func__, "Failed to query /proc/self/fd/%d%s: %m" , fd, r == -95 ? ", ignoring" : "") : -abs(_e); }); | ||||
1172 | |||||
1173 | if (r == -EOPNOTSUPP95) | ||||
1174 | /* If /proc is not available, we're most likely running in some | ||||
1175 | * chroot environment, and syncing the directory is not very | ||||
1176 | * important in that case. Let's just silently do nothing. */ | ||||
1177 | return 0; | ||||
1178 | |||||
1179 | return r; | ||||
1180 | } | ||||
1181 | |||||
1182 | if (!path_is_absolute(path)) | ||||
1183 | return -EINVAL22; | ||||
1184 | |||||
1185 | dn = dirname_malloc(path); | ||||
1186 | if (!dn) | ||||
1187 | return -ENOMEM12; | ||||
1188 | |||||
1189 | dfd = open(dn, O_RDONLY00|O_CLOEXEC02000000|O_DIRECTORY0200000); | ||||
1190 | if (dfd < 0) | ||||
1191 | return -errno(*__errno_location ()); | ||||
1192 | |||||
1193 | if (fsync(dfd) < 0) | ||||
1194 | return -errno(*__errno_location ()); | ||||
1195 | |||||
1196 | return 0; | ||||
1197 | } |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ | ||||||||
2 | #pragma once | ||||||||
3 | |||||||||
4 | #include <alloca.h> | ||||||||
5 | #include <stdbool.h> | ||||||||
6 | #include <stddef.h> | ||||||||
7 | #include <string.h> | ||||||||
8 | |||||||||
9 | #include "macro.h" | ||||||||
10 | |||||||||
11 | /* What is interpreted as whitespace? */ | ||||||||
12 | #define WHITESPACE" \t\n\r" " \t\n\r" | ||||||||
13 | #define NEWLINE"\n\r" "\n\r" | ||||||||
14 | #define QUOTES"\"\'" "\"\'" | ||||||||
15 | #define COMMENTS"#;" "#;" | ||||||||
16 | #define GLOB_CHARS"*?[" "*?[" | ||||||||
17 | #define DIGITS"0123456789" "0123456789" | ||||||||
18 | #define LOWERCASE_LETTERS"abcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyz" | ||||||||
19 | #define UPPERCASE_LETTERS"ABCDEFGHIJKLMNOPQRSTUVWXYZ" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||||||||
20 | #define LETTERS"abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" LOWERCASE_LETTERS"abcdefghijklmnopqrstuvwxyz" UPPERCASE_LETTERS"ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||||||||
21 | #define ALPHANUMERICAL"abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" LETTERS"abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" DIGITS"0123456789" | ||||||||
22 | #define HEXDIGITS"0123456789" "abcdefABCDEF" DIGITS"0123456789" "abcdefABCDEF" | ||||||||
23 | |||||||||
24 | #define streq(a,b)(strcmp((a),(b)) == 0) (strcmp((a),(b)) == 0) | ||||||||
25 | #define strneq(a, b, n)(strncmp((a), (b), (n)) == 0) (strncmp((a), (b), (n)) == 0) | ||||||||
26 | #define strcaseeq(a,b)(strcasecmp((a),(b)) == 0) (strcasecmp((a),(b)) == 0) | ||||||||
27 | #define strncaseeq(a, b, n)(strncasecmp((a), (b), (n)) == 0) (strncasecmp((a), (b), (n)) == 0) | ||||||||
28 | |||||||||
29 | int strcmp_ptr(const char *a, const char *b) _pure___attribute__ ((pure)); | ||||||||
30 | |||||||||
31 | static inline bool_Bool streq_ptr(const char *a, const char *b) { | ||||||||
32 | return strcmp_ptr(a, b) == 0; | ||||||||
33 | } | ||||||||
34 | |||||||||
35 | static inline const char* strempty(const char *s) { | ||||||||
36 | return s ?: ""; | ||||||||
37 | } | ||||||||
38 | |||||||||
39 | static inline const char* strnull(const char *s) { | ||||||||
40 | return s ?: "(null)"; | ||||||||
41 | } | ||||||||
42 | |||||||||
43 | static inline const char *strna(const char *s) { | ||||||||
44 | return s ?: "n/a"; | ||||||||
45 | } | ||||||||
46 | |||||||||
47 | static inline bool_Bool isempty(const char *p) { | ||||||||
48 | return !p
| ||||||||
49 | } | ||||||||
50 | |||||||||
51 | static inline const char *empty_to_null(const char *p) { | ||||||||
52 | return isempty(p) ? NULL((void*)0) : p; | ||||||||
53 | } | ||||||||
54 | |||||||||
55 | static inline const char *empty_to_dash(const char *str) { | ||||||||
56 | return isempty(str) ? "-" : str; | ||||||||
57 | } | ||||||||
58 | |||||||||
59 | static inline char *startswith(const char *s, const char *prefix) { | ||||||||
60 | size_t l; | ||||||||
61 | |||||||||
62 | l = strlen(prefix); | ||||||||
63 | if (strncmp(s, prefix, l) == 0) | ||||||||
64 | return (char*) s + l; | ||||||||
65 | |||||||||
66 | return NULL((void*)0); | ||||||||
67 | } | ||||||||
68 | |||||||||
69 | static inline char *startswith_no_case(const char *s, const char *prefix) { | ||||||||
70 | size_t l; | ||||||||
71 | |||||||||
72 | l = strlen(prefix); | ||||||||
73 | if (strncasecmp(s, prefix, l) == 0) | ||||||||
74 | return (char*) s + l; | ||||||||
75 | |||||||||
76 | return NULL((void*)0); | ||||||||
77 | } | ||||||||
78 | |||||||||
79 | char *endswith(const char *s, const char *postfix) _pure___attribute__ ((pure)); | ||||||||
80 | char *endswith_no_case(const char *s, const char *postfix) _pure___attribute__ ((pure)); | ||||||||
81 | |||||||||
82 | char *first_word(const char *s, const char *word) _pure___attribute__ ((pure)); | ||||||||
83 | |||||||||
84 | const char* split(const char **state, size_t *l, const char *separator, bool_Bool quoted); | ||||||||
85 | |||||||||
86 | #define FOREACH_WORD(word, length, s, state)for ((state) = (s), (word) = split(&(state), &(length ), (" \t\n\r"), (0)); (word); (word) = split(&(state), & (length), (" \t\n\r"), (0))) \ | ||||||||
87 | _FOREACH_WORD(word, length, s, WHITESPACE, false, state)for ((state) = (s), (word) = split(&(state), &(length ), (" \t\n\r"), (0)); (word); (word) = split(&(state), & (length), (" \t\n\r"), (0))) | ||||||||
88 | |||||||||
89 | #define FOREACH_WORD_SEPARATOR(word, length, s, separator, state)for ((state) = (s), (word) = split(&(state), &(length ), (separator), (0)); (word); (word) = split(&(state), & (length), (separator), (0))) \ | ||||||||
90 | _FOREACH_WORD(word, length, s, separator, false, state)for ((state) = (s), (word) = split(&(state), &(length ), (separator), (0)); (word); (word) = split(&(state), & (length), (separator), (0))) | ||||||||
91 | |||||||||
92 | #define _FOREACH_WORD(word, length, s, separator, quoted, state)for ((state) = (s), (word) = split(&(state), &(length ), (separator), (quoted)); (word); (word) = split(&(state ), &(length), (separator), (quoted))) \ | ||||||||
93 | for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted))) | ||||||||
94 | |||||||||
95 | char *strappend(const char *s, const char *suffix); | ||||||||
96 | char *strnappend(const char *s, const char *suffix, size_t length); | ||||||||
97 | |||||||||
98 | char *strjoin_real(const char *x, ...) _sentinel___attribute__ ((sentinel)); | ||||||||
99 | #define strjoin(a, ...)strjoin_real((a), ..., ((void*)0)) strjoin_real((a), __VA_ARGS__, NULL((void*)0)) | ||||||||
100 | |||||||||
101 | #define strjoina(a, ...)({ const char *_appendees_[] = { a, ... }; char *_d_, *_p_; size_t _len_ = 0; size_t _i_; for (_i_ = 0; _i_ < __extension__ ( __builtin_choose_expr( !__builtin_types_compatible_p(typeof(_appendees_ ), typeof(&*(_appendees_))), sizeof(_appendees_)/sizeof(( _appendees_)[0]), ((void)0))) && _appendees_[_i_]; _i_ ++) _len_ += strlen(_appendees_[_i_]); _p_ = _d_ = __builtin_alloca (_len_ + 1); for (_i_ = 0; _i_ < __extension__ (__builtin_choose_expr ( !__builtin_types_compatible_p(typeof(_appendees_), typeof(& *(_appendees_))), sizeof(_appendees_)/sizeof((_appendees_)[0] ), ((void)0))) && _appendees_[_i_]; _i_++) _p_ = stpcpy (_p_, _appendees_[_i_]); *_p_ = 0; _d_; }) \ | ||||||||
102 | ({ \ | ||||||||
103 | const char *_appendees_[] = { a, __VA_ARGS__ }; \ | ||||||||
104 | char *_d_, *_p_; \ | ||||||||
105 | size_t _len_ = 0; \ | ||||||||
106 | size_t _i_; \ | ||||||||
107 | for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_)__extension__ (__builtin_choose_expr( !__builtin_types_compatible_p (typeof(_appendees_), typeof(&*(_appendees_))), sizeof(_appendees_ )/sizeof((_appendees_)[0]), ((void)0))) && _appendees_[_i_]; _i_++) \ | ||||||||
108 | _len_ += strlen(_appendees_[_i_]); \ | ||||||||
109 | _p_ = _d_ = alloca(_len_ + 1)__builtin_alloca (_len_ + 1); \ | ||||||||
110 | for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_)__extension__ (__builtin_choose_expr( !__builtin_types_compatible_p (typeof(_appendees_), typeof(&*(_appendees_))), sizeof(_appendees_ )/sizeof((_appendees_)[0]), ((void)0))) && _appendees_[_i_]; _i_++) \ | ||||||||
111 | _p_ = stpcpy(_p_, _appendees_[_i_]); \ | ||||||||
112 | *_p_ = 0; \ | ||||||||
113 | _d_; \ | ||||||||
114 | }) | ||||||||
115 | |||||||||
116 | char *strstrip(char *s); | ||||||||
117 | char *delete_chars(char *s, const char *bad); | ||||||||
118 | char *delete_trailing_chars(char *s, const char *bad); | ||||||||
119 | char *truncate_nl(char *s); | ||||||||
120 | |||||||||
121 | static inline char *skip_leading_chars(const char *s, const char *bad) { | ||||||||
122 | |||||||||
123 | if (!s) | ||||||||
124 | return NULL((void*)0); | ||||||||
125 | |||||||||
126 | if (!bad) | ||||||||
127 | bad = WHITESPACE" \t\n\r"; | ||||||||
128 | |||||||||
129 | return (char*) s + strspn(s, bad); | ||||||||
130 | } | ||||||||
131 | |||||||||
132 | char ascii_tolower(char x); | ||||||||
133 | char *ascii_strlower(char *s); | ||||||||
134 | char *ascii_strlower_n(char *s, size_t n); | ||||||||
135 | |||||||||
136 | char ascii_toupper(char x); | ||||||||
137 | char *ascii_strupper(char *s); | ||||||||
138 | |||||||||
139 | int ascii_strcasecmp_n(const char *a, const char *b, size_t n); | ||||||||
140 | int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m); | ||||||||
141 | |||||||||
142 | bool_Bool chars_intersect(const char *a, const char *b) _pure___attribute__ ((pure)); | ||||||||
143 | |||||||||
144 | static inline bool_Bool _pure___attribute__ ((pure)) in_charset(const char *s, const char* charset) { | ||||||||
145 | assert(s)do { if ((__builtin_expect(!!(!(s)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("s"), "../src/basic/string-util.h", 145, __PRETTY_FUNCTION__); } while (0); | ||||||||
146 | assert(charset)do { if ((__builtin_expect(!!(!(charset)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("charset"), "../src/basic/string-util.h" , 146, __PRETTY_FUNCTION__); } while (0); | ||||||||
147 | return s[strspn(s, charset)] == '\0'; | ||||||||
148 | } | ||||||||
149 | |||||||||
150 | bool_Bool string_has_cc(const char *p, const char *ok) _pure___attribute__ ((pure)); | ||||||||
151 | |||||||||
152 | char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent); | ||||||||
153 | static inline char *ellipsize(const char *s, size_t length, unsigned percent) { | ||||||||
154 | return ellipsize_mem(s, strlen(s), length, percent); | ||||||||
155 | } | ||||||||
156 | |||||||||
157 | char *cellescape(char *buf, size_t len, const char *s); | ||||||||
158 | |||||||||
159 | /* This limit is arbitrary, enough to give some idea what the string contains */ | ||||||||
160 | #define CELLESCAPE_DEFAULT_LENGTH64 64 | ||||||||
161 | |||||||||
162 | bool_Bool nulstr_contains(const char *nulstr, const char *needle); | ||||||||
163 | |||||||||
164 | char* strshorten(char *s, size_t l); | ||||||||
165 | |||||||||
166 | char *strreplace(const char *text, const char *old_string, const char *new_string); | ||||||||
167 | |||||||||
168 | char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]); | ||||||||
169 | |||||||||
170 | char *strextend_with_separator(char **x, const char *separator, ...) _sentinel___attribute__ ((sentinel)); | ||||||||
171 | |||||||||
172 | #define strextend(x, ...)strextend_with_separator(x, ((void*)0), ...) strextend_with_separator(x, NULL((void*)0), __VA_ARGS__) | ||||||||
173 | |||||||||
174 | char *strrep(const char *s, unsigned n); | ||||||||
175 | |||||||||
176 | int split_pair(const char *s, const char *sep, char **l, char **r); | ||||||||
177 | |||||||||
178 | int free_and_strdup(char **p, const char *s); | ||||||||
179 | int free_and_strndup(char **p, const char *s, size_t l); | ||||||||
180 | |||||||||
181 | /* Normal memmem() requires haystack to be nonnull, which is annoying for zero-length buffers */ | ||||||||
182 | static inline void *memmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { | ||||||||
183 | |||||||||
184 | if (needlelen <= 0) | ||||||||
185 | return (void*) haystack; | ||||||||
186 | |||||||||
187 | if (haystacklen < needlelen) | ||||||||
188 | return NULL((void*)0); | ||||||||
189 | |||||||||
190 | assert(haystack)do { if ((__builtin_expect(!!(!(haystack)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("haystack"), "../src/basic/string-util.h" , 190, __PRETTY_FUNCTION__); } while (0); | ||||||||
191 | assert(needle)do { if ((__builtin_expect(!!(!(needle)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("needle"), "../src/basic/string-util.h", 191, __PRETTY_FUNCTION__); } while (0); | ||||||||
192 | |||||||||
193 | return memmem(haystack, haystacklen, needle, needlelen); | ||||||||
194 | } | ||||||||
195 | |||||||||
196 | #if !HAVE_EXPLICIT_BZERO1 | ||||||||
197 | void explicit_bzero(void *p, size_t l); | ||||||||
198 | #endif | ||||||||
199 | |||||||||
200 | char *string_erase(char *x); | ||||||||
201 | |||||||||
202 | char *string_free_erase(char *s); | ||||||||
203 | DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase)static inline void string_free_erasep(char * *p) { if (*p) string_free_erase (*p); }; | ||||||||
204 | #define _cleanup_string_free_erase___attribute__((cleanup(string_free_erasep))) _cleanup_(string_free_erasep)__attribute__((cleanup(string_free_erasep))) | ||||||||
205 | |||||||||
206 | bool_Bool string_is_safe(const char *p) _pure___attribute__ ((pure)); | ||||||||
207 | |||||||||
208 | static inline size_t strlen_ptr(const char *s) { | ||||||||
209 | if (!s) | ||||||||
210 | return 0; | ||||||||
211 | |||||||||
212 | return strlen(s); | ||||||||
213 | } | ||||||||
214 | |||||||||
215 | /* Like startswith(), but operates on arbitrary memory blocks */ | ||||||||
216 | static inline void *memory_startswith(const void *p, size_t sz, const char *token) { | ||||||||
217 | size_t n; | ||||||||
218 | |||||||||
219 | assert(token)do { if ((__builtin_expect(!!(!(token)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("token"), "../src/basic/string-util.h", 219 , __PRETTY_FUNCTION__); } while (0); | ||||||||
220 | |||||||||
221 | n = strlen(token); | ||||||||
222 | if (sz < n) | ||||||||
223 | return NULL((void*)0); | ||||||||
224 | |||||||||
225 | assert(p)do { if ((__builtin_expect(!!(!(p)),0))) log_assert_failed_realm (LOG_REALM_SYSTEMD, ("p"), "../src/basic/string-util.h", 225, __PRETTY_FUNCTION__); } while (0); | ||||||||
226 | |||||||||
227 | if (memcmp(p, token, n) != 0) | ||||||||
228 | return NULL((void*)0); | ||||||||
229 | |||||||||
230 | return (uint8_t*) p + n; | ||||||||
231 | } |