Line data Source code
1 : /*
2 : * Copyright (C) 2013 Andrea Mazzoleni
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "portable.h"
19 :
20 : #ifndef __MINGW32__ /* Only for Unix */
21 :
22 : #include "support.h"
23 :
24 : /**
25 : * Exit codes.
26 : */
27 : int exit_success = 0;
28 : int exit_failure = 1;
29 : int exit_sync_needed = 2;
30 :
31 1717312 : int open_noatime(const char* file, int flags)
32 : {
33 : #ifdef O_NOATIME
34 1717312 : int f = open(file, flags | O_NOATIME);
35 :
36 : /* only root is allowed to use O_NOATIME, in case retry without it */
37 1717625 : if (f == -1 && errno == EPERM)
38 0 : f = open(file, flags);
39 1717360 : return f;
40 : #else
41 : return open(file, flags);
42 : #endif
43 : }
44 :
45 0 : int dirent_hidden(struct dirent* dd)
46 : {
47 0 : return dd->d_name[0] == '.';
48 : }
49 :
50 0 : const char* stat_desc(struct stat* st)
51 : {
52 0 : if (S_ISREG(st->st_mode))
53 0 : return "regular";
54 0 : if (S_ISDIR(st->st_mode))
55 0 : return "directory";
56 0 : if (S_ISCHR(st->st_mode))
57 0 : return "character";
58 0 : if (S_ISBLK(st->st_mode))
59 0 : return "block-device";
60 0 : if (S_ISFIFO(st->st_mode))
61 0 : return "fifo";
62 0 : if (S_ISLNK(st->st_mode))
63 0 : return "link";
64 0 : if (S_ISLNK(st->st_mode))
65 0 : return "symbolic-link";
66 0 : if (S_ISSOCK(st->st_mode))
67 0 : return "socket";
68 0 : return "unknown";
69 : }
70 :
71 : /**
72 : * Get the device file from the device number.
73 : *
74 : * It uses /proc/self/mountinfo.
75 : *
76 : * Null devices (major==0) are resolved to the device indicated in mountinfo.
77 : */
78 : #if HAVE_LINUX_DEVICE
79 4 : static int devresolve_proc(uint64_t device, char* path, size_t path_size)
80 : {
81 : FILE* f;
82 : char match[32];
83 :
84 : /* generate the matching string */
85 4 : snprintf(match, sizeof(match), "%u:%u", major(device), minor(device));
86 :
87 4 : f = fopen("/proc/self/mountinfo", "r");
88 4 : if (!f) {
89 0 : log_tag("resolve:proc:%u:%u: failed to open /proc/self/mountinfo\n", major(device), minor(device));
90 0 : return -1;
91 : }
92 :
93 : /*
94 : * mountinfo format
95 : * 0 - mount ID
96 : * 1 - parent ID
97 : * 2 - major:minor
98 : * 3 - root
99 : * 4 - mount point
100 : * 5 - options
101 : * 6 - "-" (separator)
102 : * 7 - fs
103 : * 8 - mount source - /dev/device
104 : */
105 :
106 : while (1) {
107 : char buf[256];
108 : char* first_map[8];
109 : unsigned first_mac;
110 : char* second_map[8];
111 : unsigned second_mac;
112 : char* s;
113 : struct stat st;
114 : char* separator;
115 : char* majorminor;
116 : char* mountpoint;
117 : char* fs;
118 : char* mountsource;
119 :
120 156 : s = fgets(buf, sizeof(buf), f);
121 156 : if (s == 0)
122 4 : break;
123 :
124 : /* find the separator position */
125 152 : separator = strstr(s, " - ");
126 152 : if (!separator)
127 0 : continue;
128 :
129 : /* skip the separator */
130 152 : *separator = 0;
131 152 : separator += 3;
132 :
133 : /* split the line */
134 152 : first_mac = strsplit(first_map, 8, s, " \t\r\n");
135 152 : second_mac = strsplit(second_map, 8, separator, " \t\r\n");
136 :
137 : /* if too short, it's the wrong line */
138 152 : if (first_mac < 5)
139 0 : continue;
140 152 : if (second_mac < 2)
141 0 : continue;
142 :
143 152 : majorminor = first_map[2];
144 152 : mountpoint = first_map[4];
145 152 : fs = second_map[0];
146 152 : mountsource = second_map[1];
147 :
148 : /* compare major:minor from mountinfo */
149 152 : if (strcmp(majorminor, match) == 0) {
150 : /*
151 : * Accept only /dev/... mountsource
152 : *
153 : * This excludes ZFS that uses a bare label for mountsource, like "tank".
154 : *
155 : * 410 408 0:193 / /XXX rw,relatime shared:217 - zfs tank/system/data/var/lib/docker/XXX rw,xattr,noacl
156 : *
157 : * Also excludes AUTOFS unmounted devices that point to a fake filesystem
158 : * used to remount them at the first use.
159 : *
160 : * 97 25 0:42 / /XXX rw,relatime shared:76 - autofs /etc/auto.seed rw,fd=6,pgrp=952,timeout=30,minproto=5,maxproto=5,indirect
161 : */
162 0 : if (strncmp(mountsource, "/dev/", 5) != 0) {
163 0 : log_tag("resolve:proc:%u:%u: match skipped for not /dev/ mountsource for %s %s\n", major(device), minor(device), fs, mountsource);
164 0 : continue;
165 : }
166 :
167 0 : pathcpy(path, path_size, mountsource);
168 :
169 0 : log_tag("resolve:proc:%u:%u: found device %s matching device %s\n", major(device), minor(device), path, match);
170 :
171 0 : fclose(f);
172 0 : return 0;
173 : }
174 :
175 : /* get the device of the mount point */
176 : /* in Btrfs it could be different than the one in mountinfo */
177 152 : if (stat(mountpoint, &st) == 0 && st.st_dev == device) {
178 0 : if (strncmp(mountsource, "/dev/", 5) != 0) {
179 0 : log_tag("resolve:proc:%u:%u: match skipped for not /dev/ mountsource for %s %s\n", major(device), minor(device), fs, mountsource);
180 0 : continue;
181 : }
182 :
183 0 : pathcpy(path, path_size, mountsource);
184 :
185 0 : log_tag("resolve:proc:%u:%u: found device %s matching mountpoint %s\n", major(device), minor(device), path, mountpoint);
186 :
187 0 : fclose(f);
188 0 : return 0;
189 : }
190 152 : }
191 :
192 4 : log_tag("resolve:proc:%u:%u: not found\n", major(device), minor(device));
193 :
194 4 : fclose(f);
195 4 : return -1;
196 : }
197 : #endif
198 :
199 : /**
200 : * Get the device of a virtual superblock.
201 : *
202 : * This is intended to resolve the case of Btrfs filesystems that
203 : * create a virtual superblock (major==0) not backed by any low
204 : * level device.
205 : *
206 : * See:
207 : * Bug 711881 - too funny btrfs st_dev numbers
208 : * https://bugzilla.redhat.com/show_bug.cgi?id=711881
209 : */
210 : #if HAVE_LINUX_DEVICE
211 4 : static int devdereference(uint64_t device, uint64_t* new_device)
212 : {
213 : char path[PATH_MAX];
214 : struct stat st;
215 :
216 : /* use the proc interface to get the device containing the filesystem */
217 4 : if (devresolve_proc(device, path, sizeof(path)) != 0) {
218 : /* LCOV_EXCL_START */
219 : return -1;
220 : /* LCOV_EXCL_STOP */
221 : }
222 :
223 : /* check the device */
224 0 : if (stat(path, &st) != 0) {
225 : /* LCOV_EXCL_START */
226 : log_tag("dereference:%u:%u: failed to stat %s\n", major(device), minor(device), path);
227 : return -1;
228 : /* LCOV_EXCL_STOP */
229 : }
230 :
231 0 : if (major(st.st_rdev) == 0) {
232 : /* LCOV_EXCL_START */
233 : log_tag("dereference:%u:%u: still null device %s -> %u:%u\n", major(device), minor(device), path, major(st.st_rdev), minor(st.st_rdev));
234 : return -1;
235 : /* LCOV_EXCL_STOP */
236 : }
237 :
238 0 : *new_device = st.st_rdev;
239 0 : log_tag("dereference:%u:%u: found %u:%u\n", major(device), minor(device), major(st.st_rdev), minor(st.st_rdev));
240 0 : return 0;
241 : }
242 : #endif
243 :
244 : /**
245 : * Read a file extracting the specified tag TAG=VALUE format.
246 : * Return !=0 on error.
247 : */
248 : #if HAVE_LINUX_DEVICE
249 189 : static int tagread(const char* path, const char* tag, char* value, size_t value_size)
250 : {
251 : int f;
252 : int ret;
253 : int len;
254 : char buf[512];
255 : size_t tag_len;
256 : char* i;
257 : char* e;
258 :
259 189 : f = open(path, O_RDONLY);
260 190 : if (f == -1) {
261 : /* LCOV_EXCL_START */
262 : log_fatal("Failed to open '%s'.\n", path);
263 : return 0;
264 : /* LCOV_EXCL_STOP */
265 : }
266 :
267 190 : len = read(f, buf, sizeof(buf));
268 191 : if (len < 0) {
269 : /* LCOV_EXCL_START */
270 : close(f);
271 : log_fatal("Failed to read '%s'.\n", path);
272 : return -1;
273 : /* LCOV_EXCL_STOP */
274 : }
275 191 : if (len == sizeof(buf)) {
276 : /* LCOV_EXCL_START */
277 : close(f);
278 : log_fatal("Too long read '%s'.\n", path);
279 : return -1;
280 : /* LCOV_EXCL_STOP */
281 : }
282 :
283 191 : ret = close(f);
284 199 : if (ret != 0) {
285 : /* LCOV_EXCL_START */
286 : log_fatal("Failed to close '%s'.\n", path);
287 : return -1;
288 : /* LCOV_EXCL_STOP */
289 : }
290 :
291 199 : buf[len] = 0;
292 199 : tag_len = strlen(tag);
293 :
294 3414 : for (i = buf; *i; ++i) {
295 3415 : char* p = i;
296 :
297 : /* start with a space */
298 3415 : if (p != buf) {
299 3225 : if (!isspace(*p))
300 2834 : continue;
301 382 : ++p;
302 : }
303 :
304 572 : if (strncmp(p, tag, tag_len) != 0)
305 381 : continue;
306 191 : p += tag_len;
307 :
308 : /* end with a = */
309 191 : if (*p != '=')
310 0 : continue;
311 191 : ++p;
312 :
313 : /* found */
314 191 : i = p;
315 191 : break;
316 : }
317 190 : if (!*i) {
318 : /* LCOV_EXCL_START */
319 : log_fatal("Missing tag '%s' for '%s'.\n", tag, path);
320 : return -1;
321 : /* LCOV_EXCL_STOP */
322 : }
323 :
324 : /* terminate at the first space */
325 190 : e = i;
326 1018 : while (*e != 0 && !isspace(*e))
327 638 : ++e;
328 191 : *e = 0;
329 :
330 191 : if (!*i) {
331 : /* LCOV_EXCL_START */
332 : log_fatal("Empty tag '%s' for '%s'.\n", tag, path);
333 : return -1;
334 : /* LCOV_EXCL_STOP */
335 : }
336 :
337 191 : pathprint(value, value_size, "%s", i);
338 :
339 189 : return 0;
340 : }
341 : #endif
342 :
343 : /**
344 : * Get the device file from the device number.
345 : *
346 : * It uses /sys/dev/block/.../uevent.
347 : *
348 : * For null device (major==0) it fails.
349 : */
350 : #if HAVE_LINUX_DEVICE
351 191 : static int devresolve_sys(dev_t device, char* path, size_t path_size)
352 : {
353 : struct stat st;
354 : char buf[PATH_MAX];
355 :
356 : /* default device path from device number */
357 191 : pathprint(path, path_size, "/sys/dev/block/%u:%u/uevent", major(device), minor(device));
358 :
359 189 : if (tagread(path, "DEVNAME", buf, sizeof(buf)) != 0) {
360 : /* LCOV_EXCL_START */
361 : log_tag("resolve:sys:%u:%u: failed to read DEVNAME tag '%s'\n", major(device), minor(device), path);
362 : return -1;
363 : /* LCOV_EXCL_STOP */
364 : }
365 :
366 : /* set the real device path */
367 189 : pathprint(path, path_size, "/dev/%s", buf);
368 :
369 : /* check the device */
370 187 : if (stat(path, &st) != 0) {
371 : /* LCOV_EXCL_START */
372 : log_tag("resolve:sys:%u:%u: failed to stat '%s'\n", major(device), minor(device), path);
373 : return -1;
374 : /* LCOV_EXCL_STOP */
375 : }
376 189 : if (st.st_rdev != device) {
377 : /* LCOV_EXCL_START */
378 : 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);
379 : return -1;
380 : /* LCOV_EXCL_STOP */
381 : }
382 :
383 189 : log_tag("resolve:sys:%u:%u:%s: found\n", major(device), minor(device), path);
384 :
385 191 : return 0;
386 : }
387 : #endif
388 :
389 : /**
390 : * Get the device file from the device number.
391 : */
392 : #if HAVE_LINUX_DEVICE
393 191 : static int devresolve(uint64_t device, char* path, size_t path_size)
394 : {
395 191 : if (devresolve_sys(device, path, path_size) == 0)
396 191 : return 0;
397 :
398 0 : return -1;
399 : }
400 : #endif
401 :
402 : /**
403 : * Cache used by blkid.
404 : */
405 : #if HAVE_BLKID
406 : static blkid_cache cache = 0;
407 : #endif
408 :
409 : /**
410 : * Get the UUID using the /dev/disk/by-uuid/ links.
411 : * It doesn't require root permission, and the uuid are always updated.
412 : * It doesn't work with Btrfs file-systems that don't export the main UUID
413 : * in /dev/disk/by-uuid/.
414 : */
415 : #if HAVE_LINUX_DEVICE
416 5626 : static int devuuid_dev(uint64_t device, char* uuid, size_t uuid_size)
417 : {
418 : int ret;
419 : DIR* d;
420 : struct dirent* dd;
421 : struct stat st;
422 :
423 : /* scan the UUID directory searching for the device */
424 5626 : d = opendir("/dev/disk/by-uuid");
425 5626 : if (!d) {
426 0 : log_tag("uuid:by-uuidd:%u:%u: opendir(/dev/disk/by-uuid) failed, %s\n", major(device), minor(device), strerror(errno));
427 : /* directory missing?, likely we are not in Linux */
428 0 : return -1;
429 : }
430 :
431 146276 : while ((dd = readdir(d)) != 0) {
432 : /* skip "." and ".." files, UUIDs never start with '.' */
433 140650 : if (dd->d_name[0] == '.')
434 11252 : continue;
435 :
436 129398 : ret = fstatat(dirfd(d), dd->d_name, &st, 0);
437 129398 : if (ret != 0) {
438 0 : log_tag("uuid:by-uuidd:%u:%u: fstatat(%s) failed, %s\n", major(device), minor(device), dd->d_name, strerror(errno));
439 : /* generic error, ignore and continue the search */
440 0 : continue;
441 : }
442 :
443 : /* if it matches, we have the uuid */
444 129398 : if (S_ISBLK(st.st_mode) && st.st_rdev == (dev_t)device) {
445 : char buf[PATH_MAX];
446 : char path[PATH_MAX];
447 :
448 : /* resolve the link */
449 5626 : pathprint(path, sizeof(path), "/dev/disk/by-uuid/%s", dd->d_name);
450 5626 : ret = readlink(path, buf, sizeof(buf));
451 5626 : if (ret < 0 || ret == sizeof(buf)) {
452 0 : log_tag("uuid:by-uuidd:%u:%u: readlink(/dev/disk/by-uuid/%s) failed, %s\n", major(device), minor(device), dd->d_name, strerror(errno));
453 : /* generic error, ignore and continue the search */
454 0 : continue;
455 : }
456 5626 : buf[ret] = 0;
457 :
458 : /* found */
459 5626 : pathcpy(uuid, uuid_size, dd->d_name);
460 :
461 5626 : log_tag("uuid:by-uuid:%u:%u:%s: found %s\n", major(device), minor(device), uuid, buf);
462 :
463 5626 : closedir(d);
464 5626 : return 0;
465 : }
466 : }
467 :
468 0 : log_tag("uuid:by-uuidd:%u:%u: /dev/disk/by-uuid doesn't contain a matching block device\n", major(device), minor(device));
469 :
470 : /* not found */
471 0 : closedir(d);
472 0 : return -1;
473 : }
474 : #endif
475 :
476 : /**
477 : * Get the UUID using liblkid.
478 : * It uses a cache to work without root permission, resulting in UUID
479 : * not necessarily recent.
480 : * We could call blkid_probe_all() to refresh the UUID, but it would
481 : * require root permission to read the superblocks, and resulting in
482 : * all the disks spinning.
483 : */
484 : #if HAVE_BLKID
485 0 : static int devuuid_blkid(uint64_t device, char* uuid, size_t uuid_size)
486 : {
487 : char* devname;
488 : char* uuidname;
489 :
490 0 : devname = blkid_devno_to_devname(device);
491 0 : if (!devname) {
492 0 : log_tag("uuid:blkid:%u:%u: blkid_devno_to_devname() failed, %s\n", major(device), minor(device), strerror(errno));
493 : /* device mapping failed */
494 0 : return -1;
495 : }
496 :
497 0 : uuidname = blkid_get_tag_value(cache, "UUID", devname);
498 0 : if (!uuidname) {
499 0 : log_tag("uuid:blkid:%u:%u: blkid_get_tag_value(UUID,%s) failed, %s\n", major(device), minor(device), devname, strerror(errno));
500 : /* uuid mapping failed */
501 0 : free(devname);
502 0 : return -1;
503 : }
504 :
505 0 : pathcpy(uuid, uuid_size, uuidname);
506 :
507 0 : log_tag("uuid:blkid:%u:%u:%s: found %s\n", major(device), minor(device), uuid, devname);
508 :
509 0 : free(devname);
510 0 : free(uuidname);
511 0 : return 0;
512 : }
513 : #endif
514 :
515 5630 : int devuuid(uint64_t device, char* uuid, size_t uuid_size)
516 : {
517 : #if HAVE_LINUX_DEVICE
518 : /* if the major is the null device */
519 5630 : if (major(device) == 0) {
520 : /* obtain the real device */
521 4 : if (devdereference(device, &device) != 0) {
522 : /* LCOV_EXCL_START */
523 : return -1;
524 : /* LCOV_EXCL_STOP */
525 : }
526 : }
527 : #endif
528 :
529 : /* first try with the /dev/disk/by-uuid version */
530 : #if HAVE_LINUX_DEVICE
531 5626 : if (devuuid_dev(device, uuid, uuid_size) == 0)
532 5626 : return 0;
533 : #else
534 : log_tag("uuid:by-uuidd:%u:%u: by-uuid not supported\n", major(device), minor(device));
535 : #endif
536 :
537 : /* fall back to blkid for other cases */
538 : #if HAVE_BLKID
539 0 : if (devuuid_blkid(device, uuid, uuid_size) == 0)
540 0 : return 0;
541 : #else
542 : log_tag("uuid:blkid:%u:%u: blkid not supported\n", major(device), minor(device));
543 : #endif
544 :
545 0 : log_tag("uuid:notfound:%u:%u:\n", major(device), minor(device));
546 :
547 : /* not supported */
548 : (void)uuid;
549 : (void)uuid_size;
550 0 : return -1;
551 : }
552 :
553 11432 : int filephy(const char* path, uint64_t size, uint64_t* physical)
554 : {
555 : #if HAVE_LINUX_FIEMAP_H
556 : /* In Linux get the real physical address of the file */
557 : /* Note that FIEMAP doesn't require root permission */
558 : int f;
559 : struct {
560 : struct fiemap fiemap;
561 : struct fiemap_extent extent;
562 : } fm;
563 : unsigned int blknum;
564 :
565 11432 : f = open(path, O_RDONLY);
566 11432 : if (f == -1) {
567 0 : return -1;
568 : }
569 :
570 : /* first try with FIEMAP */
571 : /* if works for ext2, ext3, ext4, xfs, btrfs */
572 11432 : memset(&fm, 0, sizeof(fm));
573 11432 : fm.fiemap.fm_start = 0;
574 11432 : fm.fiemap.fm_length = ~0ULL;
575 11432 : fm.fiemap.fm_flags = FIEMAP_FLAG_SYNC; /* required to ensure that just created files report a valid address and not 0 */
576 11432 : fm.fiemap.fm_extent_count = 1; /* we are interested only at the first block */
577 :
578 11432 : if (ioctl(f, FS_IOC_FIEMAP, &fm) != -1) {
579 11432 : uint32_t flags = fm.fiemap.fm_extents[0].fe_flags;
580 11432 : uint64_t offset = fm.fiemap.fm_extents[0].fe_physical;
581 :
582 : /* check some condition for validating the offset */
583 11432 : if (flags & FIEMAP_EXTENT_DATA_INLINE) {
584 : /* if the data is inline, we don't have an offset to report */
585 0 : *physical = FILEPHY_WITHOUT_OFFSET;
586 11432 : } else if (flags & FIEMAP_EXTENT_UNKNOWN) {
587 : /* if the offset is unknown, we don't have an offset to report */
588 0 : *physical = FILEPHY_WITHOUT_OFFSET;
589 11432 : } else if (offset == 0) {
590 : /* 0 is the general fallback for file-systems when */
591 : /* they don't have an offset to report */
592 14 : *physical = FILEPHY_WITHOUT_OFFSET;
593 : } else {
594 : /* finally report the real offset */
595 11418 : *physical = offset + FILEPHY_REAL_OFFSET;
596 : }
597 :
598 11432 : if (close(f) == -1)
599 0 : return -1;
600 11432 : return 0;
601 : }
602 :
603 : /* if the file is empty, FIBMAP doesn't work, and we don't even try to use it */
604 0 : if (size == 0) {
605 0 : *physical = FILEPHY_WITHOUT_OFFSET;
606 0 : if (close(f) == -1)
607 0 : return -1;
608 0 : return 0;
609 : }
610 :
611 : /* then try with FIBMAP */
612 : /* it works for jfs, reiserfs, ntfs-3g */
613 : /* in exfat it always returns 0, that it's anyway better than the fake inodes */
614 0 : blknum = 0; /* first block */
615 0 : if (ioctl(f, FIBMAP, &blknum) != -1) {
616 0 : *physical = blknum + FILEPHY_REAL_OFFSET;
617 0 : if (close(f) == -1)
618 0 : return -1;
619 0 : return 0;
620 : }
621 :
622 : /* otherwise don't use anything, and keep the directory traversal order */
623 : /* at now this should happen only for vfat */
624 : /* and it's surely better than using fake inodes */
625 0 : *physical = FILEPHY_UNREPORTED_OFFSET;
626 0 : if (close(f) == -1)
627 0 : return -1;
628 : #else
629 : /* In a generic Unix use a dummy value for all the files */
630 : /* We don't want to risk to use the inode without knowing */
631 : /* if it really improves performance. */
632 : /* In this way we keep them in the directory traversal order */
633 : /* that at least keeps files in the same directory together. */
634 : /* Note also that in newer file-system with snapshot, like ZFS, */
635 : /* the inode doesn't represent evenmore the disk position, because files */
636 : /* are not overwritten in place, but rewritten in another location */
637 : /* of the disk. */
638 : *physical = FILEPHY_UNREPORTED_OFFSET;
639 :
640 : (void)path; /* not used here */
641 : (void)size;
642 : #endif
643 :
644 0 : return 0;
645 : }
646 :
647 5483 : int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_hardlinks, uint64_t* total_space, uint64_t* free_space)
648 : {
649 : char type[64];
650 : const char* ptype;
651 :
652 : #if HAVE_STATFS
653 : struct statfs st;
654 :
655 5483 : if (statfs(path, &st) != 0) {
656 : char dir[PATH_MAX];
657 : char* slash;
658 :
659 24 : if (errno != ENOENT) {
660 0 : return -1;
661 : }
662 :
663 : /* if it doesn't exist, we assume a file */
664 : /* and we check for the containing dir */
665 24 : if (strlen(path) + 1 > sizeof(dir)) {
666 0 : errno = ENAMETOOLONG;
667 0 : return -1;
668 : }
669 :
670 24 : strcpy(dir, path);
671 :
672 24 : slash = strrchr(dir, '/');
673 24 : if (!slash)
674 0 : return -1;
675 :
676 24 : *slash = 0;
677 24 : if (statfs(dir, &st) != 0)
678 0 : return -1;
679 : }
680 : #endif
681 :
682 : /* to get the fs type check "man stat" or "stat -f -t FILE" */
683 5483 : if (has_persistent_inode) {
684 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
685 575 : switch (st.f_type) {
686 : case 0x65735546 : /* FUSE, "fuseblk" in the stat command */
687 : case 0x4d44 : /* VFAT, "msdos" in the stat command */
688 0 : *has_persistent_inode = 0;
689 0 : break;
690 : default :
691 : /* by default assume yes */
692 575 : *has_persistent_inode = 1;
693 575 : break;
694 : }
695 : #else
696 : /* in Unix inodes are persistent by default */
697 : *has_persistent_inode = 1;
698 : #endif
699 : }
700 :
701 5483 : if (has_syncronized_hardlinks) {
702 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
703 575 : switch (st.f_type) {
704 : case 0x5346544E : /* NTFS */
705 : case 0x4d44 : /* VFAT, "msdos" in the stat command */
706 0 : *has_syncronized_hardlinks = 0;
707 0 : break;
708 : default :
709 : /* by default assume yes */
710 575 : *has_syncronized_hardlinks = 1;
711 575 : break;
712 : }
713 : #else
714 : /* in Unix hardlinks share the same metadata by default */
715 : *has_syncronized_hardlinks = 1;
716 : #endif
717 : }
718 :
719 5483 : if (total_space) {
720 : #if HAVE_STATFS
721 4908 : *total_space = st.f_bsize * (uint64_t)st.f_blocks;
722 : #else
723 : *total_space = 0;
724 : #endif
725 : }
726 :
727 5483 : if (free_space) {
728 : #if HAVE_STATFS
729 4908 : *free_space = st.f_bsize * (uint64_t)st.f_bfree;
730 : #else
731 : *free_space = 0;
732 : #endif
733 : }
734 :
735 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_FSTYPENAME
736 : /* get the filesystem type directly from the struct (Mac OS X) */
737 : (void)type;
738 : ptype = st.f_fstypename;
739 : #elif HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
740 : /* get the filesystem type from f_type (Linux) */
741 : /* from: https://github.com/influxdata/gopsutil/blob/master/disk/disk_linux.go */
742 5483 : switch (st.f_type) {
743 0 : case 0x65735546 : ptype = "fuseblk"; break;
744 0 : case 0x4D44 : ptype = "vfat/msdos"; break;
745 5483 : case 0xEF53 : ptype = "ext2/3/4"; break;
746 0 : case 0x6969 : ptype = "nfs"; break; /* remote */
747 0 : case 0x6E667364 : ptype = "nfsd"; break; /* remote */
748 0 : case 0x517B : ptype = "smb"; break; /* remote */
749 0 : case 0x5346544E : ptype = "ntfs"; break;
750 0 : case 0x52654973 : ptype = "reiserfs"; break;
751 0 : case 0x3153464A : ptype = "jfs"; break;
752 0 : case 0x58465342 : ptype = "xfs"; break;
753 0 : case 0x9123683E : ptype = "btrfs"; break;
754 0 : case 0x2FC12FC1 : ptype = "zfs"; break;
755 : default :
756 0 : snprintf(type, sizeof(type), "0x%X", (unsigned)st.f_type);
757 0 : ptype = type;
758 : }
759 : #else
760 : (void)type;
761 : ptype = "unknown";
762 : #endif
763 :
764 5483 : log_tag("statfs:%s: %s \n", ptype, path);
765 :
766 5483 : return 0;
767 : }
768 :
769 3818188 : uint64_t tick(void)
770 : {
771 : #if HAVE_MACH_ABSOLUTE_TIME
772 : /* for Mac OS X */
773 : return mach_absolute_time();
774 : #elif HAVE_CLOCK_GETTIME && (defined(CLOCK_MONOTONIC) || defined(CLOCK_MONOTONIC_RAW))
775 : /* for Linux */
776 : struct timespec tv;
777 :
778 : /* nanosecond precision with clock_gettime() */
779 : #if defined(CLOCK_MONOTONIC_RAW)
780 3818188 : if (clock_gettime(CLOCK_MONOTONIC_RAW, &tv) != 0) {
781 : #else
782 : if (clock_gettime(CLOCK_MONOTONIC, &tv) != 0) {
783 : #endif
784 0 : return 0;
785 : }
786 :
787 3818188 : return tv.tv_sec * 1000000000ULL + tv.tv_nsec;
788 : #else
789 : /* other platforms */
790 : struct timeval tv;
791 :
792 : /* microsecond precision with gettimeofday() */
793 : if (gettimeofday(&tv, 0) != 0) {
794 : return 0;
795 : }
796 :
797 : return tv.tv_sec * 1000000ULL + tv.tv_usec;
798 : #endif
799 : }
800 :
801 65 : uint64_t tick_ms(void)
802 : {
803 : struct timeval tv;
804 :
805 65 : if (gettimeofday(&tv, 0) != 0)
806 0 : return 0;
807 :
808 65 : return tv.tv_sec * 1000ULL + tv.tv_usec / 1000;
809 : }
810 :
811 282 : int randomize(void* ptr, size_t size)
812 : {
813 : int f;
814 : ssize_t ret;
815 :
816 282 : f = open("/dev/urandom", O_RDONLY);
817 282 : if (f == -1)
818 0 : return -1;
819 :
820 282 : ret = read(f, ptr, size);
821 282 : if (ret < 0 || (size_t)ret != size) {
822 0 : close(f);
823 0 : return -1;
824 : }
825 :
826 282 : if (close(f) != 0)
827 0 : return -1;
828 :
829 282 : return 0;
830 : }
831 :
832 : /**
833 : * Read a file extracting the contained device number in %u:%u format.
834 : * Return 0 on error.
835 : */
836 : #if HAVE_LINUX_DEVICE
837 79 : static dev_t devread(const char* path)
838 : {
839 : int f;
840 : int ret;
841 : int len;
842 : char buf[64];
843 : char* e;
844 : unsigned ma;
845 : unsigned mi;
846 :
847 79 : f = open(path, O_RDONLY);
848 79 : if (f == -1) {
849 : /* LCOV_EXCL_START */
850 : log_fatal("Failed to open '%s'.\n", path);
851 : return 0;
852 : /* LCOV_EXCL_STOP */
853 : }
854 :
855 79 : len = read(f, buf, sizeof(buf));
856 79 : if (len < 0) {
857 : /* LCOV_EXCL_START */
858 : close(f);
859 : log_fatal("Failed to read '%s'.\n", path);
860 : return 0;
861 : /* LCOV_EXCL_STOP */
862 : }
863 79 : if (len == sizeof(buf)) {
864 : /* LCOV_EXCL_START */
865 : close(f);
866 : log_fatal("Too long read '%s'.\n", path);
867 : return 0;
868 : /* LCOV_EXCL_STOP */
869 : }
870 :
871 79 : ret = close(f);
872 79 : if (ret != 0) {
873 : /* LCOV_EXCL_START */
874 : log_fatal("Failed to close '%s'.\n", path);
875 : return 0;
876 : /* LCOV_EXCL_STOP */
877 : }
878 :
879 79 : buf[len] = 0;
880 :
881 79 : ma = strtoul(buf, &e, 10);
882 79 : if (*e != ':') {
883 : /* LCOV_EXCL_START */
884 : log_fatal("Invalid format in '%s' for '%s'.\n", path, buf);
885 : return 0;
886 : /* LCOV_EXCL_STOP */
887 : }
888 :
889 79 : mi = strtoul(e + 1, &e, 10);
890 79 : if (*e != 0 && !isspace(*e)) {
891 : /* LCOV_EXCL_START */
892 : log_fatal("Invalid format in '%s' for '%s'.\n", path, buf);
893 : return 0;
894 : /* LCOV_EXCL_STOP */
895 : }
896 :
897 79 : return makedev(ma, mi);
898 : }
899 : #endif
900 :
901 : /**
902 : * Read a device tree filling the specified list of disk_t entries.
903 : */
904 : #if HAVE_LINUX_DEVICE
905 65 : static int devtree(const char* name, const char* custom, dev_t device, devinfo_t* parent, tommy_list* list)
906 : {
907 : char path[PATH_MAX];
908 : DIR* d;
909 65 : int slaves = 0;
910 :
911 65 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/slaves", major(device), minor(device));
912 :
913 : /* check if there is a slaves list */
914 65 : d = opendir(path);
915 65 : if (d != 0) {
916 : struct dirent* dd;
917 :
918 0 : while ((dd = readdir(d)) != 0) {
919 0 : if (dd->d_name[0] != '.') {
920 : /* for each slave, expand the full potential tree */
921 0 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/slaves/%s/dev", major(device), minor(device), dd->d_name);
922 :
923 0 : device = devread(path);
924 0 : if (!device) {
925 : /* LCOV_EXCL_START */
926 : closedir(d);
927 : return -1;
928 : /* LCOV_EXCL_STOP */
929 : }
930 :
931 0 : if (devtree(name, custom, device, parent, list) != 0) {
932 : /* LCOV_EXCL_START */
933 : closedir(d);
934 : return -1;
935 : /* LCOV_EXCL_STOP */
936 : }
937 :
938 0 : ++slaves;
939 : }
940 : }
941 :
942 0 : closedir(d);
943 : }
944 :
945 : /* if no slaves found */
946 65 : if (!slaves) {
947 : /* this is a raw device */
948 : devinfo_t* devinfo;
949 :
950 : /* check if it's a real device */
951 65 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device", major(device), minor(device));
952 65 : if (access(path, F_OK) != 0) {
953 : /* get the parent device */
954 65 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/../dev", major(device), minor(device));
955 :
956 65 : device = devread(path);
957 65 : if (!device) {
958 : /* LCOV_EXCL_START */
959 : return -1;
960 : /* LCOV_EXCL_STOP */
961 : }
962 : }
963 :
964 : /* get the device file */
965 65 : if (devresolve(device, path, sizeof(path)) != 0) {
966 : /* LCOV_EXCL_START */
967 : log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
968 : return -1;
969 : /* LCOV_EXCL_STOP */
970 : }
971 :
972 65 : devinfo = calloc_nofail(1, sizeof(devinfo_t));
973 :
974 65 : devinfo->device = device;
975 65 : pathcpy(devinfo->name, sizeof(devinfo->name), name);
976 65 : pathcpy(devinfo->smartctl, sizeof(devinfo->smartctl), custom);
977 65 : pathcpy(devinfo->file, sizeof(devinfo->file), path);
978 65 : devinfo->parent = parent;
979 :
980 : /* insert in the list */
981 65 : tommy_list_insert_tail(list, &devinfo->node, devinfo);
982 : }
983 :
984 65 : return 0;
985 : }
986 : #endif
987 :
988 : /**
989 : * Scan all the devices.
990 : *
991 : * If a device is already in, it's not added again.
992 : */
993 : #if HAVE_LINUX_DEVICE
994 1 : static int devscan(tommy_list* list)
995 : {
996 : char dir[PATH_MAX];
997 : DIR* d;
998 : struct dirent* dd;
999 :
1000 1 : pathprint(dir, sizeof(dir), "/sys/dev/block/");
1001 :
1002 : /* check if there is a slaves list */
1003 1 : d = opendir(dir);
1004 1 : if (d == 0) {
1005 : /* LCOV_EXCL_START */
1006 : log_fatal("Failed to open dir '%s'.\n", dir);
1007 : return -1;
1008 : /* LCOV_EXCL_STOP */
1009 : }
1010 :
1011 76 : while ((dd = readdir(d)) != 0) {
1012 : char path[PATH_MAX];
1013 : tommy_node* i;
1014 : dev_t device;
1015 : devinfo_t* devinfo;
1016 :
1017 74 : if (dd->d_name[0] == '.')
1018 63 : continue;
1019 :
1020 72 : pathprint(path, sizeof(path), "/sys/dev/block/%s/device", dd->d_name);
1021 :
1022 : /* check if it's a real device */
1023 72 : if (access(path, F_OK) != 0)
1024 58 : continue;
1025 :
1026 14 : pathprint(path, sizeof(path), "/sys/dev/block/%s/dev", dd->d_name);
1027 :
1028 14 : device = devread(path);
1029 14 : if (!device) {
1030 : /* LCOV_EXCL_START */
1031 : log_tag("scan:skip: Skipping device %s because failed to read its device number.\n", dd->d_name);
1032 : continue;
1033 : /* LCOV_EXCL_STOP */
1034 : }
1035 :
1036 : /* check if already present */
1037 482 : for (i = tommy_list_head(list); i != 0; i = i->next) {
1038 469 : devinfo = i->data;
1039 469 : if (devinfo->device == device)
1040 1 : break;
1041 : }
1042 :
1043 : /* if already present */
1044 14 : if (i != 0)
1045 1 : continue;
1046 :
1047 : /* get the device file */
1048 13 : if (devresolve(device, path, sizeof(path)) != 0) {
1049 : /* LCOV_EXCL_START */
1050 : log_tag("scan:skip: Skipping device %u:%u because failed to resolve.\n", major(device), minor(device));
1051 : continue;
1052 : /* LCOV_EXCL_STOP */
1053 : }
1054 :
1055 13 : devinfo = calloc_nofail(1, sizeof(devinfo_t));
1056 :
1057 13 : devinfo->device = device;
1058 13 : pathcpy(devinfo->file, sizeof(devinfo->file), path);
1059 :
1060 : /* insert in the list */
1061 13 : tommy_list_insert_tail(list, &devinfo->node, devinfo);
1062 : }
1063 :
1064 1 : closedir(d);
1065 1 : return 0;
1066 : }
1067 : #endif
1068 :
1069 : /**
1070 : * Get SMART attributes.
1071 : */
1072 : #if HAVE_LINUX_DEVICE
1073 43 : static int devsmart(dev_t device, const char* name, const char* custom, uint64_t* smart, char* serial, char* vendor, char* model)
1074 : {
1075 : char cmd[128];
1076 : char file[128];
1077 : FILE* f;
1078 : int ret;
1079 :
1080 43 : if (devresolve(device, file, sizeof(file)) != 0) {
1081 : /* LCOV_EXCL_START */
1082 : log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1083 : return -1;
1084 : /* LCOV_EXCL_STOP */
1085 : }
1086 :
1087 : /* if there is a custom command */
1088 43 : if (custom[0]) {
1089 : char option[128];
1090 5 : snprintf(option, sizeof(option), custom, file);
1091 5 : snprintf(cmd, sizeof(cmd), "smartctl -a %s", option);
1092 : } else {
1093 38 : snprintf(cmd, sizeof(cmd), "smartctl -a %s", file);
1094 : }
1095 :
1096 43 : log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
1097 :
1098 43 : f = popen(cmd, "r");
1099 43 : if (!f) {
1100 : /* LCOV_EXCL_START */
1101 : log_fatal("Failed to run '%s' (from popen).\n", cmd);
1102 : return -1;
1103 : /* LCOV_EXCL_STOP */
1104 : }
1105 :
1106 43 : if (smartctl_attribute(f, file, name, smart, serial, vendor, model) != 0) {
1107 : /* LCOV_EXCL_START */
1108 : pclose(f);
1109 : return -1;
1110 : /* LCOV_EXCL_STOP */
1111 : }
1112 :
1113 43 : ret = pclose(f);
1114 :
1115 43 : log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
1116 :
1117 43 : if (!WIFEXITED(ret)) {
1118 : /* LCOV_EXCL_START */
1119 : log_fatal("Failed to run '%s' (not exited).\n", cmd);
1120 : return -1;
1121 : /* LCOV_EXCL_STOP */
1122 : }
1123 43 : if (WEXITSTATUS(ret) == 127) {
1124 : /* LCOV_EXCL_START */
1125 : log_fatal("Failed to run '%s' (from sh).\n", cmd);
1126 : return -1;
1127 : /* LCOV_EXCL_STOP */
1128 : }
1129 :
1130 : /* store the return smartctl return value */
1131 0 : smart[SMART_FLAGS] = WEXITSTATUS(ret);
1132 :
1133 0 : return 0;
1134 : }
1135 : #endif
1136 :
1137 : /**
1138 : * Spin down a specific device.
1139 : */
1140 : #if HAVE_LINUX_DEVICE
1141 5 : static int devdown(dev_t device, const char* name, const char* custom)
1142 : {
1143 : char cmd[128];
1144 : char file[128];
1145 : FILE* f;
1146 : int ret;
1147 :
1148 5 : if (devresolve(device, file, sizeof(file)) != 0) {
1149 : /* LCOV_EXCL_START */
1150 : log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1151 : return -1;
1152 : /* LCOV_EXCL_STOP */
1153 : }
1154 :
1155 : /* if there is a custom command */
1156 5 : if (custom[0]) {
1157 : char option[128];
1158 5 : snprintf(option, sizeof(option), custom, file);
1159 5 : snprintf(cmd, sizeof(cmd), "smartctl -s standby,now %s", option);
1160 : } else {
1161 0 : snprintf(cmd, sizeof(cmd), "smartctl -s standby,now %s", file);
1162 : }
1163 :
1164 5 : log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
1165 :
1166 5 : f = popen(cmd, "r");
1167 5 : if (!f) {
1168 : /* LCOV_EXCL_START */
1169 : log_fatal("Failed to run '%s' (from popen).\n", cmd);
1170 : return -1;
1171 : /* LCOV_EXCL_STOP */
1172 : }
1173 :
1174 5 : if (smartctl_flush(f, file, name) != 0) {
1175 : /* LCOV_EXCL_START */
1176 : pclose(f);
1177 : return -1;
1178 : /* LCOV_EXCL_STOP */
1179 : }
1180 :
1181 5 : ret = pclose(f);
1182 :
1183 5 : log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
1184 :
1185 5 : if (!WIFEXITED(ret)) {
1186 : /* LCOV_EXCL_START */
1187 : log_fatal("Failed to run '%s' (not exited).\n", cmd);
1188 : return -1;
1189 : /* LCOV_EXCL_STOP */
1190 : }
1191 5 : if (WEXITSTATUS(ret) == 127) {
1192 : /* LCOV_EXCL_START */
1193 : log_fatal("Failed to run '%s' (from sh).\n", cmd);
1194 : return -1;
1195 : /* LCOV_EXCL_STOP */
1196 : }
1197 0 : if (WEXITSTATUS(ret) != 0) {
1198 : /* LCOV_EXCL_START */
1199 : log_fatal("Failed to run '%s' with return code %xh.\n", cmd, WEXITSTATUS(ret));
1200 : return -1;
1201 : /* LCOV_EXCL_STOP */
1202 : }
1203 :
1204 0 : return 0;
1205 : }
1206 : #endif
1207 :
1208 : /**
1209 : * Spin up a device.
1210 : *
1211 : * There isn't a defined way to spin up a device,
1212 : * so we just do a generic write.
1213 : */
1214 30 : static int devup(const char* mountpoint)
1215 : {
1216 : int ret;
1217 : char path[PATH_MAX];
1218 :
1219 : /* add a temporary name used for writing */
1220 30 : pathprint(path, sizeof(path), "%s.snapraid-spinup", mountpoint);
1221 :
1222 : /* do a generic write, and immediately undo it */
1223 30 : ret = mkdir(path, 0);
1224 30 : if (ret != 0 && errno != EEXIST) {
1225 : /* LCOV_EXCL_START */
1226 : log_fatal("Failed to create dir '%s'.\n", path);
1227 : return -1;
1228 : /* LCOV_EXCL_STOP */
1229 : }
1230 :
1231 : /* remove the just created dir */
1232 30 : rmdir(path);
1233 :
1234 30 : return 0;
1235 : }
1236 :
1237 : /**
1238 : * Thread for spinning up.
1239 : *
1240 : * Note that filling up the devinfo object is done inside this thread,
1241 : * to avoid to block the main thread if the device need to be spin up
1242 : * to handle stat/resolve requests.
1243 : */
1244 30 : static void* thread_spinup(void* arg)
1245 : {
1246 30 : devinfo_t* devinfo = arg;
1247 : struct stat st;
1248 : uint64_t start;
1249 :
1250 30 : start = tick_ms();
1251 :
1252 : /* first get the device number, this usually doesn't trigger a thread_spinup */
1253 30 : if (stat(devinfo->mount, &st) != 0) {
1254 : /* LCOV_EXCL_START */
1255 : log_fatal("Failed to stat device '%s'.\n", devinfo->mount);
1256 : return (void*)-1;
1257 : /* LCOV_EXCL_STOP */
1258 : }
1259 :
1260 : /* set the device number for printing */
1261 30 : devinfo->device = st.st_dev;
1262 :
1263 30 : if (devup(devinfo->mount) != 0) {
1264 : /* LCOV_EXCL_START */
1265 : return (void*)-1;
1266 : /* LCOV_EXCL_STOP */
1267 : }
1268 :
1269 30 : msg_status("Spunup device '%u:%u' for disk '%s' in %" PRIu64 " ms.\n", major(devinfo->device), minor(devinfo->device), devinfo->name, tick_ms() - start);
1270 :
1271 30 : return 0;
1272 : }
1273 :
1274 : /**
1275 : * Thread for spinning down.
1276 : */
1277 5 : static void* thread_spindown(void* arg)
1278 : {
1279 : #if HAVE_LINUX_DEVICE
1280 5 : devinfo_t* devinfo = arg;
1281 : uint64_t start;
1282 :
1283 5 : start = tick_ms();
1284 :
1285 5 : if (devdown(devinfo->device, devinfo->name, devinfo->smartctl) != 0) {
1286 : /* LCOV_EXCL_START */
1287 : return (void*)-1;
1288 : /* LCOV_EXCL_STOP */
1289 : }
1290 :
1291 0 : msg_status("Spundown device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, tick_ms() - start);
1292 :
1293 0 : return 0;
1294 : #else
1295 : (void)arg;
1296 : return (void*)-1;
1297 : #endif
1298 : }
1299 :
1300 : /**
1301 : * Thread for getting smart info.
1302 : */
1303 43 : static void* thread_smart(void* arg)
1304 : {
1305 : #if HAVE_LINUX_DEVICE
1306 43 : devinfo_t* devinfo = arg;
1307 :
1308 43 : if (devsmart(devinfo->device, devinfo->name, devinfo->smartctl, devinfo->smart, devinfo->smart_serial, devinfo->smart_vendor, devinfo->smart_model) != 0) {
1309 : /* LCOV_EXCL_START */
1310 : return (void*)-1;
1311 : /* LCOV_EXCL_STOP */
1312 : }
1313 :
1314 0 : return 0;
1315 : #else
1316 : (void)arg;
1317 : return (void*)-1;
1318 : #endif
1319 : }
1320 :
1321 3 : static int device_thread(tommy_list* list, void* (*func)(void* arg))
1322 : {
1323 3 : int fail = 0;
1324 : tommy_node* i;
1325 :
1326 : #if HAVE_PTHREAD
1327 : /* start all threads */
1328 81 : for (i = tommy_list_head(list); i != 0; i = i->next) {
1329 78 : devinfo_t* devinfo = i->data;
1330 :
1331 78 : thread_create(&devinfo->thread, 0, func, devinfo);
1332 : }
1333 :
1334 : /* join all threads */
1335 81 : for (i = tommy_list_head(list); i != 0; i = i->next) {
1336 78 : devinfo_t* devinfo = i->data;
1337 : void* retval;
1338 :
1339 78 : thread_join(devinfo->thread, &retval);
1340 :
1341 78 : if (retval != 0)
1342 48 : ++fail;
1343 : }
1344 : #else
1345 : for (i = tommy_list_head(list); i != 0; i = i->next) {
1346 : devinfo_t* devinfo = i->data;
1347 :
1348 : if (func(devinfo) != 0)
1349 : ++fail;
1350 : }
1351 : #endif
1352 3 : if (fail != 0) {
1353 : /* LCOV_EXCL_START */
1354 : return -1;
1355 : /* LCOV_EXCL_STOP */
1356 : }
1357 :
1358 1 : return 0;
1359 : }
1360 :
1361 4 : int devquery(tommy_list* high, tommy_list* low, int operation, int others)
1362 : {
1363 : tommy_node* i;
1364 4 : void* (*func)(void* arg) = 0;
1365 :
1366 : #if HAVE_LINUX_DEVICE
1367 4 : if (operation != DEVICE_UP) {
1368 : struct stat st;
1369 : /* sysfs interface is required */
1370 3 : if (stat("/sys/dev/block", &st) != 0) {
1371 : /* LCOV_EXCL_START */
1372 : log_fatal("Missing interface /sys/dev/block.\n");
1373 : return -1;
1374 : /* LCOV_EXCL_STOP */
1375 : }
1376 :
1377 : /* for each device */
1378 136 : for (i = tommy_list_head(high); i != 0; i = i->next) {
1379 65 : devinfo_t* devinfo = i->data;
1380 65 : uint64_t device = devinfo->device;
1381 :
1382 : /* if the major is the null device, find the real one */
1383 65 : if (major(device) == 0) {
1384 : /* obtain the real device */
1385 0 : if (devdereference(device, &device) != 0) {
1386 : /* LCOV_EXCL_START */
1387 : log_fatal("Failed to dereference device '%u:%u'.\n", major(device), minor(device));
1388 : return -1;
1389 : /* LCOV_EXCL_STOP */
1390 : }
1391 : }
1392 :
1393 : /* get the device file */
1394 65 : if (devresolve(device, devinfo->file, sizeof(devinfo->file)) != 0) {
1395 : /* LCOV_EXCL_START */
1396 : log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1397 : return -1;
1398 : /* LCOV_EXCL_STOP */
1399 : }
1400 :
1401 : /* expand the tree of devices */
1402 65 : if (devtree(devinfo->name, devinfo->smartctl, device, devinfo, low) != 0) {
1403 : /* LCOV_EXCL_START */
1404 : log_fatal("Failed to expand device '%u:%u'.\n", major(device), minor(device));
1405 : return -1;
1406 : /* LCOV_EXCL_STOP */
1407 : }
1408 : }
1409 : }
1410 : #endif
1411 :
1412 4 : if (operation == DEVICE_UP) {
1413 : /* duplicate the high */
1414 31 : for (i = tommy_list_head(high); i != 0; i = i->next) {
1415 30 : devinfo_t* devinfo = i->data;
1416 : devinfo_t* entry;
1417 :
1418 30 : entry = calloc_nofail(1, sizeof(devinfo_t));
1419 :
1420 30 : entry->device = devinfo->device;
1421 30 : pathcpy(entry->name, sizeof(entry->name), devinfo->name);
1422 30 : pathcpy(entry->mount, sizeof(entry->mount), devinfo->mount);
1423 :
1424 : /* insert in the high */
1425 30 : tommy_list_insert_tail(low, &entry->node, entry);
1426 : }
1427 : }
1428 :
1429 : #if HAVE_LINUX_DEVICE
1430 : /* add other devices */
1431 4 : if (others) {
1432 1 : if (devscan(low) != 0) {
1433 : /* LCOV_EXCL_START */
1434 : log_fatal("Failed to list other devices.\n");
1435 : return -1;
1436 : /* LCOV_EXCL_STOP */
1437 : }
1438 : }
1439 : #else
1440 : (void)others;
1441 : #endif
1442 :
1443 4 : switch (operation) {
1444 1 : case DEVICE_UP : func = thread_spinup; break;
1445 1 : case DEVICE_DOWN : func = thread_spindown; break;
1446 1 : case DEVICE_SMART : func = thread_smart; break;
1447 : }
1448 :
1449 4 : if (!func)
1450 1 : return 0;
1451 :
1452 3 : return device_thread(low, func);
1453 : }
1454 :
1455 271 : void os_init(int opt)
1456 : {
1457 : #if HAVE_BLKID
1458 : int ret;
1459 271 : ret = blkid_get_cache(&cache, NULL);
1460 271 : if (ret != 0) {
1461 : /* LCOV_EXCL_START */
1462 : log_fatal("WARNING Failed to get blkid cache\n");
1463 : /* LCOV_EXCL_STOP */
1464 : }
1465 : #endif
1466 :
1467 : (void)opt;
1468 271 : }
1469 :
1470 248 : void os_done(void)
1471 : {
1472 : #if HAVE_BLKID
1473 248 : if (cache != 0)
1474 248 : blkid_put_cache(cache);
1475 : #endif
1476 248 : }
1477 :
1478 : /* LCOV_EXCL_START */
1479 : void os_abort(void)
1480 : {
1481 : #if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS
1482 : void* stack[32];
1483 : char** messages;
1484 : size_t size;
1485 : unsigned i;
1486 : #endif
1487 :
1488 : printf("Stacktrace of " PACKAGE " v" VERSION);
1489 : #ifdef _linux
1490 : printf(", linux");
1491 : #endif
1492 : #ifdef __GNUC__
1493 : printf(", gcc " __VERSION__);
1494 : #endif
1495 : printf(", %d-bit", (int)sizeof(void *) * 8);
1496 : printf(", PATH_MAX=%d", PATH_MAX);
1497 : printf("\n");
1498 :
1499 : #if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS
1500 : size = backtrace(stack, 32);
1501 :
1502 : messages = backtrace_symbols(stack, size);
1503 :
1504 : for (i = 1; i < size; ++i) {
1505 : const char* msg;
1506 :
1507 : if (messages)
1508 : msg = messages[i];
1509 : else
1510 : msg = "<unknown>";
1511 :
1512 : printf("[bt] %02u: %s\n", i, msg);
1513 :
1514 : if (messages) {
1515 : int ret;
1516 : char addr2line[1024];
1517 : size_t j = 0;
1518 : while (msg[j] != '(' && msg[j] != ' ' && msg[j] != 0)
1519 : ++j;
1520 :
1521 : snprintf(addr2line, sizeof(addr2line), "addr2line %p -e %.*s", stack[i], (unsigned)j, msg);
1522 :
1523 : ret = system(addr2line);
1524 : if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0)
1525 : printf("exit:%d\n", WEXITSTATUS(ret));
1526 : if (WIFSIGNALED(ret))
1527 : printf("signal:%d\n", WTERMSIG(ret));
1528 : }
1529 : }
1530 : #endif
1531 :
1532 : printf("Please report this error to the SnapRAID Forum:\n");
1533 : printf("https://sourceforge.net/p/snapraid/discussion/1677233/\n");
1534 :
1535 : abort();
1536 : }
1537 : /* LCOV_EXCL_STOP */
1538 :
1539 1 : void os_clear(void)
1540 : {
1541 : /* ANSI codes */
1542 1 : printf("\033[H"); /* cursor at topleft */
1543 1 : printf("\033[2J"); /* clear screen */
1544 1 : }
1545 :
1546 7 : size_t direct_size(void)
1547 : {
1548 : long size;
1549 :
1550 7 : size = sysconf(_SC_PAGESIZE);
1551 :
1552 7 : if (size == -1) {
1553 : /* LCOV_EXCL_START */
1554 : log_fatal("No page size\n");
1555 : exit(EXIT_FAILURE);
1556 : /* LCOV_EXCL_STOP */
1557 : }
1558 :
1559 7 : return size;
1560 : }
1561 :
1562 : #endif
1563 :
|