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