Line data Source code
1 : // SPDX-License-Identifier: GPL-3.0-or-later
2 : // Copyright (C) 2013 Andrea Mazzoleni
3 :
4 : #include "portable.h"
5 :
6 : #ifndef __MINGW32__ /* Only for Unix */
7 :
8 : #include "support.h"
9 :
10 : /**
11 : * Exit codes.
12 : */
13 : int exit_success = 0;
14 : int exit_failure = 1;
15 : int exit_sync_needed = 2;
16 :
17 : #define BTRFS_SUPER_MAGIC 0x9123683E
18 :
19 : #if HAVE_POSIX_FADVISE
20 : /**
21 : * Wrapper around posix_fadvise() that handles ENOENT by converting it to
22 : * ENOSYS.
23 : *
24 : * Why can posix_fadvise() return ENOENT?
25 : *
26 : * According to POSIX and the Linux kernel's standard fadvise64 implementation
27 : * (mm/fadvise.c), posix_fadvise() should only return EBADF (bad file
28 : * descriptor), ESPIPE (fd refers to a pipe or FIFO), or EINVAL (invalid
29 : * advice value or arguments). ENOENT is never generated by the kernel's
30 : * generic path.
31 : *
32 : * However, the VFS layer allows individual filesystems to provide their own
33 : * .fadvise() method, which is called instead of (or in addition to) the
34 : * generic implementation. Certain filesystem drivers can return ENOENT in
35 : * this method for reasons that are completely unrelated to the file not
36 : * existing (the file is clearly open, we have a valid fd):
37 : *
38 : * - NFS: A stale NFS inode can be revalidated against the server while
39 : * fadvise is being processed. If the server-side file has been removed or
40 : * renamed, the revalidation discovers the inode is gone and returns
41 : * -ENOENT, even though the client still holds an open file descriptor.
42 : *
43 : * - FUSE-based filesystems (mergerfs, sshfs, rclone, etc.): The FUSE daemon
44 : * receives the fadvise request and may forward it to an underlying backend.
45 : * If that backend cannot find the file (e.g., the backing path has changed,
46 : * the FUSE daemon is partially initialised, or the daemon translates the
47 : * request and the target layer returns an error), the daemon may return
48 : * ENOENT to the kernel.
49 : *
50 : * - Overlayfs (used by Docker, Podman, LXC): The overlayfs .fadvise()
51 : * implementation delegates to the lower or upper layer. If the lookup
52 : * through the overlay stack fails to find the real inode (e.g., the upper
53 : * directory entry is absent and the lower copy is in a weird state), the
54 : * delegation can return ENOENT.
55 : *
56 : * - Other network or virtual filesystems: Any out-of-tree or in-tree
57 : * filesystem that implements .fadvise() and performs a path or inode
58 : * lookup as part of processing the hint can propagate ENOENT if that
59 : * internal lookup fails.
60 : *
61 : * Because posix_fadvise() is always an advisory hint, it never affects
62 : * correctness, only performance, it is safe to treat unexpected errors,
63 : * including ENOENT, as "not supported" (ENOSYS) and continue.
64 : */
65 121094 : int posix_fadvise_wrapper(int fd, off_t offset, off_t len, int advice)
66 : {
67 121094 : int ret = posix_fadvise(fd, offset, len, advice);
68 :
69 121094 : if (ret == ENOENT)
70 0 : return ENOSYS;
71 :
72 121094 : return ret;
73 : }
74 : #endif
75 :
76 1830392 : int open_noatime(const char* file, int flags)
77 : {
78 : #ifdef O_NOATIME
79 1830392 : int f = open(file, flags | O_NOATIME);
80 :
81 : /* only root is allowed to use O_NOATIME, in case retry without it */
82 1830392 : if (f == -1 && errno == EPERM)
83 0 : f = open(file, flags);
84 1830392 : return f;
85 : #else
86 : return open(file, flags);
87 : #endif
88 : }
89 :
90 0 : int dirent_hidden(struct dirent* dd)
91 : {
92 0 : return dd->d_name[0] == '.';
93 : }
94 :
95 0 : const char* stat_desc(struct stat* st)
96 : {
97 0 : if (S_ISREG(st->st_mode))
98 0 : return "regular";
99 0 : if (S_ISDIR(st->st_mode))
100 0 : return "directory";
101 0 : if (S_ISCHR(st->st_mode))
102 0 : return "character";
103 0 : if (S_ISBLK(st->st_mode))
104 0 : return "block-device";
105 0 : if (S_ISFIFO(st->st_mode))
106 0 : return "fifo";
107 0 : if (S_ISLNK(st->st_mode))
108 0 : return "symlink";
109 0 : if (S_ISSOCK(st->st_mode))
110 0 : return "socket";
111 0 : return "unknown";
112 : }
113 :
114 : static const char* smartctl_paths[] = {
115 : /* Linux & BSD */
116 : "/usr/sbin/smartctl",
117 : "/sbin/smartctl",
118 : "/usr/local/sbin/smartctl",
119 : "/usr/bin/smartctl",
120 : "/usr/local/bin/smartctl",
121 : /* macOS (Intel & Apple Silicon) */
122 : "/opt/homebrew/sbin/smartctl",
123 : 0
124 : };
125 :
126 83 : const char* find_smartctl(void)
127 : {
128 : int i;
129 :
130 83 : for (i = 0; smartctl_paths[i]; ++i) {
131 83 : if (access(smartctl_paths[i], X_OK) == 0)
132 83 : return smartctl_paths[i];
133 : }
134 :
135 0 : return 0;
136 : }
137 :
138 : /**
139 : * Read a file from sys
140 : *
141 : * Return -1 on error, otherwise the size of data read
142 : */
143 : #if HAVE_LINUX_DEVICE
144 2172 : static int sysread(const char* path, char* buf, size_t buf_size)
145 : {
146 : int f;
147 : int ret;
148 : int len;
149 :
150 2172 : f = open(path, O_RDONLY);
151 2172 : if (f == -1) {
152 : /* LCOV_EXCL_START */
153 : return -1;
154 : /* LCOV_EXCL_STOP */
155 : }
156 :
157 1220 : len = read(f, buf, buf_size);
158 1220 : if (len < 0) {
159 : /* LCOV_EXCL_START */
160 : close(f);
161 : return -1;
162 : /* LCOV_EXCL_STOP */
163 : }
164 :
165 1220 : ret = close(f);
166 1220 : if (ret != 0) {
167 : /* LCOV_EXCL_START */
168 : return -1;
169 : /* LCOV_EXCL_STOP */
170 : }
171 :
172 1220 : return len;
173 : }
174 : #endif
175 :
176 : #if HAVE_LINUX_DEVICE
177 : /*
178 : * sysread_vpd_pg83 — parse raw binary VPD page 0x83 (Device Identification)
179 : * from a sysfs vpd_pg83 file and extract the first NAA (Network Address
180 : * Authority) designator for the Logical Unit (ASSOCIATION == 0x00).
181 : */
182 200 : static int sysattr_vpd_pg80(const char* path, char* dst, size_t dst_size)
183 : {
184 : unsigned char buf[512];
185 200 : int ret = sysread(path, (char*)buf, sizeof(buf));
186 :
187 : /* need at least the header */
188 200 : if (ret < 4) {
189 : /* LCOV_EXCL_START */
190 : return -1;
191 : /* LCOV_EXCL_STOP */
192 : }
193 :
194 : /* validate page code */
195 200 : if (buf[1] != 0x80) {
196 : /* LCOV_EXCL_START */
197 : return -1;
198 : /* LCOV_EXCL_STOP */
199 : }
200 :
201 : /* clamp to what was actually read */
202 200 : size_t page_len = buf[3];
203 200 : size_t available = (size_t)ret - 4;
204 200 : if (available < page_len)
205 0 : page_len = available;
206 :
207 : /* if empty */
208 200 : if (page_len == 0) {
209 : /* LCOV_EXCL_START */
210 : return -1;
211 : /* LCOV_EXCL_STOP */
212 : }
213 :
214 : /* if too bit to store the 0 termination */
215 200 : if (4 + page_len + 1 > sizeof(buf)) {
216 : /* LCOV_EXCL_START */
217 : return -1;
218 : /* LCOV_EXCL_STOP */
219 : }
220 :
221 200 : char* attr = (char*)buf + 4;
222 200 : attr[page_len] = 0;
223 200 : strtrim(attr);
224 :
225 : /* if empty */
226 200 : if (!*attr) {
227 : /* LCOV_EXCL_START */
228 : return -1;
229 : /* LCOV_EXCL_STOP */
230 : }
231 :
232 200 : pathcpy(dst, dst_size, attr);
233 200 : return 0;
234 : }
235 : #endif
236 :
237 : /*
238 : * sysread_vpd_pg83 — parse raw binary VPD page 0x83 (Device Identification)
239 : * from a sysfs vpd_pg83 file and extract the first NAA (Network Address
240 : * Authority) designator for the Logical Unit (ASSOCIATION == 0x00).
241 : *
242 : * The result is written into dst as a lowercase hex string,
243 : * e.g.: "5000c500abcdef01" (8-byte / NAA type 1,2,3,5)
244 : * "6000c500abcdef010000000000000000" (16-byte / NAA type 6)
245 : *
246 : * VPD page 0x83 wire format (SPC-4 §7.7.2)
247 : *
248 : * Page header — 4 bytes:
249 : * [0] peripheral qualifier (7:5) | device type (4:0)
250 : * [1] page code = 0x83
251 : * [2] PAGE LENGTH MSB } 16-bit big-endian: number of bytes that follow
252 : * [3] PAGE LENGTH LSB } the header (i.e. total descriptor bytes)
253 : *
254 : * Then a sequence of Designation Descriptors:
255 : * [+0] PROTOCOL ID (7:4) | CODE SET (3:0)
256 : * 0x1 = binary, 0x2 = ASCII, 0x3 = UTF-8
257 : * [+1] PIV (7) | reserved (6) | ASSOCIATION (5:4) | DESIGNATOR TYPE (3:0)
258 : * ASSOCIATION: 0x0 = logical unit ← we want only this
259 : * 0x1 = target port
260 : * 0x2 = target device
261 : * DESIGNATOR TYPE: 0x3 = NAA ← we want only this
262 : * [+2] reserved
263 : * [+3] DESIGNATOR LENGTH = N (bytes that follow in this descriptor)
264 : * [+4 … +4+N-1] DESIGNATOR (binary for NAA)
265 : *
266 : * ── NAA subtypes (top nibble of designator byte 0) ───────────────────────
267 : *
268 : * 0x1 NAA IEEE Extended → 8 bytes
269 : * 0x2 NAA Locally Assigned → 8 bytes
270 : * 0x3 NAA IEEE Registered → 8 bytes (most common on SATA/SAS)
271 : * 0x5 NAA IEEE Registered → 8 bytes
272 : * 0x6 NAA IEEE Registered Ext. → 16 bytes
273 : * others: reserved
274 : *
275 : * The DESIGNATOR LENGTH from the descriptor header must agree with the
276 : * expected length for the NAA subtype. If they disagree, the descriptor
277 : * is malformed and must be skipped.
278 : *
279 : * Returns 0 and fills dst on success, -1 if no valid NAA LU descriptor found.
280 : */
281 : #if HAVE_LINUX_DEVICE
282 188 : static int sysattr_vpd_pg83(const char* path, char* dst, size_t dst_size)
283 : {
284 : unsigned char buf[4096];
285 :
286 188 : int ret = sysread(path, (char*)buf, sizeof(buf));
287 188 : if (ret < 4) {
288 : /* LCOV_EXCL_START */
289 : return -1;
290 : /* LCOV_EXCL_STOP */
291 : }
292 :
293 : /* validate page code */
294 188 : if (buf[1] != 0x83) {
295 : /* LCOV_EXCL_START */
296 : return -1;
297 : /* LCOV_EXCL_STOP */
298 : }
299 :
300 188 : size_t page_len = ((size_t)buf[2] << 8) | buf[3];
301 :
302 : /* clamp to what was actually read */
303 188 : size_t available = (size_t)ret - 4;
304 188 : if (page_len > available)
305 0 : page_len = available;
306 :
307 : /* if empty */
308 188 : if (page_len == 0) {
309 : /* LCOV_EXCL_START */
310 : return -1;
311 : /* LCOV_EXCL_STOP */
312 : }
313 :
314 : /*
315 : * Walk every Designation Descriptor looking for the first NAA descriptor
316 : * that is associated with the Logical Unit (ASSOCIATION == 0x00).
317 : *
318 : * We take the first one found rather than scoring: all NAA subtypes are
319 : * globally unique by construction; the first LU-associated NAA is the
320 : * canonical device WWN.
321 : */
322 188 : size_t offset = 0;
323 552 : while (offset + 4 <= page_len) {
324 552 : const unsigned char* desc = buf + 4 + offset;
325 :
326 552 : unsigned code_set = desc[0] & 0x0f;
327 552 : unsigned assoc = (desc[1] >> 4) & 0x03;
328 552 : unsigned dtype = desc[1] & 0x0f;
329 552 : size_t id_len = desc[3];
330 :
331 : /* bounds check: the full descriptor must lie within page_len */
332 552 : if (offset + 4 + id_len > page_len)
333 0 : break; /* malformed page — stop iterating */
334 :
335 : /* skip anything that is not an LU-associated NAA descriptor */
336 552 : if (assoc != 0x00 || dtype != 0x03)
337 364 : goto next;
338 :
339 : /*
340 : * NAA is always binary (CODE SET == 0x01).
341 : * Reject if the device mis-advertises it as ASCII (defensive).
342 : */
343 188 : if (code_set != 0x01)
344 0 : goto next;
345 :
346 : /* if empty */
347 188 : if (id_len == 0)
348 0 : goto next;
349 :
350 : /*
351 : * Validate NAA subtype vs. expected byte length.
352 : * The NAA value is the top nibble of the first byte of the
353 : * designator (NOT the descriptor header byte).
354 : */
355 188 : unsigned naa = (desc[4] >> 4) & 0x0f;
356 188 : size_t expected = 0;
357 :
358 188 : switch (naa) {
359 188 : case 0x1 : /* IEEE Extended */
360 : case 0x2 : /* lLocally Assigned */
361 : case 0x3 : /* IEEE Registered */
362 : case 0x5 : /* IEEE Registered */
363 188 : expected = 8;
364 188 : break;
365 0 : case 0x6 : /* IEEE Registered Extended */
366 0 : expected = 16;
367 0 : break;
368 0 : default :
369 0 : goto next;
370 : }
371 :
372 : /* DESIGNATOR LENGTH must match the NAA subtype exactly */
373 188 : if (id_len != expected)
374 0 : goto next;
375 :
376 : /*
377 : * Format as "naa:<hex>".
378 : * Each byte → 2 hex digits; prefix "naa:" is 4 chars; +1 for NUL.
379 : * Maximum: "naa:" + 32 hex digits (16 bytes) + NUL = 37 bytes.
380 : */
381 188 : if (dst_size < 4 + id_len * 2 + 1)
382 0 : return -1; /* caller's buffer too small */
383 :
384 1692 : for (size_t i = 0; i < id_len; i++)
385 1504 : snprintf(dst + i * 2, 3, "%02x", desc[4 + i]);
386 188 : dst[4 + id_len * 2] = '\0';
387 :
388 188 : return 0;
389 :
390 364 : next:
391 364 : offset += 4 + id_len;
392 : }
393 :
394 0 : return -1;
395 : }
396 : #endif
397 :
398 : /**
399 : * Read a file from sys.
400 : * Trim spaces.
401 : * Always put an ending 0.
402 : * Do not report error on reading.
403 : * Return an error if truncated
404 : *
405 : * Return -1 on error, 0 on success
406 : */
407 : #if HAVE_LINUX_DEVICE
408 1188 : static int sysattr(const char* path, char* buf, size_t buf_size)
409 : {
410 : int len;
411 :
412 1188 : len = sysread(path, buf, buf_size);
413 1188 : if (len < 0) {
414 : /* LCOV_EXCL_START */
415 : return -1;
416 : /* LCOV_EXCL_STOP */
417 : }
418 :
419 236 : if ((size_t)len + 1 > buf_size) {
420 : /* LCOV_EXCL_START */
421 : return -1;
422 : /* LCOV_EXCL_STOP */
423 : }
424 :
425 236 : buf[len] = 0;
426 :
427 236 : strtrim(buf);
428 :
429 236 : return 0;
430 : }
431 : #endif
432 :
433 : struct dev_struct {
434 : uint64_t device; /**< Device ID. */
435 : tommy_node node;
436 : };
437 :
438 : /**
439 : * Get the devices of a virtual device.
440 : *
441 : * This is intended to resolve the case of Btrfs filesystems that
442 : * create a virtual superblock (major==0) not backed by any low
443 : * level device.
444 : *
445 : * See:
446 : * Bug 711881 - too funny btrfs st_dev numbers
447 : * https://bugzilla.redhat.com/show_bug.cgi?id=711881
448 : */
449 : #if HAVE_LINUX_DEVICE
450 157 : static int devdereference(uint64_t device, const char* dir, tommy_list* devlist)
451 : {
452 157 : if (major(device) == 0) {
453 : struct statfs sfs;
454 :
455 0 : int fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
456 0 : if (fd < 0) {
457 : /* LCOV_EXCL_START */
458 : return -1;
459 : /* LCOV_EXCL_STOP */
460 : }
461 :
462 0 : if (fstatfs(fd, &sfs) != 0) {
463 : /* LCOV_EXCL_START */
464 : close(fd);
465 : return -1;
466 : /* LCOV_EXCL_STOP */
467 : }
468 :
469 0 : if (sfs.f_type != BTRFS_SUPER_MAGIC) {
470 : /* LCOV_EXCL_START */
471 : close(fd);
472 : return -1;
473 : /* LCOV_EXCL_STOP */
474 : }
475 :
476 : struct btrfs_ioctl_fs_info_args fs_info;
477 0 : memset(&fs_info, 0, sizeof(fs_info));
478 0 : if (ioctl(fd, BTRFS_IOC_FS_INFO, &fs_info) < 0) {
479 : /* LCOV_EXCL_START */
480 : close(fd);
481 : return -1;
482 : /* LCOV_EXCL_STOP */
483 : }
484 :
485 0 : if (fs_info.max_id == 0) {
486 : /* LCOV_EXCL_START */
487 : close(fd);
488 : return -1;
489 : /* LCOV_EXCL_STOP */
490 : }
491 :
492 0 : for (__u64 i = 1; i <= fs_info.max_id; ++i) {
493 : struct btrfs_ioctl_dev_info_args dev_info;
494 0 : memset(&dev_info, 0, sizeof(dev_info));
495 0 : dev_info.devid = i;
496 :
497 0 : if (ioctl(fd, BTRFS_IOC_DEV_INFO, &dev_info) != 0) {
498 : /* LCOV_EXCL_START */
499 : close(fd);
500 : return -1;
501 : /* LCOV_EXCL_STOP */
502 : }
503 :
504 : /* get major:minor, use stat on the path returned */
505 : struct stat st;
506 0 : if (stat((char*)dev_info.path, &st) != 0) {
507 : /* LCOV_EXCL_START */
508 : close(fd);
509 : return -1;
510 : /* LCOV_EXCL_STOP */
511 : }
512 :
513 0 : struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
514 0 : dev->device = st.st_rdev;
515 0 : tommy_list_insert_tail(devlist, &dev->node, dev);
516 : }
517 :
518 0 : close(fd);
519 0 : return 0;
520 : }
521 :
522 : /* insert the device itself in the list */
523 157 : struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
524 157 : dev->device = device;
525 157 : tommy_list_insert_tail(devlist, &dev->node, dev);
526 :
527 157 : return 0;
528 : }
529 : #endif
530 :
531 : /**
532 : * Read a file extracting the specified tag TAG=VALUE format.
533 : * Return !=0 on error.
534 : */
535 : #if HAVE_LINUX_DEVICE
536 439 : static int tagread(const char* path, const char* tag, char* value, size_t value_size)
537 : {
538 : int ret;
539 : char buf[512];
540 : size_t tag_len;
541 : char* i;
542 : char* e;
543 :
544 439 : ret = sysread(path, buf, sizeof(buf));
545 439 : if (ret < 0) {
546 : /* LCOV_EXCL_START */
547 : log_fatal(errno, "Failed to read '%s'.\n", path);
548 : return -1;
549 : /* LCOV_EXCL_STOP */
550 : }
551 439 : if ((size_t)ret + 1 > sizeof(buf)) {
552 : /* LCOV_EXCL_START */
553 : log_fatal(EEXTERNAL, "Too long read '%s'.\n", path);
554 : return -1;
555 : /* LCOV_EXCL_STOP */
556 : }
557 :
558 : /* ending 0 */
559 439 : buf[ret] = 0;
560 :
561 439 : tag_len = strlen(tag);
562 :
563 7725 : for (i = buf; *i; ++i) {
564 7725 : char* p = i;
565 :
566 : /* start with a space */
567 7725 : if (p != buf) {
568 7286 : if (!isspace(*p))
569 6408 : continue;
570 878 : ++p;
571 : }
572 :
573 1317 : if (strncmp(p, tag, tag_len) != 0)
574 878 : continue;
575 439 : p += tag_len;
576 :
577 : /* end with a = */
578 439 : if (*p != '=')
579 0 : continue;
580 439 : ++p;
581 :
582 : /* found */
583 439 : i = p;
584 439 : break;
585 : }
586 439 : if (!*i) {
587 : /* LCOV_EXCL_START */
588 : log_fatal(EEXTERNAL, "Missing tag '%s' for '%s'.\n", tag, path);
589 : return -1;
590 : /* LCOV_EXCL_STOP */
591 : }
592 :
593 : /* terminate at the first space */
594 439 : e = i;
595 1941 : while (*e != 0 && !isspace(*e))
596 1502 : ++e;
597 439 : *e = 0;
598 :
599 439 : if (!*i) {
600 : /* LCOV_EXCL_START */
601 : log_fatal(EEXTERNAL, "Empty tag '%s' for '%s'.\n", tag, path);
602 : return -1;
603 : /* LCOV_EXCL_STOP */
604 : }
605 :
606 439 : pathprint(value, value_size, "%s", i);
607 :
608 439 : return 0;
609 : }
610 : #endif
611 :
612 : /**
613 : * Get the device file from the device number.
614 : *
615 : * It uses /sys/dev/block/.../uevent.
616 : *
617 : * For null device (major==0) it fails.
618 : */
619 : #if HAVE_LINUX_DEVICE
620 439 : static int devresolve_sys(dev_t device, char* path, size_t path_size)
621 : {
622 : struct stat st;
623 : char buf[PATH_MAX];
624 :
625 : /* default device path from device number */
626 439 : pathprint(path, path_size, "/sys/dev/block/%u:%u/uevent", major(device), minor(device));
627 :
628 439 : if (tagread(path, "DEVNAME", buf, sizeof(buf)) != 0) {
629 : /* LCOV_EXCL_START */
630 : log_tag("resolve:sys:%u:%u: failed to read DEVNAME tag '%s'\n", major(device), minor(device), path);
631 : return -1;
632 : /* LCOV_EXCL_STOP */
633 : }
634 :
635 : /* set the real device path */
636 439 : pathprint(path, path_size, "/dev/%s", buf);
637 :
638 : /* check the device */
639 439 : if (stat(path, &st) != 0) {
640 : /* LCOV_EXCL_START */
641 : log_tag("resolve:sys:%u:%u: failed to stat '%s'\n", major(device), minor(device), path);
642 : return -1;
643 : /* LCOV_EXCL_STOP */
644 : }
645 439 : if (st.st_rdev != device) {
646 : /* LCOV_EXCL_START */
647 : log_tag("resolve:sys:%u:%u: unexpected device '%u:%u' for '%s'.\n", major(device), minor(device), major(st.st_rdev), minor(st.st_rdev), path);
648 : return -1;
649 : /* LCOV_EXCL_STOP */
650 : }
651 :
652 439 : log_tag("resolve:sys:%u:%u:%s: found\n", major(device), minor(device), path);
653 :
654 439 : return 0;
655 : }
656 : #endif
657 :
658 : /**
659 : * Get the device file from the device number.
660 : */
661 : #if HAVE_LINUX_DEVICE
662 439 : static int devresolve(uint64_t device, char* path, size_t path_size)
663 : {
664 439 : if (devresolve_sys(device, path, path_size) == 0)
665 439 : return 0;
666 :
667 0 : return -1;
668 : }
669 : #endif
670 :
671 : /**
672 : * Cache used by blkid.
673 : */
674 : #if HAVE_BLKID
675 : static blkid_cache cache = 0;
676 : #endif
677 :
678 : /**
679 : * Get the UUID using the /dev/disk/by-uuid/ links.
680 : * It doesn't require root permission, and the uuid are always updated.
681 : * It doesn't work with Btrfs file-systems that don't export the main UUID
682 : * in /dev/disk/by-uuid/.
683 : */
684 : #if HAVE_LINUX_DEVICE
685 10968 : static int devuuid_dev(uint64_t device, char* uuid, size_t uuid_size)
686 : {
687 : int ret;
688 : DIR* d;
689 : struct dirent* dd;
690 : struct stat st;
691 :
692 : /* scan the UUID directory searching for the device */
693 10968 : d = opendir("/dev/disk/by-uuid");
694 10968 : if (!d) {
695 : /* LCOV_EXCL_START */
696 : log_tag("uuid:by-uuid:%u:%u: opendir(/dev/disk/by-uuid) failed, %s\n", major(device), minor(device), strerror(errno));
697 : /* directory missing?, likely we are not in Linux */
698 : return -1;
699 : /* LCOV_EXCL_STOP */
700 : }
701 :
702 10968 : int dir_fd = dirfd(d);
703 10968 : if (dir_fd == -1) {
704 : /* LCOV_EXCL_START */
705 : log_tag("uuid:by-uuid:%u:%u: dirfd(/dev/disk/by-uuid) failed, %s\n", major(device), minor(device), strerror(errno));
706 : return -1;
707 : /* LCOV_EXCL_STOP */
708 : }
709 :
710 196772 : while ((dd = readdir(d)) != 0) {
711 : /* skip "." and ".." files, UUIDs never start with '.' */
712 196768 : if (dd->d_name[0] == '.')
713 21936 : continue;
714 :
715 174832 : ret = fstatat(dir_fd, dd->d_name, &st, 0);
716 174832 : if (ret != 0) {
717 : /* LCOV_EXCL_START */
718 : log_tag("uuid:by-uuid:%u:%u: fstatat(%s) failed, %s\n", major(device), minor(device), dd->d_name, strerror(errno));
719 : /* generic error, ignore and continue the search */
720 : continue;
721 : /* LCOV_EXCL_STOP */
722 : }
723 :
724 : /* if it matches, we have the uuid */
725 174832 : if (S_ISBLK(st.st_mode) && st.st_rdev == (dev_t)device) {
726 : char buf[PATH_MAX];
727 : char path[PATH_MAX];
728 :
729 : /* resolve the link */
730 10964 : pathprint(path, sizeof(path), "/dev/disk/by-uuid/%s", dd->d_name);
731 10964 : ret = readlink(path, buf, sizeof(buf));
732 10964 : if (ret < 0 || ret >= PATH_MAX) {
733 : /* LCOV_EXCL_START */
734 : log_tag("uuid:by-uuid:%u:%u: readlink(/dev/disk/by-uuid/%s) failed, %s\n", major(device), minor(device), dd->d_name, strerror(errno));
735 : /* generic error, ignore and continue the search */
736 : continue;
737 : /* LCOV_EXCL_STOP */
738 : }
739 10964 : buf[ret] = 0;
740 :
741 : /* found */
742 10964 : pathcpy(uuid, uuid_size, dd->d_name);
743 :
744 10964 : log_tag("uuid:by-uuid:%u:%u:%s: found %s\n", major(device), minor(device), uuid, buf);
745 :
746 10964 : closedir(d);
747 10964 : return 0;
748 : }
749 : }
750 :
751 4 : log_tag("uuid:by-uuid:%u:%u: /dev/disk/by-uuid doesn't contain a matching block device\n", major(device), minor(device));
752 :
753 : /* not found */
754 4 : closedir(d);
755 4 : return -1;
756 : }
757 : #endif
758 :
759 : /**
760 : * Get the UUID using libblkid.
761 : * It uses a cache to work without root permission, resulting in UUID
762 : * not necessarily recent.
763 : * We could call blkid_probe_all() to refresh the UUID, but it would
764 : * require root permission to read the superblocks, and resulting in
765 : * all the disks spinning.
766 : */
767 : #if HAVE_BLKID
768 4 : static int devuuid_blkid(uint64_t device, char* uuid, size_t uuid_size)
769 : {
770 : char* devname;
771 : char* uuidname;
772 :
773 4 : devname = blkid_devno_to_devname(device);
774 4 : if (!devname) {
775 : /* LCOV_EXCL_START */
776 : log_tag("uuid:blkid:%u:%u: blkid_devno_to_devname() failed, %s\n", major(device), minor(device), strerror(errno));
777 : /* device mapping failed */
778 : return -1;
779 : /* LCOV_EXCL_STOP */
780 : }
781 :
782 0 : uuidname = blkid_get_tag_value(cache, "UUID", devname);
783 0 : if (!uuidname) {
784 : /* LCOV_EXCL_START */
785 : log_tag("uuid:blkid:%u:%u: blkid_get_tag_value(UUID,%s) failed, %s\n", major(device), minor(device), devname, strerror(errno));
786 : /* uuid mapping failed */
787 : free(devname);
788 : return -1;
789 : /* LCOV_EXCL_STOP */
790 : }
791 :
792 0 : pathcpy(uuid, uuid_size, uuidname);
793 :
794 0 : log_tag("uuid:blkid:%u:%u:%s: found %s\n", major(device), minor(device), uuid, devname);
795 :
796 0 : free(devname);
797 0 : free(uuidname);
798 0 : return 0;
799 : }
800 : #endif
801 :
802 :
803 : /**
804 : * Get the LABEL using libblkid.
805 : * It uses a cache to work without root permission, resulting in LABEL
806 : * not necessarily recent.
807 : */
808 : #if HAVE_BLKID
809 6002 : static int devuuid_label(uint64_t device, char* label, size_t label_size)
810 : {
811 : char* devname;
812 : char* labelname;
813 :
814 6002 : devname = blkid_devno_to_devname(device);
815 6002 : if (!devname) {
816 : /* LCOV_EXCL_START */
817 : log_tag("label:blkid:%u:%u: blkid_devno_to_devname() failed, %s\n", major(device), minor(device), strerror(errno));
818 : /* device mapping failed */
819 : return -1;
820 : /* LCOV_EXCL_STOP */
821 : }
822 :
823 5939 : labelname = blkid_get_tag_value(cache, "LABEL", devname);
824 5939 : if (!labelname) {
825 : /* LCOV_EXCL_START */
826 : log_tag("label:blkid:%u:%u: blkid_get_tag_value(LABEL,%s) failed, %s\n", major(device), minor(device), devname, strerror(errno));
827 : free(devname);
828 : /* label mapping failed */
829 : return -1;
830 : /* LCOV_EXCL_STOP */
831 : }
832 :
833 5939 : pathcpy(label, label_size, labelname);
834 :
835 5939 : log_tag("label:blkid:%u:%u:%s: found %s\n", major(device), minor(device), label, devname);
836 :
837 5939 : free(devname);
838 5939 : free(labelname);
839 5939 : return 0;
840 : }
841 : #endif
842 :
843 :
844 : #ifdef __APPLE__
845 : static int devuuid_darwin(const char* path, char* uuid, size_t uuid_size)
846 : {
847 : CFStringRef path_apple = 0;
848 : DASessionRef session = 0;
849 : CFURLRef path_appler = 0;
850 : int result = -1;
851 :
852 : *uuid = 0;
853 :
854 : path_apple = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
855 : if (!path_apple)
856 : goto bail;
857 :
858 : session = DASessionCreate(kCFAllocatorDefault);
859 : if (!session)
860 : goto bail;
861 :
862 : path_appler = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_apple, kCFURLPOSIXPathStyle, false);
863 : if (!path_appler)
864 : goto bail;
865 :
866 : /* find the mount point */
867 : DADiskRef disk = NULL;
868 : while (path_appler) {
869 : disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, path_appler);
870 : if (disk)
871 : break;
872 :
873 : CFURLRef parent = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, path_appler);
874 : if (!parent)
875 : break;
876 :
877 : /* check if we hit the root (parent == child) */
878 : if (CFEqual(parent, path_appler)) {
879 : CFRelease(parent);
880 : break;
881 : }
882 :
883 : CFRelease(path_appler);
884 : path_appler = parent;
885 : }
886 :
887 : /* safely extract the UUID */
888 : if (disk) {
889 : CFDictionaryRef description = DADiskCopyDescription(disk);
890 : if (description) {
891 : /* key might not exist for NTFS/ExFAT on some drivers */
892 : const void* value = (CFUUIDRef)CFDictionaryGetValue(description, kDADiskDescriptionVolumeUUIDKey);
893 : CFStringRef uuid_string = NULL;
894 :
895 : if (value) {
896 : if (CFGetTypeID(value) == CFUUIDGetTypeID()) {
897 : uuid_string = CFUUIDCreateString(kCFAllocatorDefault, (CFUUIDRef)value);
898 : } else if (CFGetTypeID(value) == CFStringGetTypeID()) {
899 : /* if it's already a string, retain it so we can release it later consistently */
900 : uuid_string = (CFStringRef)CFRetain(value);
901 : }
902 : }
903 :
904 : if (uuid_string) {
905 : if (CFStringGetCString(uuid_string, uuid, uuid_size, kCFStringEncodingUTF8)) {
906 : result = 0; /* success */
907 : }
908 : CFRelease(uuid_string);
909 : }
910 :
911 : CFRelease(description);
912 : }
913 : CFRelease(disk);
914 : }
915 :
916 : bail:
917 : /* clean up */
918 : if (path_appler) CFRelease(path_appler);
919 : if (session) CFRelease(session);
920 : if (path_apple) CFRelease(path_apple);
921 :
922 : return result;
923 : }
924 : #endif
925 :
926 : #if HAVE_LINUX_DEVICE
927 11031 : static int devuuid_btrfs(uint64_t device, const char* dir, char* uuid, size_t uuid_size)
928 : {
929 : struct statfs sfs;
930 :
931 11031 : int fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
932 11031 : if (fd < 0) {
933 : /* LCOV_EXCL_START */
934 : return -1;
935 : /* LCOV_EXCL_STOP*/
936 : }
937 :
938 6462 : if (fstatfs(fd, &sfs) != 0) {
939 : /* LCOV_EXCL_START */
940 : close(fd);
941 : return -1;
942 : /* LCOV_EXCL_STOP */
943 : }
944 :
945 6462 : if (sfs.f_type != BTRFS_SUPER_MAGIC) {
946 6399 : close(fd);
947 6399 : return -1;
948 : }
949 :
950 : struct btrfs_ioctl_fs_info_args info;
951 63 : memset(&info, 0, sizeof(info));
952 63 : if (ioctl(fd, BTRFS_IOC_FS_INFO, &info) < 0) {
953 : /* LCOV_EXCL_START */
954 : close(fd);
955 : return -1;
956 : /* LCOV_EXCL_STOP */
957 : }
958 :
959 63 : snprintf(uuid, uuid_size,
960 : "%02x%02x%02x%02x-"
961 : "%02x%02x-"
962 : "%02x%02x-"
963 : "%02x%02x-"
964 : "%02x%02x%02x%02x%02x%02x",
965 63 : info.fsid[0], info.fsid[1], info.fsid[2], info.fsid[3],
966 63 : info.fsid[4], info.fsid[5],
967 63 : info.fsid[6], info.fsid[7],
968 63 : info.fsid[8], info.fsid[9],
969 63 : info.fsid[10], info.fsid[11], info.fsid[12], info.fsid[13], info.fsid[14], info.fsid[15]
970 : );
971 :
972 63 : log_tag("uuid:by-btrfs:%u:%u:%s: found %s\n", major(device), minor(device), uuid, dir);
973 :
974 63 : close(fd);
975 63 : return 0;
976 : }
977 : #endif
978 :
979 11031 : int devuuid(uint64_t device_id, const char* device_path, char* uuid, size_t uuid_size)
980 : {
981 : (void)device_path;
982 : (void)device_id;
983 :
984 : #ifdef __APPLE__
985 : if (devuuid_darwin(device_path, uuid, uuid_size) == 0)
986 : return 0;
987 : #endif
988 :
989 : #if HAVE_LINUX_DEVICE
990 11031 : if (devuuid_btrfs(device_id, device_path, uuid, uuid_size) == 0)
991 63 : return 0;
992 : #endif
993 :
994 : #if HAVE_LINUX_DEVICE
995 10968 : if (devuuid_dev(device_id, uuid, uuid_size) == 0)
996 10964 : return 0;
997 : #else
998 : log_tag("uuid:by-uuid:%u:%u: by-uuid not supported\n", major(device_id), minor(device_id));
999 : #endif
1000 :
1001 : #if HAVE_BLKID
1002 4 : if (devuuid_blkid(device_id, uuid, uuid_size) == 0)
1003 0 : return 0;
1004 : #else
1005 : log_tag("uuid:blkid:%u:%u: blkid support not compiled in\n", major(device_id), minor(device_id));
1006 : #endif
1007 :
1008 4 : log_tag("uuid:notfound:%u:%u:\n", major(device_id), minor(device_id));
1009 :
1010 : /* not supported */
1011 : (void)uuid;
1012 : (void)uuid_size;
1013 4 : return -1;
1014 : }
1015 :
1016 11432 : int filephy(const char* path, uint64_t size, uint64_t* physical)
1017 : {
1018 : #if HAVE_LINUX_FIEMAP_H
1019 : /*
1020 : * In Linux get the real physical address of the file
1021 : * Note that FIEMAP doesn't require root permission
1022 : */
1023 : int f;
1024 : struct fiemap* fiemap;
1025 : size_t fiemap_size;
1026 : unsigned int blknum;
1027 :
1028 11432 : f = open(path, O_RDONLY);
1029 11432 : if (f == -1) {
1030 : /* LCOV_EXCL_START */
1031 : return -1;
1032 : /* LCOV_EXCL_STOP */
1033 : }
1034 :
1035 : /*
1036 : * First try with FIEMAP
1037 : * if works for ext2, ext3, ext4, xfs, btrfs
1038 : */
1039 11432 : fiemap_size = sizeof(struct fiemap) + sizeof(struct fiemap_extent);
1040 11432 : fiemap = malloc_nofail(fiemap_size);
1041 11432 : memset(fiemap, 0, fiemap_size);
1042 11432 : fiemap->fm_start = 0;
1043 11432 : fiemap->fm_length = ~0ULL;
1044 11432 : fiemap->fm_flags = FIEMAP_FLAG_SYNC; /* required to ensure that just created files report a valid address and not 0 */
1045 11432 : fiemap->fm_extent_count = 1; /* we are interested only at the first block */
1046 :
1047 11432 : if (ioctl(f, FS_IOC_FIEMAP, fiemap) != -1) {
1048 11432 : uint32_t flags = fiemap->fm_extents[0].fe_flags;
1049 11432 : uint64_t offset = fiemap->fm_extents[0].fe_physical;
1050 :
1051 : /* check some condition for validating the offset */
1052 11432 : if (flags & FIEMAP_EXTENT_DATA_INLINE) {
1053 : /* if the data is inline, we don't have an offset to report */
1054 0 : *physical = FILEPHY_WITHOUT_OFFSET;
1055 11432 : } else if (flags & FIEMAP_EXTENT_UNKNOWN) {
1056 : /* if the offset is unknown, we don't have an offset to report */
1057 0 : *physical = FILEPHY_WITHOUT_OFFSET;
1058 11432 : } else if (offset == 0) {
1059 : /*
1060 : * 0 is the general fallback for file-systems when
1061 : * they don't have an offset to report
1062 : */
1063 14 : *physical = FILEPHY_WITHOUT_OFFSET;
1064 : } else {
1065 : /* finally report the real offset */
1066 11418 : *physical = offset + FILEPHY_REAL_OFFSET;
1067 : }
1068 :
1069 11432 : free(fiemap);
1070 :
1071 11432 : if (close(f) == -1)
1072 0 : return -1;
1073 11432 : return 0;
1074 : }
1075 :
1076 0 : free(fiemap);
1077 :
1078 : /* if the file is empty, FIBMAP doesn't work, and we don't even try to use it */
1079 0 : if (size == 0) {
1080 0 : *physical = FILEPHY_WITHOUT_OFFSET;
1081 0 : if (close(f) == -1)
1082 0 : return -1;
1083 0 : return 0;
1084 : }
1085 :
1086 : /*
1087 : * Then try with FIBMAP
1088 : * it works for jfs, reiserfs, ntfs-3g
1089 : * in exfat it always returns 0, that it's anyway better than the fake inodes
1090 : */
1091 0 : blknum = 0; /* first block */
1092 0 : if (ioctl(f, FIBMAP, &blknum) != -1) {
1093 0 : *physical = blknum + FILEPHY_REAL_OFFSET;
1094 0 : if (close(f) == -1)
1095 0 : return -1;
1096 0 : return 0;
1097 : }
1098 :
1099 : /*
1100 : * Otherwise don't use anything, and keep the directory traversal order
1101 : * at now this should happen only for vfat
1102 : * and it's surely better than using fake inodes
1103 : */
1104 0 : *physical = FILEPHY_UNREPORTED_OFFSET;
1105 0 : if (close(f) == -1)
1106 0 : return -1;
1107 : #else
1108 : /*
1109 : * In a generic Unix use a dummy value for all the files
1110 : * We don't want to risk to use the inode without knowing
1111 : * if it really improves performance.
1112 : * In this way we keep them in the directory traversal order
1113 : * that at least keeps files in the same directory together.
1114 : * Note also that in newer file-system with snapshot, like ZFS,
1115 : * the inode doesn't represent even more the disk position, because files
1116 : * are not overwritten in place, but rewritten in another location
1117 : * of the disk.
1118 : */
1119 : *physical = FILEPHY_UNREPORTED_OFFSET;
1120 :
1121 : (void)path; /* not used here */
1122 : (void)size;
1123 : #endif
1124 :
1125 0 : return 0;
1126 : }
1127 :
1128 : /* from man statfs */
1129 : #define ADFS_SUPER_MAGIC 0xadf5
1130 : #define AFFS_SUPER_MAGIC 0xADFF
1131 : #define BDEVFS_MAGIC 0x62646576
1132 : #define BEFS_SUPER_MAGIC 0x42465331
1133 : #define BFS_MAGIC 0x1BADFACE
1134 : #define BINFMTFS_MAGIC 0x42494e4d
1135 : #define BTRFS_SUPER_MAGIC 0x9123683E
1136 : #define CGROUP_SUPER_MAGIC 0x27e0eb
1137 : #define CIFS_MAGIC_NUMBER 0xFF534D42
1138 : #define CODA_SUPER_MAGIC 0x73757245
1139 : #define COH_SUPER_MAGIC 0x012FF7B7
1140 : #define CRAMFS_MAGIC 0x28cd3d45
1141 : #define DEBUGFS_MAGIC 0x64626720
1142 : #define DEVFS_SUPER_MAGIC 0x1373
1143 : #define DEVPTS_SUPER_MAGIC 0x1cd1
1144 : #define EFIVARFS_MAGIC 0xde5e81e4
1145 : #define EFS_SUPER_MAGIC 0x00414A53
1146 : #define EXT_SUPER_MAGIC 0x137D
1147 : #define EXT2_OLD_SUPER_MAGIC 0xEF51
1148 : #define EXT4_SUPER_MAGIC 0xEF53 /* also ext2/ext3 */
1149 : #define FUSE_SUPER_MAGIC 0x65735546
1150 : #define FUTEXFS_SUPER_MAGIC 0xBAD1DEA
1151 : #define HFS_SUPER_MAGIC 0x4244
1152 : #define HFSPLUS_SUPER_MAGIC 0x482b
1153 : #define HOSTFS_SUPER_MAGIC 0x00c0ffee
1154 : #define HPFS_SUPER_MAGIC 0xF995E849
1155 : #define HUGETLBFS_MAGIC 0x958458f6
1156 : #define ISOFS_SUPER_MAGIC 0x9660
1157 : #define JFFS2_SUPER_MAGIC 0x72b6
1158 : #define JFS_SUPER_MAGIC 0x3153464a
1159 : #define MINIX_SUPER_MAGIC 0x137F
1160 : #define MINIX_SUPER_MAGIC2 0x138F
1161 : #define MINIX2_SUPER_MAGIC 0x2468
1162 : #define MINIX2_SUPER_MAGIC2 0x2478
1163 : #define MINIX3_SUPER_MAGIC 0x4d5a
1164 : #define MQUEUE_MAGIC 0x19800202
1165 : #define MSDOS_SUPER_MAGIC 0x4d44
1166 : #define NCP_SUPER_MAGIC 0x564c
1167 : #define NFS_SUPER_MAGIC 0x6969
1168 : #define NILFS_SUPER_MAGIC 0x3434
1169 : #define NTFS_SB_MAGIC 0x5346544e
1170 : #define OCFS2_SUPER_MAGIC 0x7461636f
1171 : #define OPENPROM_SUPER_MAGIC 0x9fa1
1172 : #define PIPEFS_MAGIC 0x50495045
1173 : #define PROC_SUPER_MAGIC 0x9fa0
1174 : #define PSTOREFS_MAGIC 0x6165676C
1175 : #define QNX4_SUPER_MAGIC 0x002f
1176 : #define QNX6_SUPER_MAGIC 0x68191122
1177 : #define RAMFS_MAGIC 0x858458f6
1178 : #define REISERFS_SUPER_MAGIC 0x52654973
1179 : #define ROMFS_MAGIC 0x7275
1180 : #define SELINUX_MAGIC 0xf97cff8c
1181 : #define SMACK_MAGIC 0x43415d53
1182 : #define SMB_SUPER_MAGIC 0x517B
1183 : #define SOCKFS_MAGIC 0x534F434B
1184 : #define SQUASHFS_MAGIC 0x73717368
1185 : #define SYSFS_MAGIC 0x62656572
1186 : #define SYSV2_SUPER_MAGIC 0x012FF7B6
1187 : #define SYSV4_SUPER_MAGIC 0x012FF7B5
1188 : #define TMPFS_MAGIC 0x01021994
1189 : #define UDF_SUPER_MAGIC 0x15013346
1190 : #define UFS_MAGIC 0x00011954
1191 : #define USBDEVICE_SUPER_MAGIC 0x9fa2
1192 : #define V9FS_MAGIC 0x01021997
1193 : #define VXFS_SUPER_MAGIC 0xa501FCF5
1194 : #define XENFS_SUPER_MAGIC 0xabba1974
1195 : #define XENIX_SUPER_MAGIC 0x012FF7B4
1196 : #define XFS_SUPER_MAGIC 0x58465342
1197 : #define _XIAFS_SUPER_MAGIC 0x012FD16D
1198 : #define AFS_SUPER_MAGIC 0x5346414F
1199 : #define AUFS_SUPER_MAGIC 0x61756673
1200 : #define ANON_INODE_FS_SUPER_MAGIC 0x09041934
1201 : #define CEPH_SUPER_MAGIC 0x00C36400
1202 : #define ECRYPTFS_SUPER_MAGIC 0xF15F
1203 : #define FAT_SUPER_MAGIC 0x4006
1204 : #define FHGFS_SUPER_MAGIC 0x19830326
1205 : #define FUSEBLK_SUPER_MAGIC 0x65735546
1206 : #define FUSECTL_SUPER_MAGIC 0x65735543
1207 : #define GFS_SUPER_MAGIC 0x1161970
1208 : #define GPFS_SUPER_MAGIC 0x47504653
1209 : #define MTD_INODE_FS_SUPER_MAGIC 0x11307854
1210 : #define INOTIFYFS_SUPER_MAGIC 0x2BAD1DEA
1211 : #define ISOFS_R_WIN_SUPER_MAGIC 0x4004
1212 : #define ISOFS_WIN_SUPER_MAGIC 0x4000
1213 : #define JFFS_SUPER_MAGIC 0x07C0
1214 : #define KAFS_SUPER_MAGIC 0x6B414653
1215 : #define LUSTRE_SUPER_MAGIC 0x0BD00BD0
1216 : #define NFSD_SUPER_MAGIC 0x6E667364
1217 : #define PANFS_SUPER_MAGIC 0xAAD7AAEA
1218 : #define RPC_PIPEFS_SUPER_MAGIC 0x67596969
1219 : #define SECURITYFS_SUPER_MAGIC 0x73636673
1220 : #define UFS_BYTESWAPPED_SUPER_MAGIC 0x54190100
1221 : #define VMHGFS_SUPER_MAGIC 0xBACBACBC
1222 : #define VZFS_SUPER_MAGIC 0x565A4653
1223 : #define ZFS_SUPER_MAGIC 0x2FC12FC1
1224 :
1225 : struct filesystem_entry {
1226 : unsigned id;
1227 : const char* name;
1228 : int remote;
1229 : } FILESYSTEMS[] = {
1230 : { ADFS_SUPER_MAGIC, "adfs", 0 },
1231 : { AFFS_SUPER_MAGIC, "affs", 0 },
1232 : { AFS_SUPER_MAGIC, "afs", 1 },
1233 : { AUFS_SUPER_MAGIC, "aufs", 1 },
1234 : { BEFS_SUPER_MAGIC, "befs", 0 },
1235 : { BDEVFS_MAGIC, "bdevfs", 0 },
1236 : { BFS_MAGIC, "bfs", 0 },
1237 : { BINFMTFS_MAGIC, "binfmt_misc", 0 },
1238 : { BTRFS_SUPER_MAGIC, "btrfs", 0 },
1239 : { CEPH_SUPER_MAGIC, "ceph", 1 },
1240 : { CGROUP_SUPER_MAGIC, "cgroupfs", 0 },
1241 : { CIFS_MAGIC_NUMBER, "cifs", 1 },
1242 : { CODA_SUPER_MAGIC, "coda", 1 },
1243 : { COH_SUPER_MAGIC, "coh", 0 },
1244 : { CRAMFS_MAGIC, "cramfs", 0 },
1245 : { DEBUGFS_MAGIC, "debugfs", 0 },
1246 : { DEVFS_SUPER_MAGIC, "devfs", 0 },
1247 : { DEVPTS_SUPER_MAGIC, "devpts", 0 },
1248 : { ECRYPTFS_SUPER_MAGIC, "ecryptfs", 0 },
1249 : { EFS_SUPER_MAGIC, "efs", 0 },
1250 : { EXT_SUPER_MAGIC, "ext", 0 },
1251 : { EXT4_SUPER_MAGIC, "ext4", 0 },
1252 : { EXT2_OLD_SUPER_MAGIC, "ext2", 0 },
1253 : { FAT_SUPER_MAGIC, "fat", 0 },
1254 : { FHGFS_SUPER_MAGIC, "fhgfs", 1 },
1255 : { FUSEBLK_SUPER_MAGIC, "fuseblk", 1 },
1256 : { FUSECTL_SUPER_MAGIC, "fusectl", 1 },
1257 : { FUTEXFS_SUPER_MAGIC, "futexfs", 0 },
1258 : { GFS_SUPER_MAGIC, "gfs/gfs2", 1 },
1259 : { GPFS_SUPER_MAGIC, "gpfs", 1 },
1260 : { HFS_SUPER_MAGIC, "hfs", 0 },
1261 : { HFSPLUS_SUPER_MAGIC, "hfsplus", 0 },
1262 : { HPFS_SUPER_MAGIC, "hpfs", 0 },
1263 : { HUGETLBFS_MAGIC, "hugetlbfs", 0 },
1264 : { MTD_INODE_FS_SUPER_MAGIC, "inodefs", 0 },
1265 : { INOTIFYFS_SUPER_MAGIC, "inotifyfs", 0 },
1266 : { ISOFS_SUPER_MAGIC, "isofs", 0 },
1267 : { ISOFS_R_WIN_SUPER_MAGIC, "isofs", 0 },
1268 : { ISOFS_WIN_SUPER_MAGIC, "isofs", 0 },
1269 : { JFFS_SUPER_MAGIC, "jffs", 0 },
1270 : { JFFS2_SUPER_MAGIC, "jffs2", 0 },
1271 : { JFS_SUPER_MAGIC, "jfs", 0 },
1272 : { KAFS_SUPER_MAGIC, "k-afs", 1 },
1273 : { LUSTRE_SUPER_MAGIC, "lustre", 1 },
1274 : { MINIX_SUPER_MAGIC, "minix", 0 },
1275 : { MINIX_SUPER_MAGIC2, "minix", 0 },
1276 : { MINIX2_SUPER_MAGIC, "minix2", 0 },
1277 : { MINIX2_SUPER_MAGIC2, "minix2", 0 },
1278 : { MINIX3_SUPER_MAGIC, "minix3", 0 },
1279 : { MQUEUE_MAGIC, "mqueue", 0 },
1280 : { MSDOS_SUPER_MAGIC, "msdos", 0 },
1281 : { NCP_SUPER_MAGIC, "novell", 1 },
1282 : { NFS_SUPER_MAGIC, "nfs", 1 },
1283 : { NFSD_SUPER_MAGIC, "nfsd", 1 },
1284 : { NILFS_SUPER_MAGIC, "nilfs", 0 },
1285 : { NTFS_SB_MAGIC, "ntfs", 0 },
1286 : { OPENPROM_SUPER_MAGIC, "openprom", 0 },
1287 : { OCFS2_SUPER_MAGIC, "ocfs2", 1 },
1288 : { PANFS_SUPER_MAGIC, "panfs", 1 },
1289 : { PIPEFS_MAGIC, "pipefs", 1 },
1290 : { PROC_SUPER_MAGIC, "proc", 0 },
1291 : { PSTOREFS_MAGIC, "pstorefs", 0 },
1292 : { QNX4_SUPER_MAGIC, "qnx4", 0 },
1293 : { QNX6_SUPER_MAGIC, "qnx6", 0 },
1294 : { RAMFS_MAGIC, "ramfs", 0 },
1295 : { REISERFS_SUPER_MAGIC, "reiserfs", 0 },
1296 : { ROMFS_MAGIC, "romfs", 0 },
1297 : { RPC_PIPEFS_SUPER_MAGIC, "rpc_pipefs", 0 },
1298 : { SECURITYFS_SUPER_MAGIC, "securityfs", 0 },
1299 : { SELINUX_MAGIC, "selinux", 0 },
1300 : { SMB_SUPER_MAGIC, "smb", 1 },
1301 : { SOCKFS_MAGIC, "sockfs", 0 },
1302 : { SQUASHFS_MAGIC, "squashfs", 0 },
1303 : { SYSFS_MAGIC, "sysfs", 0 },
1304 : { SYSV2_SUPER_MAGIC, "sysv2", 0 },
1305 : { SYSV4_SUPER_MAGIC, "sysv4", 0 },
1306 : { TMPFS_MAGIC, "tmpfs", 0 },
1307 : { UDF_SUPER_MAGIC, "udf", 0 },
1308 : { UFS_MAGIC, "ufs", 0 },
1309 : { UFS_BYTESWAPPED_SUPER_MAGIC, "ufs", 0 },
1310 : { USBDEVICE_SUPER_MAGIC, "usbdevfs", 0 },
1311 : { V9FS_MAGIC, "v9fs", 0 },
1312 : { VMHGFS_SUPER_MAGIC, "vmhgfs", 1 },
1313 : { VXFS_SUPER_MAGIC, "vxfs", 0 },
1314 : { VZFS_SUPER_MAGIC, "vzfs", 0 },
1315 : { XENFS_SUPER_MAGIC, "xenfs", 0 },
1316 : { XENIX_SUPER_MAGIC, "xenix", 0 },
1317 : { XFS_SUPER_MAGIC, "xfs", 0 },
1318 : { _XIAFS_SUPER_MAGIC, "xia", 0 },
1319 : { ZFS_SUPER_MAGIC, "zfs", 0 },
1320 : { 0 }
1321 : };
1322 :
1323 6710 : int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_hardlinks, uint64_t* total_space, uint64_t* free_space, char* fstype, size_t fstype_size, char* fslabel, size_t fslabel_size)
1324 : {
1325 : #if HAVE_STATFS
1326 : struct statfs st;
1327 :
1328 6710 : if (statfs(path, &st) != 0) {
1329 : char dir[PATH_MAX];
1330 : char* slash;
1331 :
1332 49 : if (errno != ENOENT) {
1333 : /* LCOV_EXCL_START */
1334 : return -1;
1335 : /* LCOV_EXCL_STOP */
1336 : }
1337 :
1338 : /*
1339 : * If it doesn't exist, we assume a file
1340 : * and we check for the containing dir
1341 : */
1342 49 : if (strlen(path) + 1 > sizeof(dir)) {
1343 : /* LCOV_EXCL_START */
1344 : errno = ENAMETOOLONG;
1345 : return -1;
1346 : /* LCOV_EXCL_STOP */
1347 : }
1348 :
1349 49 : strcpy(dir, path);
1350 :
1351 49 : slash = strrchr(dir, '/');
1352 49 : if (!slash) {
1353 : /* LCOV_EXCL_START */
1354 : return -1;
1355 : /* LCOV_EXCL_STOP */
1356 : }
1357 :
1358 49 : *slash = 0;
1359 49 : if (statfs(dir, &st) != 0) {
1360 : /* LCOV_EXCL_START */
1361 : return -1;
1362 : /* LCOV_EXCL_STOP */
1363 : }
1364 : }
1365 : #endif
1366 :
1367 : /* to get the fs type check "man stat" or "stat -f -t FILE" */
1368 6710 : if (has_persistent_inode) {
1369 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
1370 659 : switch (st.f_type) {
1371 0 : case FUSEBLK_SUPER_MAGIC : /* FUSE, "fuseblk" in the stat command */
1372 : case MSDOS_SUPER_MAGIC : /* VFAT, "msdos" in the stat command */
1373 0 : *has_persistent_inode = 0;
1374 0 : break;
1375 659 : default :
1376 : /* by default assume yes */
1377 659 : *has_persistent_inode = 1;
1378 659 : break;
1379 : }
1380 : #else
1381 : /* in Unix inodes are persistent by default */
1382 : *has_persistent_inode = 1;
1383 : #endif
1384 : }
1385 :
1386 6710 : if (has_syncronized_hardlinks) {
1387 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
1388 659 : switch (st.f_type) {
1389 0 : case NTFS_SB_MAGIC : /* NTFS */
1390 : case MSDOS_SUPER_MAGIC : /* VFAT, "msdos" in the stat command */
1391 0 : *has_syncronized_hardlinks = 0;
1392 0 : break;
1393 659 : default :
1394 : /* by default assume yes */
1395 659 : *has_syncronized_hardlinks = 1;
1396 659 : break;
1397 : }
1398 : #else
1399 : /* in Unix hardlinks share the same metadata by default */
1400 : *has_syncronized_hardlinks = 1;
1401 : #endif
1402 : }
1403 :
1404 6710 : if (total_space) {
1405 : #if HAVE_STATFS
1406 6051 : *total_space = st.f_bsize * (uint64_t)st.f_blocks;
1407 : #else
1408 : *total_space = 0;
1409 : #endif
1410 : }
1411 :
1412 6710 : if (free_space) {
1413 : #if HAVE_STATFS
1414 6051 : *free_space = st.f_bsize * (uint64_t)st.f_bfree;
1415 : #else
1416 : *free_space = 0;
1417 : #endif
1418 : }
1419 :
1420 6710 : const char* ptype = 0;
1421 :
1422 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_FSTYPENAME
1423 : /* get the filesystem type directly from the struct (Mac OS X) */
1424 : ptype = st.f_fstypename;
1425 : #elif HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
1426 : /*
1427 : * Get the filesystem type from f_type (Linux)
1428 : * from: https://github.com/influxdata/gopsutil/blob/master/disk/disk_linux.go
1429 : */
1430 146411 : for (int i = 0; FILESYSTEMS[i].id != 0; ++i) {
1431 146411 : if (st.f_type == FILESYSTEMS[i].id) {
1432 6710 : ptype = FILESYSTEMS[i].name;
1433 6710 : break;
1434 : }
1435 : }
1436 : #endif
1437 :
1438 6710 : if (fstype) {
1439 6051 : fstype[0] = 0;
1440 6051 : if (ptype)
1441 6051 : snprintf(fstype, fstype_size, "%s", ptype);
1442 : }
1443 :
1444 6710 : if (fslabel) {
1445 6051 : fslabel[0] = 0;
1446 : #if HAVE_BLKID
1447 : struct stat fst;
1448 6051 : if (stat(path, &fst) == 0)
1449 6002 : devuuid_label(fst.st_dev, fslabel, fslabel_size);
1450 : #else
1451 : (void)fslabel_size;
1452 : #endif
1453 : }
1454 :
1455 6710 : return 0;
1456 : }
1457 :
1458 48 : int fssnapshot(const char* path, char* root, size_t root_size)
1459 : {
1460 : #if HAVE_LINUX_DEVICE
1461 : struct statfs sfs;
1462 : struct stat st;
1463 : char current_path[PATH_MAX];
1464 :
1465 48 : pathcpy(current_path, sizeof(current_path), path);
1466 :
1467 48 : if (statfs(current_path, &sfs) != 0) {
1468 : /* LCOV_EXCL_START */
1469 : log_error(errno, "Error stating filesystem '%s'. %s.\n", current_path, strerror(errno));
1470 : return -1;
1471 : /* LCOV_EXCL_STOP */
1472 : }
1473 :
1474 : /* only btrfs */
1475 48 : if (sfs.f_type != BTRFS_SUPER_MAGIC)
1476 0 : return -1;
1477 :
1478 : /* walk up the directory tree to find the subvolume root (Inode 256) */
1479 : while (1) {
1480 48 : if (stat(current_path, &st) != 0) {
1481 : /* LCOV_EXCL_START */
1482 : log_error(errno, "Error stating '%s'. %s.\n", current_path, strerror(errno));
1483 : return -1;
1484 : /* LCOV_EXCL_STOP */
1485 : }
1486 :
1487 : /* btrfs reserved inode 256 for subvolume roots */
1488 48 : if (st.st_ino == 256) {
1489 : /* copy the subvol root */
1490 48 : pathcpy(root, root_size, current_path);
1491 48 : return 0;
1492 : }
1493 :
1494 : /* move to parent directory */
1495 0 : pathup(current_path);
1496 :
1497 0 : if (current_path[0] == 0) {
1498 : /* LCOV_EXCL_START */
1499 : log_error(ENOENT, "No subvolume root found for '%s'. %s.\n", path, strerror(errno));
1500 : return -1;
1501 : /* LCOV_EXCL_STOP */
1502 : }
1503 : }
1504 : #else
1505 : (void)path;
1506 : (void)root;
1507 : (void)root_size;
1508 : return -1;
1509 : #endif
1510 : }
1511 :
1512 30 : int fssnapshot_create(const char* source, const char* parent_dir, const char* name)
1513 : {
1514 : #if HAVE_LINUX_DEVICE
1515 : struct btrfs_ioctl_vol_args_v2 args;
1516 : int fd_source;
1517 : int fd_dest_parent;
1518 : int ret;
1519 :
1520 : /* open the source subvolume to get a file descriptor */
1521 30 : fd_source = open(source, O_RDONLY | O_DIRECTORY);
1522 30 : if (fd_source < 0) {
1523 : /* LCOV_EXCL_START */
1524 : return -1;
1525 : /* LCOV_EXCL_STOP */
1526 : }
1527 :
1528 30 : fd_dest_parent = open(parent_dir, O_RDONLY | O_DIRECTORY);
1529 30 : if (fd_dest_parent < 0) {
1530 : /* LCOV_EXCL_START */
1531 : close(fd_source);
1532 : return -1;
1533 : /* LCOV_EXCL_STOP */
1534 : }
1535 :
1536 30 : memset(&args, 0, sizeof(args));
1537 30 : args.fd = fd_source;
1538 30 : args.flags = BTRFS_SUBVOL_RDONLY;
1539 30 : pathcpy(args.name, BTRFS_SUBVOL_NAME_MAX, name);
1540 :
1541 : /* issue the snapshot command to the PARENT directory of the destination */
1542 30 : ret = ioctl(fd_dest_parent, BTRFS_IOC_SNAP_CREATE_V2, &args);
1543 30 : if (ret < 0) {
1544 : /* LCOV_EXCL_START */
1545 : close(fd_source);
1546 : close(fd_dest_parent);
1547 : return -1;
1548 : /* LCOV_EXCL_STOP */
1549 : }
1550 :
1551 30 : close(fd_source);
1552 30 : close(fd_dest_parent);
1553 :
1554 30 : return 0;
1555 : #else
1556 : (void)source;
1557 : (void)parent_dir;
1558 : (void)name;
1559 : return -1;
1560 : #endif
1561 : }
1562 :
1563 48 : int fssnapshot_delete(const char* parent_dir, const char* name)
1564 : {
1565 : #if HAVE_LINUX_DEVICE
1566 : struct btrfs_ioctl_vol_args args;
1567 : int fd_parent;
1568 : int ret;
1569 :
1570 48 : fd_parent = open(parent_dir, O_RDONLY | O_DIRECTORY);
1571 48 : if (fd_parent < 0) {
1572 : /* LCOV_EXCL_START */
1573 : return -1;
1574 : /* LCOV_EXCL_STOP */
1575 : }
1576 :
1577 48 : memset(&args, 0, sizeof(args));
1578 48 : pathcpy(args.name, BTRFS_PATH_NAME_MAX, name);
1579 :
1580 48 : ret = ioctl(fd_parent, BTRFS_IOC_SNAP_DESTROY, &args);
1581 :
1582 48 : if (ret < 0 && errno != ENOENT) {
1583 : /* LCOV_EXCL_START */
1584 : close(fd_parent);
1585 : return -1;
1586 : /* LCOV_EXCL_STOP */
1587 : }
1588 :
1589 48 : close(fd_parent);
1590 48 : return 0;
1591 : #else
1592 : (void)parent_dir;
1593 : (void)name;
1594 : return -1;
1595 : #endif
1596 : }
1597 :
1598 18 : int fssnapshot_rename(const char* parent_dir, const char* old_name, const char* new_name)
1599 : {
1600 : #if HAVE_LINUX_DEVICE
1601 : char old_path[PATH_MAX];
1602 : char new_path[PATH_MAX];
1603 :
1604 18 : pathcpy(old_path, sizeof(old_path), parent_dir);
1605 18 : pathcatc(old_path, sizeof(old_path), '/');
1606 18 : pathcat(old_path, sizeof(old_path), old_name);
1607 18 : pathcpy(new_path, sizeof(new_path), parent_dir);
1608 18 : pathcatc(new_path, sizeof(new_path), '/');
1609 18 : pathcat(new_path, sizeof(new_path), new_name);
1610 :
1611 18 : if (rename(old_path, new_path) < 0) {
1612 : /* LCOV_EXCL_START */
1613 : return -1;
1614 : /* LCOV_EXCL_STOP */
1615 : }
1616 :
1617 18 : return 0;
1618 : #else
1619 : (void)parent_dir;
1620 : (void)old_name;
1621 : (void)new_name;
1622 : return -1;
1623 : #endif
1624 : }
1625 :
1626 4133031 : uint64_t os_tick(void)
1627 : {
1628 : #if HAVE_MACH_ABSOLUTE_TIME
1629 : /* for Mac OS X */
1630 : return mach_absolute_time();
1631 : #elif HAVE_CLOCK_GETTIME && (defined(CLOCK_MONOTONIC) || defined(CLOCK_MONOTONIC_RAW))
1632 : /* for Linux */
1633 : struct timespec tv;
1634 :
1635 : /* nanosecond precision with clock_gettime() */
1636 : #if defined(CLOCK_MONOTONIC_RAW)
1637 4133031 : if (clock_gettime(CLOCK_MONOTONIC_RAW, &tv) != 0) {
1638 : #else
1639 : if (clock_gettime(CLOCK_MONOTONIC, &tv) != 0) {
1640 : #endif
1641 : /* LCOV_EXCL_START */
1642 : return 0;
1643 : /* LCOV_EXCL_STOP */
1644 : }
1645 :
1646 4133031 : return tv.tv_sec * 1000000000ULL + tv.tv_nsec;
1647 : #else
1648 : /* other platforms */
1649 : struct timeval tv;
1650 :
1651 : /* microsecond precision with gettimeofday() */
1652 : if (gettimeofday(&tv, 0) != 0) {
1653 : /* LCOV_EXCL_START */
1654 : return 0;
1655 : /* LCOV_EXCL_STOP */
1656 : }
1657 :
1658 : return tv.tv_sec * 1000000ULL + tv.tv_usec;
1659 : #endif
1660 : }
1661 :
1662 3894 : uint64_t os_tick_ms(void)
1663 : {
1664 : struct timeval tv;
1665 :
1666 3894 : if (gettimeofday(&tv, 0) != 0) {
1667 : /* LCOV_EXCL_START */
1668 : return 0;
1669 : /* LCOV_EXCL_STOP */
1670 : }
1671 :
1672 3894 : return tv.tv_sec * 1000ULL + tv.tv_usec / 1000;
1673 : }
1674 :
1675 332 : int randomize(void* ptr, size_t size)
1676 : {
1677 : int f;
1678 : ssize_t ret;
1679 :
1680 332 : f = open("/dev/urandom", O_RDONLY);
1681 332 : if (f == -1) {
1682 : /* LCOV_EXCL_START */
1683 : return -1;
1684 : /* LCOV_EXCL_STOP */
1685 : }
1686 :
1687 332 : ret = read(f, ptr, size);
1688 332 : if (ret < 0 || (size_t)ret != size) {
1689 : /* LCOV_EXCL_START */
1690 : close(f);
1691 : return -1;
1692 : /* LCOV_EXCL_STOP */
1693 : }
1694 :
1695 332 : if (close(f) != 0) {
1696 : /* LCOV_EXCL_START */
1697 : return -1;
1698 : /* LCOV_EXCL_STOP */
1699 : }
1700 :
1701 332 : return 0;
1702 : }
1703 :
1704 : /**
1705 : * Read a file extracting the contained device number in %u:%u format.
1706 : * Return 0 on error.
1707 : */
1708 : #if HAVE_LINUX_DEVICE
1709 310 : static dev_t devread(const char* path)
1710 : {
1711 : int f;
1712 : int ret;
1713 : int len;
1714 : char buf[64];
1715 : char* e;
1716 : unsigned ma;
1717 : unsigned mi;
1718 :
1719 310 : f = open(path, O_RDONLY);
1720 310 : if (f == -1) {
1721 : /* LCOV_EXCL_START */
1722 : log_fatal(errno, "Failed to open '%s'.\n", path);
1723 : return 0;
1724 : /* LCOV_EXCL_STOP */
1725 : }
1726 :
1727 310 : len = read(f, buf, sizeof(buf));
1728 310 : if (len < 0) {
1729 : /* LCOV_EXCL_START */
1730 : close(f);
1731 : log_fatal(errno, "Failed to read '%s'.\n", path);
1732 : return 0;
1733 : /* LCOV_EXCL_STOP */
1734 : }
1735 310 : if (len == sizeof(buf)) {
1736 : /* LCOV_EXCL_START */
1737 : close(f);
1738 : log_fatal(EEXTERNAL, "Too long read '%s'.\n", path);
1739 : return 0;
1740 : /* LCOV_EXCL_STOP */
1741 : }
1742 :
1743 310 : ret = close(f);
1744 310 : if (ret != 0) {
1745 : /* LCOV_EXCL_START */
1746 : log_fatal(errno, "Failed to close '%s'.\n", path);
1747 : return 0;
1748 : /* LCOV_EXCL_STOP */
1749 : }
1750 :
1751 310 : buf[len] = 0;
1752 :
1753 310 : ma = strtoul(buf, &e, 10);
1754 310 : if (*e != ':') {
1755 : /* LCOV_EXCL_START */
1756 : log_fatal(EEXTERNAL, "Invalid format in '%s' for '%s'.\n", path, buf);
1757 : return 0;
1758 : /* LCOV_EXCL_STOP */
1759 : }
1760 :
1761 310 : mi = strtoul(e + 1, &e, 10);
1762 310 : if (*e != 0 && !isspace(*e)) {
1763 : /* LCOV_EXCL_START */
1764 : log_fatal(EEXTERNAL, "Invalid format in '%s' for '%s'.\n", path, buf);
1765 : return 0;
1766 : /* LCOV_EXCL_STOP */
1767 : }
1768 :
1769 310 : return makedev(ma, mi);
1770 : }
1771 : #endif
1772 :
1773 : /**
1774 : * Read a device tree filling the specified list of disk_t entries.
1775 : */
1776 : #if HAVE_LINUX_DEVICE
1777 310 : static int devtree(devinfo_t* parent, dev_t device, tommy_list* list)
1778 : {
1779 : char path[PATH_MAX];
1780 : DIR* d;
1781 310 : int slaves = 0;
1782 :
1783 310 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/slaves", major(device), minor(device));
1784 :
1785 : /* check if there is a slaves list */
1786 310 : d = opendir(path);
1787 310 : if (d != 0) {
1788 : struct dirent* dd;
1789 :
1790 612 : while ((dd = readdir(d)) != 0) {
1791 459 : if (dd->d_name[0] != '.') {
1792 : dev_t subdev;
1793 :
1794 : /* for each slave, expand the full potential tree */
1795 153 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/slaves/%s/dev", major(device), minor(device), dd->d_name);
1796 :
1797 153 : subdev = devread(path);
1798 153 : if (!subdev) {
1799 : /* LCOV_EXCL_START */
1800 : closedir(d);
1801 : return -1;
1802 : /* LCOV_EXCL_STOP */
1803 : }
1804 :
1805 153 : if (devtree(parent, subdev, list) != 0) {
1806 : /* LCOV_EXCL_START */
1807 : closedir(d);
1808 : return -1;
1809 : /* LCOV_EXCL_STOP */
1810 : }
1811 :
1812 153 : ++slaves;
1813 : }
1814 : }
1815 :
1816 153 : closedir(d);
1817 : }
1818 :
1819 : /* if no slaves found */
1820 310 : if (!slaves) {
1821 : /* this is a raw device */
1822 : devinfo_t* devinfo;
1823 :
1824 : /* check if it's a real device */
1825 157 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device", major(device), minor(device));
1826 157 : if (access(path, F_OK) != 0) {
1827 : /* get the parent device */
1828 157 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/../dev", major(device), minor(device));
1829 :
1830 157 : device = devread(path);
1831 157 : if (!device) {
1832 : /* LCOV_EXCL_START */
1833 : return -1;
1834 : /* LCOV_EXCL_STOP */
1835 : }
1836 : }
1837 :
1838 : /* get the device file */
1839 157 : if (devresolve(device, path, sizeof(path)) != 0) {
1840 : /* LCOV_EXCL_START */
1841 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1842 : return -1;
1843 : /* LCOV_EXCL_STOP */
1844 : }
1845 :
1846 157 : devinfo = calloc_nofail(1, sizeof(devinfo_t));
1847 :
1848 157 : devinfo->device = device;
1849 157 : pathcpy(devinfo->name, sizeof(devinfo->name), parent->name);
1850 157 : pathcpy(devinfo->smartctl, sizeof(devinfo->smartctl), parent->smartctl);
1851 157 : memcpy(devinfo->smartignore, parent->smartignore, sizeof(devinfo->smartignore));
1852 157 : pathcpy(devinfo->file, sizeof(devinfo->file), path);
1853 157 : devinfo->parent = parent;
1854 :
1855 : /* insert in the list */
1856 157 : tommy_list_insert_tail(list, &devinfo->node, devinfo);
1857 : }
1858 :
1859 310 : return 0;
1860 : }
1861 : #endif
1862 :
1863 : /**
1864 : * Compute disk usage by aggregating access statistics.
1865 : */
1866 : #if HAVE_LINUX_DEVICE
1867 157 : static int devstat(dev_t device, uint64_t* count)
1868 : {
1869 : char path[PATH_MAX];
1870 : char buf[512];
1871 : int token;
1872 : int ret;
1873 : char* i;
1874 :
1875 157 : *count = 0;
1876 :
1877 157 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/stat", major(device), minor(device));
1878 :
1879 157 : ret = sysread(path, buf, sizeof(buf));
1880 157 : if (ret < 0) {
1881 : /* LCOV_EXCL_START */
1882 : log_fatal(errno, "Failed to read '%s'.\n", path);
1883 : return -1;
1884 : /* LCOV_EXCL_STOP */
1885 : }
1886 157 : if ((size_t)ret + 1 > sizeof(buf)) {
1887 : /* LCOV_EXCL_START */
1888 : log_fatal(EEXTERNAL, "Too long read '%s'.\n", path);
1889 : return -1;
1890 : /* LCOV_EXCL_STOP */
1891 : }
1892 :
1893 : /* ending 0 */
1894 157 : buf[ret] = 0;
1895 :
1896 157 : i = buf;
1897 157 : token = 1; /* token number */
1898 785 : while (*i) {
1899 : char* n;
1900 : char* e;
1901 : unsigned long long v;
1902 :
1903 : /* skip spaces */
1904 2490 : while (*i && isspace(*i))
1905 1705 : ++i;
1906 :
1907 : /* read digits */
1908 785 : n = i;
1909 5652 : while (*i && isdigit(*i))
1910 4867 : ++i;
1911 :
1912 785 : if (i == n) /* if no digit, abort */
1913 157 : break;
1914 785 : if (*i == 0 || !isspace(*i)) /* if no space, abort */
1915 : break;
1916 785 : *i++ = 0; /* put a terminator */
1917 :
1918 785 : v = strtoull(n, &e, 10);
1919 785 : if (*e != 0) {
1920 : /* LCOV_EXCL_START */
1921 : break;
1922 : /* LCOV_EXCL_STOP */
1923 : }
1924 :
1925 : /* sum reads and writes completed */
1926 785 : if (token == 1 || token == 5) {
1927 314 : *count += v;
1928 314 : if (token == 5)
1929 157 : break; /* stop here */
1930 : }
1931 :
1932 628 : ++token;
1933 : }
1934 :
1935 157 : return 0;
1936 : }
1937 : #endif
1938 :
1939 : /**
1940 : * Get SMART attributes.
1941 : */
1942 : #if HAVE_LINUX_DEVICE
1943 26 : static int devsmart(dev_t device, const char* name, const char* smartctl, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* interface)
1944 : {
1945 : char cmd[PATH_MAX + 64];
1946 : char file[PATH_MAX];
1947 : FILE* f;
1948 : int ret;
1949 : const char* x;
1950 :
1951 26 : x = find_smartctl();
1952 26 : if (!x) {
1953 : /* LCOV_EXCL_START */
1954 : log_fatal(EEXTERNAL, "Cannot find smartctl.\n");
1955 : return -1;
1956 : /* LCOV_EXCL_STOP */
1957 : }
1958 :
1959 26 : if (devresolve(device, file, sizeof(file)) != 0) {
1960 : /* LCOV_EXCL_START */
1961 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1962 : return -1;
1963 : /* LCOV_EXCL_STOP */
1964 : }
1965 :
1966 : /* if there is a custom smartctl command */
1967 26 : if (smartctl[0]) {
1968 : char option[PATH_MAX];
1969 0 : snprintf(option, sizeof(option), smartctl, file);
1970 0 : snprintf(cmd, sizeof(cmd), "%s -a %s", x, option);
1971 : } else {
1972 26 : snprintf(cmd, sizeof(cmd), "%s -a %s", x, file);
1973 : }
1974 :
1975 26 : log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
1976 :
1977 26 : f = popen(cmd, "r");
1978 26 : if (!f) {
1979 : /* LCOV_EXCL_START */
1980 : log_tag("device:%s:%s:shell\n", file, name);
1981 : log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd);
1982 : return -1;
1983 : /* LCOV_EXCL_STOP */
1984 : }
1985 :
1986 26 : if (smartctl_attribute(f, file, name, smart, info, serial, family, model, interface) != 0) {
1987 : /* LCOV_EXCL_START */
1988 : pclose(f);
1989 : log_tag("device:%s:%s:shell\n", file, name);
1990 : return -1;
1991 : /* LCOV_EXCL_STOP */
1992 : }
1993 :
1994 26 : ret = pclose(f);
1995 :
1996 26 : log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
1997 :
1998 26 : if (!WIFEXITED(ret)) {
1999 : /* LCOV_EXCL_START */
2000 : log_tag("device:%s:%s:abort\n", file, name);
2001 : log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd);
2002 : return -1;
2003 : /* LCOV_EXCL_STOP */
2004 : }
2005 26 : if (WEXITSTATUS(ret) == 127) {
2006 : /* LCOV_EXCL_START */
2007 : log_tag("device:%s:%s:shell\n", file, name);
2008 : log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd);
2009 : return -1;
2010 : /* LCOV_EXCL_STOP */
2011 : }
2012 :
2013 : /* store the return smartctl return value */
2014 26 : smart[SMART_FLAGS].raw = WEXITSTATUS(ret);
2015 :
2016 26 : return 0;
2017 : }
2018 : #endif
2019 :
2020 : /**
2021 : * Get device attributes.
2022 : */
2023 : #if HAVE_LINUX_DEVICE
2024 54 : static void devattr(dev_t device, uint64_t* info, char* serial, char* family, char* model, char* interface)
2025 : {
2026 : char path[PATH_MAX];
2027 : char buf[512];
2028 : int ret;
2029 :
2030 : (void)family; /* not available, smartctl uses an internal database to get it */
2031 :
2032 54 : if (info[INFO_SIZE] == SMART_UNASSIGNED) {
2033 12 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/size", major(device), minor(device));
2034 12 : if (sysattr(path, buf, sizeof(buf)) == 0) {
2035 : char* e;
2036 : uint64_t v;
2037 12 : v = strtoul(buf, &e, 10);
2038 12 : if (*e == 0)
2039 12 : info[INFO_SIZE] = v * 512;
2040 : }
2041 : }
2042 :
2043 54 : if (info[INFO_ROTATION_RATE] == SMART_UNASSIGNED) {
2044 12 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/queue/rotational", major(device), minor(device));
2045 12 : if (sysattr(path, buf, sizeof(buf)) == 0) {
2046 : char* e;
2047 : uint64_t v;
2048 12 : v = strtoul(buf, &e, 10);
2049 12 : if (*e == 0)
2050 12 : info[INFO_ROTATION_RATE] = v;
2051 : }
2052 : }
2053 :
2054 54 : if (*model == 0) {
2055 12 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device/model", major(device), minor(device));
2056 12 : if (sysattr(path, buf, sizeof(buf)) == 0) {
2057 12 : if (buf[0] != 0)
2058 12 : pathcpy(model, SMART_MAX, buf);
2059 : }
2060 : }
2061 :
2062 54 : if (*serial == 0) {
2063 12 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device/serial", major(device), minor(device));
2064 12 : if (sysattr(path, buf, sizeof(buf)) == 0) {
2065 0 : if (buf[0] != 0)
2066 0 : pathcpy(serial, SMART_MAX, buf);
2067 : }
2068 : }
2069 :
2070 54 : if (*serial == 0) {
2071 12 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device/vpd_pg80", major(device), minor(device));
2072 12 : if (sysattr_vpd_pg80(path, buf, sizeof(buf)) == 0) {
2073 12 : if (buf[0] != 0)
2074 12 : pathcpy(serial, SMART_MAX, buf);
2075 : }
2076 : }
2077 :
2078 : /* always override interface if it's usb */
2079 54 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u", major(device), minor(device));
2080 54 : ret = readlink(path, buf, sizeof(buf));
2081 54 : if (ret > 0 && (unsigned)ret < sizeof(buf)) {
2082 54 : buf[ret] = 0;
2083 54 : if (strstr(buf, "usb") != 0)
2084 0 : strcpy(interface, "USB");
2085 : }
2086 54 : }
2087 : #endif
2088 :
2089 : /**
2090 : * Get POWER state.
2091 : */
2092 : #if HAVE_LINUX_DEVICE
2093 40 : static int devprobe(dev_t device, const char* name, const char* smartctl, int* power, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* interface)
2094 : {
2095 : char cmd[PATH_MAX + 64];
2096 : char file[PATH_MAX];
2097 : FILE* f;
2098 : int ret;
2099 : const char* x;
2100 :
2101 40 : x = find_smartctl();
2102 40 : if (!x) {
2103 : /* LCOV_EXCL_START */
2104 : log_fatal(EEXTERNAL, "Cannot find smartctl.\n");
2105 : return -1;
2106 : /* LCOV_EXCL_STOP */
2107 : }
2108 :
2109 40 : if (devresolve(device, file, sizeof(file)) != 0) {
2110 : /* LCOV_EXCL_START */
2111 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device));
2112 : return -1;
2113 : /* LCOV_EXCL_STOP */
2114 : }
2115 :
2116 : /* if there is a custom smartctl command */
2117 40 : if (smartctl[0]) {
2118 : char option[PATH_MAX];
2119 0 : snprintf(option, sizeof(option), smartctl, file);
2120 0 : snprintf(cmd, sizeof(cmd), "%s -n standby,3 -a %s", x, option);
2121 : } else {
2122 40 : snprintf(cmd, sizeof(cmd), "%s -n standby,3 -a %s", x, file);
2123 : }
2124 :
2125 40 : log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
2126 :
2127 40 : f = popen(cmd, "r");
2128 40 : if (!f) {
2129 : /* LCOV_EXCL_START */
2130 : log_tag("device:%s:%s:shell\n", file, name);
2131 : log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd);
2132 : return -1;
2133 : /* LCOV_EXCL_STOP */
2134 : }
2135 :
2136 40 : if (smartctl_attribute(f, file, name, smart, info, serial, family, model, interface) != 0) {
2137 : /* LCOV_EXCL_START */
2138 : pclose(f);
2139 : log_tag("device:%s:%s:shell\n", file, name);
2140 : return -1;
2141 : /* LCOV_EXCL_STOP */
2142 : }
2143 :
2144 40 : ret = pclose(f);
2145 :
2146 40 : log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
2147 :
2148 40 : if (!WIFEXITED(ret)) {
2149 : /* LCOV_EXCL_START */
2150 : log_tag("device:%s:%s:abort\n", file, name);
2151 : log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd);
2152 : return -1;
2153 : /* LCOV_EXCL_STOP */
2154 : }
2155 40 : if (WEXITSTATUS(ret) == 127) {
2156 : /* LCOV_EXCL_START */
2157 : log_tag("device:%s:%s:shell\n", file, name);
2158 : log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd);
2159 : return -1;
2160 : /* LCOV_EXCL_STOP */
2161 : }
2162 :
2163 40 : if (WEXITSTATUS(ret) == 3) {
2164 24 : log_tag("attr:%s:%s:power:standby\n", file, name);
2165 24 : *power = POWER_STANDBY;
2166 : } else {
2167 16 : log_tag("attr:%s:%s:power:active\n", file, name);
2168 16 : *power = POWER_ACTIVE;
2169 :
2170 : /* store the smartctl return value */
2171 16 : if (smart)
2172 16 : smart[SMART_FLAGS].raw = WEXITSTATUS(ret);
2173 : }
2174 :
2175 40 : return 0;
2176 : }
2177 : #endif
2178 :
2179 : /**
2180 : * Spin down a specific device.
2181 : */
2182 : #if HAVE_LINUX_DEVICE
2183 17 : static int devdown(dev_t device, const char* name, const char* smartctl)
2184 : {
2185 : char cmd[PATH_MAX + 64];
2186 : char file[PATH_MAX];
2187 : FILE* f;
2188 : int ret;
2189 : const char* x;
2190 :
2191 17 : x = find_smartctl();
2192 17 : if (!x) {
2193 : /* LCOV_EXCL_START */
2194 : log_fatal(EEXTERNAL, "Cannot find smartctl.\n");
2195 : return -1;
2196 : /* LCOV_EXCL_STOP */
2197 : }
2198 :
2199 17 : if (devresolve(device, file, sizeof(file)) != 0) {
2200 : /* LCOV_EXCL_START */
2201 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device));
2202 : return -1;
2203 : /* LCOV_EXCL_STOP */
2204 : }
2205 :
2206 : /* if there is a custom smartctl command */
2207 17 : if (smartctl[0]) {
2208 : char option[PATH_MAX];
2209 5 : snprintf(option, sizeof(option), smartctl, file);
2210 5 : snprintf(cmd, sizeof(cmd), "%s -s standby,now %s", x, option);
2211 : } else {
2212 12 : snprintf(cmd, sizeof(cmd), "%s -s standby,now %s", x, file);
2213 : }
2214 :
2215 17 : log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
2216 :
2217 17 : f = popen(cmd, "r");
2218 17 : if (!f) {
2219 : /* LCOV_EXCL_START */
2220 : log_tag("device:%s:%s:shell\n", file, name);
2221 : log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd);
2222 : return -1;
2223 : /* LCOV_EXCL_STOP */
2224 : }
2225 :
2226 17 : if (smartctl_flush(f, file, name) != 0) {
2227 : /* LCOV_EXCL_START */
2228 : pclose(f);
2229 : log_tag("device:%s:%s:shell\n", file, name);
2230 : return -1;
2231 : /* LCOV_EXCL_STOP */
2232 : }
2233 :
2234 17 : ret = pclose(f);
2235 :
2236 17 : log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
2237 :
2238 17 : if (!WIFEXITED(ret)) {
2239 : /* LCOV_EXCL_START */
2240 : log_tag("device:%s:%s:abort\n", file, name);
2241 : log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd);
2242 : return -1;
2243 : /* LCOV_EXCL_STOP */
2244 : }
2245 17 : if (WEXITSTATUS(ret) == 127) {
2246 : /* LCOV_EXCL_START */
2247 : log_tag("device:%s:%s:shell\n", file, name);
2248 : log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd);
2249 : return -1;
2250 : /* LCOV_EXCL_STOP */
2251 : }
2252 17 : if (WEXITSTATUS(ret) != 0) {
2253 : /* LCOV_EXCL_START */
2254 : log_tag("device:%s:%s:exit:%d\n", file, name, WEXITSTATUS(ret));
2255 : log_fatal(EEXTERNAL, "Failed to run '%s' with return code %xh.\n", cmd, WEXITSTATUS(ret));
2256 : return -1;
2257 : /* LCOV_EXCL_STOP */
2258 : }
2259 :
2260 12 : log_tag("attr:%s:%s:power:down\n", file, name);
2261 :
2262 12 : return 0;
2263 : }
2264 : #endif
2265 :
2266 : /**
2267 : * Spin down a specific device if it's up.
2268 : */
2269 : #if HAVE_LINUX_DEVICE
2270 12 : static int devdownifup(dev_t device, const char* name, const char* smartctl, int* power)
2271 : {
2272 12 : *power = POWER_UNKNOWN;
2273 :
2274 12 : if (devprobe(device, name, smartctl, power, 0, 0, 0, 0, 0, 0) != 0)
2275 0 : return -1;
2276 :
2277 12 : if (*power == POWER_ACTIVE)
2278 0 : return devdown(device, name, smartctl);
2279 :
2280 12 : return 0;
2281 : }
2282 : #endif
2283 :
2284 : /**
2285 : * Spin up a specific device.
2286 : */
2287 : #if HAVE_LINUX_DEVICE
2288 42 : static int devup(dev_t device, const char* name)
2289 : {
2290 : char file[PATH_MAX];
2291 : int ret;
2292 : int f;
2293 : void* buf;
2294 : uint64_t size;
2295 : uint64_t offset;
2296 :
2297 42 : if (devresolve(device, file, sizeof(file)) != 0) {
2298 : /* LCOV_EXCL_START */
2299 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device));
2300 : return -1;
2301 : /* LCOV_EXCL_STOP */
2302 : }
2303 :
2304 : /* O_DIRECT requires memory aligned to the block size */
2305 42 : if (posix_memalign(&buf, 4096, 4096) != 0) {
2306 : /* LCOV_EXCL_START */
2307 : log_fatal(errno, "Failed to allocate aligned memory for device '%u:%u'.\n", major(device), minor(device));
2308 : return -1;
2309 : /* LCOV_EXCL_STOP */
2310 : }
2311 :
2312 42 : f = open(file, O_RDONLY | O_DIRECT);
2313 42 : if (f < 0) {
2314 : /* LCOV_EXCL_START */
2315 : free(buf);
2316 : log_tag("device:%s:%s:error:%d\n", file, name, errno);
2317 : log_fatal(errno, "Failed to open device '%u:%u'.\n", major(device), minor(device));
2318 : return -1;
2319 : /* LCOV_EXCL_STOP */
2320 : }
2321 :
2322 12 : if (ioctl(f, BLKGETSIZE64, &size) < 0) {
2323 : /* LCOV_EXCL_START */
2324 : close(f);
2325 : free(buf);
2326 : log_tag("device:%s:%s:error:%d\n", file, name, errno);
2327 : log_fatal(errno, "Failed to get device size '%u:%u'.\n", major(device), minor(device));
2328 : return -1;
2329 : /* LCOV_EXCL_STOP */
2330 : }
2331 :
2332 : /* select a random offset */
2333 12 : offset = (random_u64() % (size / 4096)) * 4096;
2334 :
2335 : #if HAVE_POSIX_FADVISE
2336 : /* clear cache */
2337 12 : ret = posix_fadvise_wrapper(f, offset, 4096, POSIX_FADV_DONTNEED);
2338 12 : if (ret == ENOSYS) {
2339 : /* call is not supported */
2340 0 : ret = 0;
2341 : }
2342 12 : if (ret != 0) {
2343 : /* LCOV_EXCL_START */
2344 : close(f);
2345 : free(buf);
2346 : log_tag("device:%s:%s:error:%d\n", file, name, errno);
2347 : log_fatal(errno, "Failed to advise device '%u:%u'.\n", major(device), minor(device));
2348 : return -1;
2349 : /* LCOV_EXCL_STOP */
2350 : }
2351 : #endif
2352 :
2353 12 : ret = pread(f, buf, 4096, offset);
2354 12 : if (ret < 0) {
2355 : /* LCOV_EXCL_START */
2356 : close(f);
2357 : free(buf);
2358 : log_tag("device:%s:%s:error:%d\n", file, name, errno);
2359 : log_fatal(errno, "Failed to read device '%u:%u'.\n", major(device), minor(device));
2360 : return -1;
2361 : /* LCOV_EXCL_STOP */
2362 : }
2363 :
2364 12 : ret = close(f);
2365 12 : if (ret < 0) {
2366 : /* LCOV_EXCL_START */
2367 : free(buf);
2368 : log_tag("device:%s:%s:error:%d\n", file, name, errno);
2369 : log_fatal(errno, "Failed to close device '%u:%u'.\n", major(device), minor(device));
2370 : return -1;
2371 : /* LCOV_EXCL_STOP */
2372 : }
2373 :
2374 12 : log_tag("attr:%s:%s:power:up\n", file, name);
2375 :
2376 12 : free(buf);
2377 12 : return 0;
2378 : }
2379 : #endif
2380 :
2381 : /**
2382 : * Thread for spinning up.
2383 : *
2384 : * Note that filling up the devinfo object is done inside this thread,
2385 : * to avoid to block the main thread if the device need to be spin up
2386 : * to handle stat/resolve requests.
2387 : */
2388 42 : static void* thread_spinup(void* arg)
2389 : {
2390 : #if HAVE_LINUX_DEVICE
2391 42 : devinfo_t* devinfo = arg;
2392 : uint64_t start;
2393 :
2394 42 : start = os_tick_ms();
2395 :
2396 42 : if (devup(devinfo->device, devinfo->name) != 0) {
2397 : /* LCOV_EXCL_START */
2398 : return (void*)-1;
2399 : /* LCOV_EXCL_STOP */
2400 : }
2401 :
2402 12 : msg_status("Spunup device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start);
2403 :
2404 : /* after the spin up, get SMART info */
2405 12 : if (devsmart(devinfo->device, devinfo->name, devinfo->smartctl, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) {
2406 : /* LCOV_EXCL_START */
2407 : return (void*)-1;
2408 : /* LCOV_EXCL_STOP */
2409 : }
2410 :
2411 : /*
2412 : * Retrieve some attributes directly from the system.
2413 : *
2414 : * smartctl intentionally skips queries on devices in standby mode
2415 : * to prevent accidentally spinning them up.
2416 : */
2417 12 : devattr(devinfo->device, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf);
2418 :
2419 12 : return 0;
2420 : #else
2421 : (void)arg;
2422 : return (void*)-1;
2423 : #endif
2424 : }
2425 :
2426 : /**
2427 : * Thread for spinning down.
2428 : */
2429 17 : static void* thread_spindown(void* arg)
2430 : {
2431 : #if HAVE_LINUX_DEVICE
2432 17 : devinfo_t* devinfo = arg;
2433 : uint64_t start;
2434 :
2435 17 : start = os_tick_ms();
2436 :
2437 17 : if (devdown(devinfo->device, devinfo->name, devinfo->smartctl) != 0) {
2438 : /* LCOV_EXCL_START */
2439 : return (void*)-1;
2440 : /* LCOV_EXCL_STOP */
2441 : }
2442 :
2443 12 : msg_status("Spundown device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start);
2444 :
2445 12 : return 0;
2446 : #else
2447 : (void)arg;
2448 : return (void*)-1;
2449 : #endif
2450 : }
2451 :
2452 : /**
2453 : * Thread for spinning down.
2454 : */
2455 12 : static void* thread_spindownifup(void* arg)
2456 : {
2457 : #if HAVE_LINUX_DEVICE
2458 12 : devinfo_t* devinfo = arg;
2459 : uint64_t start;
2460 : int power;
2461 :
2462 12 : start = os_tick_ms();
2463 :
2464 12 : if (devdownifup(devinfo->device, devinfo->name, devinfo->smartctl, &power) != 0) {
2465 : /* LCOV_EXCL_START */
2466 : return (void*)-1;
2467 : /* LCOV_EXCL_STOP */
2468 : }
2469 :
2470 12 : if (power == POWER_ACTIVE)
2471 0 : msg_status("Spundown device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start);
2472 :
2473 12 : return 0;
2474 : #else
2475 : (void)arg;
2476 : return (void*)-1;
2477 : #endif
2478 : }
2479 :
2480 : /**
2481 : * Thread for getting smart info.
2482 : */
2483 14 : static void* thread_smart(void* arg)
2484 : {
2485 : #if HAVE_LINUX_DEVICE
2486 14 : devinfo_t* devinfo = arg;
2487 :
2488 14 : if (devsmart(devinfo->device, devinfo->name, devinfo->smartctl, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) {
2489 : /* LCOV_EXCL_START */
2490 : return (void*)-1;
2491 : /* LCOV_EXCL_STOP */
2492 : }
2493 :
2494 : /*
2495 : * Retrieve some attributes directly from the system.
2496 : *
2497 : * smartctl intentionally skips queries on devices in standby mode
2498 : * to prevent accidentally spinning them up.
2499 : */
2500 14 : devattr(devinfo->device, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf);
2501 :
2502 14 : return 0;
2503 : #else
2504 : (void)arg;
2505 : return (void*)-1;
2506 : #endif
2507 : }
2508 :
2509 : /**
2510 : * Thread for getting power info.
2511 : */
2512 28 : static void* thread_probe(void* arg)
2513 : {
2514 : #if HAVE_LINUX_DEVICE
2515 28 : devinfo_t* devinfo = arg;
2516 :
2517 28 : if (devprobe(devinfo->device, devinfo->name, devinfo->smartctl, &devinfo->power, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) {
2518 : /* LCOV_EXCL_START */
2519 : return (void*)-1;
2520 : /* LCOV_EXCL_STOP */
2521 : }
2522 :
2523 : /*
2524 : * Retrieve some attributes directly from the system.
2525 : *
2526 : * smartctl intentionally skips queries on devices in standby mode
2527 : * to prevent accidentally spinning them up.
2528 : */
2529 28 : devattr(devinfo->device, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf);
2530 :
2531 28 : return 0;
2532 : #else
2533 : (void)arg;
2534 : return (void*)-1;
2535 : #endif
2536 : }
2537 :
2538 8 : static int device_thread(tommy_list* list, void* (*func)(void* arg))
2539 : {
2540 8 : int fail = 0;
2541 : tommy_node* i;
2542 :
2543 : #if HAVE_THREAD
2544 : /* start all threads */
2545 121 : for (i = tommy_list_head(list); i != 0; i = i->next) {
2546 113 : devinfo_t* devinfo = i->data;
2547 :
2548 113 : thread_create(&devinfo->thread, func, devinfo);
2549 : }
2550 :
2551 : /* join all threads */
2552 121 : for (i = tommy_list_head(list); i != 0; i = i->next) {
2553 113 : devinfo_t* devinfo = i->data;
2554 : void* retval;
2555 :
2556 113 : thread_join(devinfo->thread, &retval);
2557 :
2558 113 : if (retval != 0)
2559 35 : ++fail;
2560 : }
2561 : #else
2562 : for (i = tommy_list_head(list); i != 0; i = i->next) {
2563 : devinfo_t* devinfo = i->data;
2564 :
2565 : if (func(devinfo) != 0)
2566 : ++fail;
2567 : }
2568 : #endif
2569 8 : if (fail != 0) {
2570 : /* LCOV_EXCL_START */
2571 : return -1;
2572 : /* LCOV_EXCL_STOP */
2573 : }
2574 :
2575 6 : return 0;
2576 : }
2577 :
2578 10 : int devquery(tommy_list* high, tommy_list* low, int operation)
2579 : {
2580 10 : void* (*func)(void* arg) = 0;
2581 :
2582 : #if HAVE_LINUX_DEVICE
2583 : tommy_node* i;
2584 : struct stat st;
2585 :
2586 : /* sysfs interface is required */
2587 10 : if (stat("/sys/dev/block", &st) != 0) {
2588 : /* LCOV_EXCL_START */
2589 : log_fatal(EEXTERNAL, "Missing interface /sys/dev/block.\n");
2590 : return -1;
2591 : /* LCOV_EXCL_STOP */
2592 : }
2593 :
2594 : /* for each high device */
2595 167 : for (i = tommy_list_head(high); i != 0; i = i->next) {
2596 157 : devinfo_t* devinfo = i->data;
2597 157 : uint64_t device = devinfo->device;
2598 :
2599 : #if HAVE_SYNCFS
2600 157 : if (operation == DEVICE_DOWN) {
2601 : /* flush the high level filesystem before spinning down */
2602 17 : int f = open(devinfo->mount, O_RDONLY);
2603 17 : if (f >= 0) {
2604 17 : syncfs(f);
2605 17 : close(f);
2606 : }
2607 : }
2608 : #endif
2609 :
2610 : tommy_list devlist;
2611 157 : tommy_list_init(&devlist);
2612 :
2613 : /* obtain the real devices */
2614 157 : if (devdereference(device, devinfo->mount, &devlist) != 0) {
2615 : /* LCOV_EXCL_START */
2616 : log_fatal(EEXTERNAL, "Failed to dereference device '%u:%u' at '%s'.\n", major(device), minor(device), devinfo->mount);
2617 : return -1;
2618 : /* LCOV_EXCL_STOP */
2619 : }
2620 :
2621 157 : devinfo->file[0] = 0;
2622 314 : for (tommy_node* j = tommy_list_head(&devlist); j != 0; j = j->next) {
2623 157 : struct dev_struct* dev = j->data;
2624 : uint64_t access_stat;
2625 :
2626 : /* retrieve access stat for the high level device */
2627 157 : if (devstat(dev->device, &access_stat) == 0) {
2628 : /* cumulate access stat in the first split */
2629 157 : if (devinfo->split)
2630 39 : devinfo->split->access_stat += access_stat;
2631 : else
2632 118 : devinfo->access_stat += access_stat;
2633 : }
2634 :
2635 : /* get the device file */
2636 : char file[PATH_MAX];
2637 157 : if (devresolve(dev->device, file, sizeof(file)) != 0) {
2638 : /* LCOV_EXCL_START */
2639 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(dev->device), minor(dev->device));
2640 : return -1;
2641 : /* LCOV_EXCL_STOP */
2642 : }
2643 :
2644 : /* add to the list of device files */
2645 157 : if (devinfo->file[0] != 0)
2646 0 : pathcat(devinfo->file, sizeof(devinfo->file), ",");
2647 157 : pathcat(devinfo->file, sizeof(devinfo->file), file);
2648 :
2649 : /* expand the tree of devices */
2650 157 : if (devtree(devinfo, dev->device, low) != 0) {
2651 : /* LCOV_EXCL_START */
2652 : log_fatal(EEXTERNAL, "Failed to expand device '%u:%u'.\n", major(dev->device), minor(dev->device));
2653 : return -1;
2654 : /* LCOV_EXCL_STOP */
2655 : }
2656 : }
2657 :
2658 157 : tommy_list_foreach(&devlist, free);
2659 : }
2660 : #else
2661 : (void)high;
2662 : #endif
2663 :
2664 10 : switch (operation) {
2665 2 : case DEVICE_UP : func = thread_spinup; break;
2666 2 : case DEVICE_DOWN : func = thread_spindown; break;
2667 1 : case DEVICE_SMART : func = thread_smart; break;
2668 2 : case DEVICE_PROBE : func = thread_probe; break;
2669 1 : case DEVICE_DOWNIFUP : func = thread_spindownifup; break;
2670 : }
2671 :
2672 10 : if (!func)
2673 2 : return 0;
2674 :
2675 8 : return device_thread(low, func);
2676 : }
2677 :
2678 322 : void os_init(int opt)
2679 : {
2680 : #if HAVE_BLKID
2681 : int ret;
2682 322 : ret = blkid_get_cache(&cache, NULL);
2683 322 : if (ret != 0) {
2684 : /* LCOV_EXCL_START */
2685 : log_fatal(EEXTERNAL, "WARNING Failed to get blkid cache\n");
2686 : /* LCOV_EXCL_STOP */
2687 : }
2688 : #endif
2689 :
2690 : /* set LC_ALL=C to make smartctl ignoring the locale when printing info */
2691 322 : setenv("LC_ALL", "C", 1);
2692 :
2693 : (void)opt;
2694 322 : }
2695 :
2696 297 : void os_done(void)
2697 : {
2698 : #if HAVE_BLKID
2699 297 : if (cache != 0)
2700 297 : blkid_put_cache(cache);
2701 : #endif
2702 297 : }
2703 :
2704 : /* LCOV_EXCL_START */
2705 : void os_abort(void)
2706 : {
2707 : #if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS
2708 : void* stack[32];
2709 : char** messages;
2710 : size_t size;
2711 : unsigned i;
2712 : #endif
2713 :
2714 : printf("Stacktrace of " PACKAGE " v" VERSION);
2715 : #ifdef _linux
2716 : printf(", linux");
2717 : #endif
2718 : #ifdef __GNUC__
2719 : printf(", gcc " __VERSION__);
2720 : #endif
2721 : printf(", %d-bit", (int)sizeof(void*) * 8);
2722 : printf(", PATH_MAX=%d", PATH_MAX);
2723 : printf("\n");
2724 :
2725 : #if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS
2726 : size = backtrace(stack, 32);
2727 :
2728 : messages = backtrace_symbols(stack, size);
2729 :
2730 : for (i = 1; i < size; ++i) {
2731 : const char* msg;
2732 :
2733 : if (messages)
2734 : msg = messages[i];
2735 : else
2736 : msg = "<unknown>";
2737 :
2738 : printf("[bt] %02u: %s\n", i, msg);
2739 :
2740 : if (messages) {
2741 : int ret;
2742 : char addr2line[1024];
2743 : size_t j = 0;
2744 : while (msg[j] != '(' && msg[j] != ' ' && msg[j] != 0)
2745 : ++j;
2746 :
2747 : snprintf(addr2line, sizeof(addr2line), "addr2line %p -e %.*s", stack[i], (unsigned)j, msg);
2748 :
2749 : ret = system(addr2line);
2750 : if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0)
2751 : printf("exit:%d\n", WEXITSTATUS(ret));
2752 : if (WIFSIGNALED(ret))
2753 : printf("signal:%d\n", WTERMSIG(ret));
2754 : }
2755 : }
2756 : #endif
2757 :
2758 : printf("Please report this error to the SnapRAID Forum:\n");
2759 : printf("https://sourceforge.net/p/snapraid/discussion/1677233/\n");
2760 :
2761 : abort();
2762 : }
2763 : /* LCOV_EXCL_STOP */
2764 :
2765 0 : void os_clear(void)
2766 : {
2767 : /* ANSI codes */
2768 0 : printf("\033[H"); /* cursor at topleft */
2769 0 : printf("\033[2J"); /* clear screen */
2770 0 : }
2771 :
2772 135 : size_t direct_size(void)
2773 : {
2774 : long size;
2775 :
2776 135 : size = sysconf(_SC_PAGESIZE);
2777 :
2778 135 : if (size == -1) {
2779 : /* LCOV_EXCL_START */
2780 : log_fatal(EEXTERNAL, "No page size\n");
2781 : exit(EXIT_FAILURE);
2782 : /* LCOV_EXCL_STOP */
2783 : }
2784 :
2785 135 : return size;
2786 : }
2787 :
2788 : #if HAVE_LINUX_DEVICE
2789 :
2790 : /* List of possible ambient temperature labels */
2791 : const char* AMBIENT_LABEL[] = {
2792 : "systin",
2793 : "auxtin",
2794 : "mb",
2795 : "m/b",
2796 : "board",
2797 : "motherboard",
2798 : "system",
2799 : "chassis",
2800 : "case",
2801 : "room",
2802 : "ambient",
2803 : 0
2804 : };
2805 :
2806 1 : int ambient_temperature(void)
2807 : {
2808 : DIR* dir;
2809 : struct dirent* entry;
2810 1 : int lowest_temp = 0;
2811 :
2812 1 : dir = opendir("/sys/class/hwmon");
2813 1 : if (!dir) {
2814 : /* LCOV_EXCL_START */
2815 : return 0;
2816 : /* LCOV_EXCL_STOP */
2817 : }
2818 :
2819 : /* iterate through hwmon devices */
2820 5 : while ((entry = readdir(dir)) != NULL) {
2821 : char path[PATH_MAX];
2822 : DIR* hwmon_dir;
2823 : struct dirent* hwmon_entry;
2824 :
2825 4 : if (strncmp(entry->d_name, "hwmon", 5) != 0)
2826 2 : continue;
2827 :
2828 2 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s", entry->d_name);
2829 :
2830 : /* iterate through temp*_input files */
2831 2 : hwmon_dir = opendir(path);
2832 2 : if (!hwmon_dir) {
2833 : /* LCOV_EXCL_START */
2834 : continue;
2835 : /* LCOV_EXCL_STOP */
2836 : }
2837 :
2838 66 : while ((hwmon_entry = readdir(hwmon_dir)) != NULL) {
2839 : char value[128];
2840 : char name[128];
2841 : char label[128];
2842 : char* dash;
2843 : char* e;
2844 : long temp;
2845 :
2846 64 : if (strncmp(hwmon_entry->d_name, "temp", 4) != 0)
2847 63 : continue;
2848 :
2849 18 : dash = strrchr(hwmon_entry->d_name, '_');
2850 18 : if (dash == 0) {
2851 : /* LCOV_EXCL_START */
2852 : continue;
2853 : /* LCOV_EXCL_STOP */
2854 : }
2855 :
2856 18 : if (strcmp(dash, "_input") != 0)
2857 14 : continue;
2858 :
2859 : /* read the temperature */
2860 4 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s/%s", entry->d_name, hwmon_entry->d_name);
2861 :
2862 4 : if (sysattr(path, value, sizeof(value)) != 0) {
2863 : /* LCOV_EXCL_START */
2864 : continue;
2865 : /* LCOV_EXCL_STOP */
2866 : }
2867 :
2868 4 : temp = strtol(value, &e, 10) / 1000;
2869 4 : if (*e != 0 && !isspace(*e)) {
2870 : /* LCOV_EXCL_START */
2871 : continue;
2872 : /* LCOV_EXCL_STOP */
2873 : }
2874 :
2875 : /* cut the file name at "_input" */
2876 4 : *dash = 0;
2877 :
2878 : /* read the corresponding name */
2879 4 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s/name", entry->d_name);
2880 4 : if (sysattr(path, name, sizeof(name)) != 0) {
2881 : /* LCOV_EXCL_START */
2882 : /* fallback to using the hwmon name */
2883 : pathcpy(name, sizeof(name), entry->d_name);
2884 : /* LCOV_EXCL_STOP */
2885 : }
2886 :
2887 : /* read the corresponding label file */
2888 4 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s/%s_label", entry->d_name, hwmon_entry->d_name);
2889 4 : if (sysattr(path, label, sizeof(label)) != 0) {
2890 : /* LCOV_EXCL_START */
2891 : /* fallback to using the temp* name (e.g., temp1, temp2) */
2892 : pathcpy(label, sizeof(label), hwmon_entry->d_name);
2893 : /* LCOV_EXCL_STOP */
2894 : }
2895 :
2896 4 : log_tag("thermal:ambient:device:%s:%s:%s:%s:%ld\n", entry->d_name, name, hwmon_entry->d_name, label, temp);
2897 :
2898 : /* check if temperature is in reasonable range */
2899 4 : if (temp < 15 || temp > 40)
2900 3 : continue;
2901 :
2902 : /* lower case */
2903 1 : strlwr(label);
2904 :
2905 : /* check if label matches possible ambient labels */
2906 3 : for (int i = 0; AMBIENT_LABEL[i]; ++i) {
2907 3 : if (worddigitstr(label, AMBIENT_LABEL[i]) != 0) {
2908 1 : log_tag("thermal:ambient:candidate:%ld\n", temp);
2909 1 : if (lowest_temp == 0 || lowest_temp > temp)
2910 1 : lowest_temp = temp;
2911 1 : break;
2912 : }
2913 : }
2914 :
2915 : /* accept also generic "temp1" */
2916 1 : if (strcmp(label, "temp1") == 0) {
2917 0 : log_tag("thermal:ambient:candidate:%ld\n", temp);
2918 0 : if (lowest_temp == 0 || lowest_temp > temp)
2919 0 : lowest_temp = temp;
2920 : }
2921 : }
2922 :
2923 2 : closedir(hwmon_dir);
2924 : }
2925 :
2926 1 : closedir(dir);
2927 :
2928 1 : return lowest_temp;
2929 : }
2930 : #else
2931 : int ambient_temperature(void)
2932 : {
2933 : return 0;
2934 : }
2935 : #endif
2936 :
2937 13 : int devmap(void)
2938 : {
2939 : #if HAVE_LINUX_DEVICE
2940 : char esc_buffer[ESC_MAX];
2941 : char path[PATH_MAX];
2942 13 : DIR* d = opendir("/sys/block");
2943 13 : if (!d) {
2944 : /* LCOV_EXCL_START */
2945 : return -1;
2946 : /* LCOV_EXCL_STOP */
2947 : }
2948 :
2949 : struct dirent* dd;
2950 714 : while (1) {
2951 : char buf[256];
2952 :
2953 727 : dd = readdir(d);
2954 727 : if (dd == 0)
2955 13 : break;
2956 :
2957 714 : if (dd->d_name[0] == '.')
2958 526 : continue;
2959 :
2960 : /* verify it has a hardware device link (excludes virtual disks) */
2961 688 : pathprint(path, sizeof(path), "/sys/block/%s/device", dd->d_name);
2962 688 : if (access(path, F_OK) != 0)
2963 500 : continue;
2964 :
2965 : /* device/serial */
2966 188 : pathprint(path, sizeof(path), "/sys/block/%s/device/serial", dd->d_name);
2967 188 : if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) {
2968 0 : log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf, esc_buffer));
2969 : }
2970 :
2971 : /* device/vpd_pg80 */
2972 188 : pathprint(path, sizeof(path), "/sys/block/%s/device/vpd_pg80", dd->d_name);
2973 188 : if (sysattr_vpd_pg80(path, buf, sizeof(buf)) == 0 && buf[0] != 0) {
2974 188 : log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf, esc_buffer));
2975 : }
2976 :
2977 : /* device/vpd_pg83 */
2978 188 : pathprint(path, sizeof(path), "/sys/block/%s/device/vpd_pg83", dd->d_name);
2979 188 : if (sysattr_vpd_pg83(path, buf, sizeof(buf)) == 0 && buf[0] != 0) {
2980 188 : log_tag("map:/dev/%s:naa.%s\n", dd->d_name, esc_tag(buf, esc_buffer));
2981 : }
2982 :
2983 : /* device/WWN */
2984 188 : pathprint(path, sizeof(path), "/sys/block/%s/device/wwn", dd->d_name);
2985 188 : if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) {
2986 0 : log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf, esc_buffer));
2987 : }
2988 :
2989 : /* device/WWID */
2990 188 : pathprint(path, sizeof(path), "/sys/block/%s/device/wwid", dd->d_name);
2991 188 : if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) {
2992 188 : log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf, esc_buffer));
2993 : }
2994 :
2995 : /* WWN */
2996 188 : pathprint(path, sizeof(path), "/sys/block/%s/wwn", dd->d_name);
2997 188 : if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) {
2998 0 : log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf, esc_buffer));
2999 : }
3000 :
3001 : /* WWID */
3002 188 : pathprint(path, sizeof(path), "/sys/block/%s/wwid", dd->d_name);
3003 188 : if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) {
3004 0 : log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf, esc_buffer));
3005 : }
3006 :
3007 : /* uuid */
3008 188 : pathprint(path, sizeof(path), "/sys/block/%s/uuid", dd->d_name);
3009 188 : if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) {
3010 0 : log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf, esc_buffer));
3011 : }
3012 : }
3013 :
3014 13 : closedir(d);
3015 : #endif
3016 13 : return 0;
3017 : }
3018 :
3019 : #endif
3020 :
|