]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/stat-util.c
basic: spit out chase_symlinks() from fs-util.[ch] → chase-symlinks.[ch]
[thirdparty/systemd.git] / src / basic / stat-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <sched.h>
6 #include <sys/statvfs.h>
7 #include <sys/types.h>
8 #include <unistd.h>
9
10 #include "alloc-util.h"
11 #include "chase-symlinks.h"
12 #include "dirent-util.h"
13 #include "errno-util.h"
14 #include "fd-util.h"
15 #include "fileio.h"
16 #include "macro.h"
17 #include "missing_fs.h"
18 #include "missing_magic.h"
19 #include "missing_syscall.h"
20 #include "parse-util.h"
21 #include "stat-util.h"
22 #include "string-util.h"
23
24 int is_symlink(const char *path) {
25 struct stat info;
26
27 assert(path);
28
29 if (lstat(path, &info) < 0)
30 return -errno;
31
32 return !!S_ISLNK(info.st_mode);
33 }
34
35 int is_dir(const char* path, bool follow) {
36 struct stat st;
37 int r;
38
39 assert(path);
40
41 if (follow)
42 r = stat(path, &st);
43 else
44 r = lstat(path, &st);
45 if (r < 0)
46 return -errno;
47
48 return !!S_ISDIR(st.st_mode);
49 }
50
51 int is_dir_fd(int fd) {
52 struct stat st;
53
54 if (fstat(fd, &st) < 0)
55 return -errno;
56
57 return !!S_ISDIR(st.st_mode);
58 }
59
60 int is_device_node(const char *path) {
61 struct stat info;
62
63 assert(path);
64
65 if (lstat(path, &info) < 0)
66 return -errno;
67
68 return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode));
69 }
70
71 int dir_is_empty_at(int dir_fd, const char *path) {
72 _cleanup_close_ int fd = -1;
73 _cleanup_closedir_ DIR *d = NULL;
74 struct dirent *de;
75
76 if (path) {
77 fd = openat(dir_fd, path, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
78 if (fd < 0)
79 return -errno;
80 } else {
81 /* Note that DUPing is not enough, as the internal pointer
82 * would still be shared and moved by FOREACH_DIRENT. */
83 fd = fd_reopen(dir_fd, O_CLOEXEC);
84 if (fd < 0)
85 return fd;
86 }
87
88 d = take_fdopendir(&fd);
89 if (!d)
90 return -errno;
91
92 FOREACH_DIRENT(de, d, return -errno)
93 return 0;
94
95 return 1;
96 }
97
98 bool null_or_empty(struct stat *st) {
99 assert(st);
100
101 if (S_ISREG(st->st_mode) && st->st_size <= 0)
102 return true;
103
104 /* We don't want to hardcode the major/minor of /dev/null, hence we do a simpler "is this a character
105 * device node?" check. */
106
107 if (S_ISCHR(st->st_mode))
108 return true;
109
110 return false;
111 }
112
113 int null_or_empty_path(const char *fn) {
114 struct stat st;
115
116 assert(fn);
117
118 /* If we have the path, let's do an easy text comparison first. */
119 if (path_equal(fn, "/dev/null"))
120 return true;
121
122 if (stat(fn, &st) < 0)
123 return -errno;
124
125 return null_or_empty(&st);
126 }
127
128 int null_or_empty_fd(int fd) {
129 struct stat st;
130
131 assert(fd >= 0);
132
133 if (fstat(fd, &st) < 0)
134 return -errno;
135
136 return null_or_empty(&st);
137 }
138
139 int path_is_read_only_fs(const char *path) {
140 struct statvfs st;
141
142 assert(path);
143
144 if (statvfs(path, &st) < 0)
145 return -errno;
146
147 if (st.f_flag & ST_RDONLY)
148 return true;
149
150 /* On NFS, statvfs() might not reflect whether we can actually
151 * write to the remote share. Let's try again with
152 * access(W_OK) which is more reliable, at least sometimes. */
153 if (access(path, W_OK) < 0 && errno == EROFS)
154 return true;
155
156 return false;
157 }
158
159 int files_same(const char *filea, const char *fileb, int flags) {
160 struct stat a, b;
161
162 assert(filea);
163 assert(fileb);
164
165 if (fstatat(AT_FDCWD, filea, &a, flags) < 0)
166 return -errno;
167
168 if (fstatat(AT_FDCWD, fileb, &b, flags) < 0)
169 return -errno;
170
171 return a.st_dev == b.st_dev &&
172 a.st_ino == b.st_ino;
173 }
174
175 bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) {
176 assert(s);
177 assert_cc(sizeof(statfs_f_type_t) >= sizeof(s->f_type));
178
179 return F_TYPE_EQUAL(s->f_type, magic_value);
180 }
181
182 int fd_is_fs_type(int fd, statfs_f_type_t magic_value) {
183 struct statfs s;
184
185 if (fstatfs(fd, &s) < 0)
186 return -errno;
187
188 return is_fs_type(&s, magic_value);
189 }
190
191 int path_is_fs_type(const char *path, statfs_f_type_t magic_value) {
192 struct statfs s;
193
194 if (statfs(path, &s) < 0)
195 return -errno;
196
197 return is_fs_type(&s, magic_value);
198 }
199
200 bool is_temporary_fs(const struct statfs *s) {
201 return is_fs_type(s, TMPFS_MAGIC) ||
202 is_fs_type(s, RAMFS_MAGIC);
203 }
204
205 bool is_network_fs(const struct statfs *s) {
206 return is_fs_type(s, CIFS_MAGIC_NUMBER) ||
207 is_fs_type(s, CODA_SUPER_MAGIC) ||
208 is_fs_type(s, NCP_SUPER_MAGIC) ||
209 is_fs_type(s, NFS_SUPER_MAGIC) ||
210 is_fs_type(s, SMB_SUPER_MAGIC) ||
211 is_fs_type(s, V9FS_MAGIC) ||
212 is_fs_type(s, AFS_SUPER_MAGIC) ||
213 is_fs_type(s, OCFS2_SUPER_MAGIC);
214 }
215
216 int fd_is_temporary_fs(int fd) {
217 struct statfs s;
218
219 if (fstatfs(fd, &s) < 0)
220 return -errno;
221
222 return is_temporary_fs(&s);
223 }
224
225 int fd_is_network_fs(int fd) {
226 struct statfs s;
227
228 if (fstatfs(fd, &s) < 0)
229 return -errno;
230
231 return is_network_fs(&s);
232 }
233
234 int path_is_temporary_fs(const char *path) {
235 struct statfs s;
236
237 if (statfs(path, &s) < 0)
238 return -errno;
239
240 return is_temporary_fs(&s);
241 }
242
243 int stat_verify_regular(const struct stat *st) {
244 assert(st);
245
246 /* Checks whether the specified stat() structure refers to a regular file. If not returns an appropriate error
247 * code. */
248
249 if (S_ISDIR(st->st_mode))
250 return -EISDIR;
251
252 if (S_ISLNK(st->st_mode))
253 return -ELOOP;
254
255 if (!S_ISREG(st->st_mode))
256 return -EBADFD;
257
258 return 0;
259 }
260
261 int fd_verify_regular(int fd) {
262 struct stat st;
263
264 assert(fd >= 0);
265
266 if (fstat(fd, &st) < 0)
267 return -errno;
268
269 return stat_verify_regular(&st);
270 }
271
272 int stat_verify_directory(const struct stat *st) {
273 assert(st);
274
275 if (S_ISLNK(st->st_mode))
276 return -ELOOP;
277
278 if (!S_ISDIR(st->st_mode))
279 return -ENOTDIR;
280
281 return 0;
282 }
283
284 int fd_verify_directory(int fd) {
285 struct stat st;
286
287 assert(fd >= 0);
288
289 if (fstat(fd, &st) < 0)
290 return -errno;
291
292 return stat_verify_directory(&st);
293 }
294
295 int device_path_make_major_minor(mode_t mode, dev_t devno, char **ret) {
296 const char *t;
297
298 /* Generates the /dev/{char|block}/MAJOR:MINOR path for a dev_t */
299
300 if (S_ISCHR(mode))
301 t = "char";
302 else if (S_ISBLK(mode))
303 t = "block";
304 else
305 return -ENODEV;
306
307 if (asprintf(ret, "/dev/%s/%u:%u", t, major(devno), minor(devno)) < 0)
308 return -ENOMEM;
309
310 return 0;
311 }
312
313 int device_path_make_canonical(mode_t mode, dev_t devno, char **ret) {
314 _cleanup_free_ char *p = NULL;
315 int r;
316
317 /* Finds the canonical path for a device, i.e. resolves the /dev/{char|block}/MAJOR:MINOR path to the end. */
318
319 assert(ret);
320
321 if (major(devno) == 0 && minor(devno) == 0) {
322 char *s;
323
324 /* A special hack to make sure our 'inaccessible' device nodes work. They won't have symlinks in
325 * /dev/block/ and /dev/char/, hence we handle them specially here. */
326
327 if (S_ISCHR(mode))
328 s = strdup("/run/systemd/inaccessible/chr");
329 else if (S_ISBLK(mode))
330 s = strdup("/run/systemd/inaccessible/blk");
331 else
332 return -ENODEV;
333
334 if (!s)
335 return -ENOMEM;
336
337 *ret = s;
338 return 0;
339 }
340
341 r = device_path_make_major_minor(mode, devno, &p);
342 if (r < 0)
343 return r;
344
345 return chase_symlinks(p, NULL, 0, ret, NULL);
346 }
347
348 int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devno) {
349 mode_t mode;
350 dev_t devno;
351 int r;
352
353 /* Tries to extract the major/minor directly from the device path if we can. Handles /dev/block/ and /dev/char/
354 * paths, as well out synthetic inaccessible device nodes. Never goes to disk. Returns -ENODEV if the device
355 * path cannot be parsed like this. */
356
357 if (path_equal(path, "/run/systemd/inaccessible/chr")) {
358 mode = S_IFCHR;
359 devno = makedev(0, 0);
360 } else if (path_equal(path, "/run/systemd/inaccessible/blk")) {
361 mode = S_IFBLK;
362 devno = makedev(0, 0);
363 } else {
364 const char *w;
365
366 w = path_startswith(path, "/dev/block/");
367 if (w)
368 mode = S_IFBLK;
369 else {
370 w = path_startswith(path, "/dev/char/");
371 if (!w)
372 return -ENODEV;
373
374 mode = S_IFCHR;
375 }
376
377 r = parse_dev(w, &devno);
378 if (r < 0)
379 return r;
380 }
381
382 if (ret_mode)
383 *ret_mode = mode;
384 if (ret_devno)
385 *ret_devno = devno;
386
387 return 0;
388 }
389
390 int proc_mounted(void) {
391 int r;
392
393 /* A quick check of procfs is properly mounted */
394
395 r = path_is_fs_type("/proc/", PROC_SUPER_MAGIC);
396 if (r == -ENOENT) /* not mounted at all */
397 return false;
398
399 return r;
400 }
401
402 bool stat_inode_unmodified(const struct stat *a, const struct stat *b) {
403
404 /* Returns if the specified stat structures reference the same, unmodified inode. This check tries to
405 * be reasonably careful when detecting changes: we check both inode and mtime, to cater for file
406 * systems where mtimes are fixed to 0 (think: ostree/nixos type installations). We also check file
407 * size, backing device, inode type and if this refers to a device not the major/minor.
408 *
409 * Note that we don't care if file attributes such as ownership or access mode change, this here is
410 * about contents of the file. The purpose here is to detect file contents changes, and nothing
411 * else. */
412
413 return a && b &&
414 (a->st_mode & S_IFMT) != 0 && /* We use the check for .st_mode if the structure was ever initialized */
415 ((a->st_mode ^ b->st_mode) & S_IFMT) == 0 && /* same inode type */
416 a->st_mtim.tv_sec == b->st_mtim.tv_sec &&
417 a->st_mtim.tv_nsec == b->st_mtim.tv_nsec &&
418 (!S_ISREG(a->st_mode) || a->st_size == b->st_size) && /* if regular file, compare file size */
419 a->st_dev == b->st_dev &&
420 a->st_ino == b->st_ino &&
421 (!(S_ISCHR(a->st_mode) || S_ISBLK(a->st_mode)) || a->st_rdev == b->st_rdev); /* if device node, also compare major/minor, because we can */
422 }
423
424 int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct statx *sx) {
425 static bool avoid_statx = false;
426 struct stat st;
427
428 if (!avoid_statx) {
429 if (statx(dfd, path, flags, mask, sx) < 0) {
430 if (!ERRNO_IS_NOT_SUPPORTED(errno) && errno != EPERM)
431 return -errno;
432
433 /* If statx() is not supported or if we see EPERM (which might indicate seccomp
434 * filtering or so), let's do a fallback. Not that on EACCES we'll not fall back,
435 * since that is likely an indication of fs access issues, which we should
436 * propagate */
437 } else
438 return 0;
439
440 avoid_statx = true;
441 }
442
443 /* Only do fallback if fstatat() supports the flag too, or if it's one of the sync flags, which are
444 * OK to ignore */
445 if ((flags & ~(AT_EMPTY_PATH|AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW|
446 AT_STATX_SYNC_AS_STAT|AT_STATX_FORCE_SYNC|AT_STATX_DONT_SYNC)) != 0)
447 return -EOPNOTSUPP;
448
449 if (fstatat(dfd, path, &st, flags & (AT_EMPTY_PATH|AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW)) < 0)
450 return -errno;
451
452 *sx = (struct statx) {
453 .stx_mask = STATX_TYPE|STATX_MODE|
454 STATX_NLINK|STATX_UID|STATX_GID|
455 STATX_ATIME|STATX_MTIME|STATX_CTIME|
456 STATX_INO|STATX_SIZE|STATX_BLOCKS,
457 .stx_blksize = st.st_blksize,
458 .stx_nlink = st.st_nlink,
459 .stx_uid = st.st_uid,
460 .stx_gid = st.st_gid,
461 .stx_mode = st.st_mode,
462 .stx_ino = st.st_ino,
463 .stx_size = st.st_size,
464 .stx_blocks = st.st_blocks,
465 .stx_rdev_major = major(st.st_rdev),
466 .stx_rdev_minor = minor(st.st_rdev),
467 .stx_dev_major = major(st.st_dev),
468 .stx_dev_minor = minor(st.st_dev),
469 .stx_atime.tv_sec = st.st_atim.tv_sec,
470 .stx_atime.tv_nsec = st.st_atim.tv_nsec,
471 .stx_mtime.tv_sec = st.st_mtim.tv_sec,
472 .stx_mtime.tv_nsec = st.st_mtim.tv_nsec,
473 .stx_ctime.tv_sec = st.st_ctim.tv_sec,
474 .stx_ctime.tv_nsec = st.st_ctim.tv_nsec,
475 };
476
477 return 0;
478 }