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 1716677 : int open_noatime(const char* file, int flags)
32 : {
33 : #ifdef O_NOATIME
34 1716677 : int f = open(file, flags | O_NOATIME);
35 :
36 : /* only root is allowed to use O_NOATIME, in case retry without it */
37 1716677 : if (f == -1 && errno == EPERM)
38 0 : f = open(file, flags);
39 1716677 : 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 : * Read a file from sys
73 : *
74 : * Return -1 on error, otherwise the data read
75 : */
76 : #if HAVE_LINUX_DEVICE
77 177 : static int sysread(const char* path, char* buf, size_t buf_size)
78 : {
79 : int f;
80 : int ret;
81 : int len;
82 :
83 177 : f = open(path, O_RDONLY);
84 177 : if (f == -1) {
85 : /* LCOV_EXCL_START */
86 : log_fatal("Failed to open '%s'.\n", path);
87 : return -1;
88 : /* LCOV_EXCL_STOP */
89 : }
90 :
91 177 : len = read(f, buf, buf_size);
92 177 : if (len < 0) {
93 : /* LCOV_EXCL_START */
94 : close(f);
95 : log_fatal("Failed to read '%s'.\n", path);
96 : return -1;
97 : /* LCOV_EXCL_STOP */
98 : }
99 :
100 177 : ret = close(f);
101 177 : if (ret != 0) {
102 : /* LCOV_EXCL_START */
103 : log_fatal("Failed to close '%s'.\n", path);
104 : return -1;
105 : /* LCOV_EXCL_STOP */
106 : }
107 :
108 177 : if (len == (int)buf_size)
109 0 : buf[buf_size - 1] = 0;
110 : else
111 177 : buf[len] = 0;
112 :
113 177 : return len;
114 : }
115 : #endif
116 :
117 : /**
118 : * Get the device file from the device number.
119 : *
120 : * It uses /proc/self/mountinfo.
121 : *
122 : * Null devices (major==0) are resolved to the device indicated in mountinfo.
123 : */
124 : #if HAVE_LINUX_DEVICE
125 4 : static int devresolve_proc(uint64_t device, char* path, size_t path_size)
126 : {
127 : FILE* f;
128 : char match[32];
129 :
130 : /* generate the matching string */
131 4 : snprintf(match, sizeof(match), "%u:%u", major(device), minor(device));
132 :
133 4 : f = fopen("/proc/self/mountinfo", "r");
134 4 : if (!f) {
135 0 : log_tag("resolve:proc:%u:%u: failed to open /proc/self/mountinfo\n", major(device), minor(device));
136 0 : return -1;
137 : }
138 :
139 : /*
140 : * mountinfo format
141 : * 0 - mount ID
142 : * 1 - parent ID
143 : * 2 - major:minor
144 : * 3 - root
145 : * 4 - mount point
146 : * 5 - options
147 : * 6 - "-" (separator)
148 : * 7 - fs
149 : * 8 - mount source - /dev/device
150 : */
151 :
152 184 : while (1) {
153 : char buf[256];
154 : char* first_map[8];
155 : unsigned first_mac;
156 : char* second_map[8];
157 : unsigned second_mac;
158 : char* s;
159 : struct stat st;
160 : char* separator;
161 : char* majorminor;
162 : char* mountpoint;
163 : char* fs;
164 : char* mountsource;
165 :
166 188 : s = fgets(buf, sizeof(buf), f);
167 188 : if (s == 0)
168 4 : break;
169 :
170 : /* find the separator position */
171 184 : separator = strstr(s, " - ");
172 184 : if (!separator)
173 0 : continue;
174 :
175 : /* skip the separator */
176 184 : *separator = 0;
177 184 : separator += 3;
178 :
179 : /* split the line */
180 184 : first_mac = strsplit(first_map, 8, s, " \t\r\n");
181 184 : second_mac = strsplit(second_map, 8, separator, " \t\r\n");
182 :
183 : /* if too short, it's the wrong line */
184 184 : if (first_mac < 5)
185 0 : continue;
186 184 : if (second_mac < 2)
187 0 : continue;
188 :
189 184 : majorminor = first_map[2];
190 184 : mountpoint = first_map[4];
191 184 : fs = second_map[0];
192 184 : mountsource = second_map[1];
193 :
194 : /* compare major:minor from mountinfo */
195 184 : if (strcmp(majorminor, match) == 0) {
196 : /*
197 : * Accept only /dev/... mountsource
198 : *
199 : * This excludes ZFS that uses a bare label for mountsource, like "tank".
200 : *
201 : * 410 408 0:193 / /XXX rw,relatime shared:217 - zfs tank/system/data/var/lib/docker/XXX rw,xattr,noacl
202 : *
203 : * Also excludes AUTOFS unmounted devices that point to a fake filesystem
204 : * used to remount them at the first use.
205 : *
206 : * 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
207 : */
208 0 : if (strncmp(mountsource, "/dev/", 5) != 0) {
209 0 : log_tag("resolve:proc:%u:%u: match skipped for not /dev/ mountsource for %s %s\n", major(device), minor(device), fs, mountsource);
210 0 : continue;
211 : }
212 :
213 0 : pathcpy(path, path_size, mountsource);
214 :
215 0 : log_tag("resolve:proc:%u:%u: found device %s matching device %s\n", major(device), minor(device), path, match);
216 :
217 0 : fclose(f);
218 0 : return 0;
219 : }
220 :
221 : /* get the device of the mount point */
222 : /* in Btrfs it could be different than the one in mountinfo */
223 184 : if (stat(mountpoint, &st) == 0 && st.st_dev == device) {
224 0 : if (strncmp(mountsource, "/dev/", 5) != 0) {
225 0 : log_tag("resolve:proc:%u:%u: match skipped for not /dev/ mountsource for %s %s\n", major(device), minor(device), fs, mountsource);
226 0 : continue;
227 : }
228 :
229 0 : pathcpy(path, path_size, mountsource);
230 :
231 0 : log_tag("resolve:proc:%u:%u: found device %s matching mountpoint %s\n", major(device), minor(device), path, mountpoint);
232 :
233 0 : fclose(f);
234 0 : return 0;
235 : }
236 : }
237 :
238 4 : log_tag("resolve:proc:%u:%u: not found\n", major(device), minor(device));
239 :
240 4 : fclose(f);
241 4 : return -1;
242 : }
243 : #endif
244 :
245 : /**
246 : * Get the device of a virtual superblock.
247 : *
248 : * This is intended to resolve the case of Btrfs filesystems that
249 : * create a virtual superblock (major==0) not backed by any low
250 : * level device.
251 : *
252 : * See:
253 : * Bug 711881 - too funny btrfs st_dev numbers
254 : * https://bugzilla.redhat.com/show_bug.cgi?id=711881
255 : */
256 : #if HAVE_LINUX_DEVICE
257 4 : static int devdereference(uint64_t device, uint64_t* new_device)
258 : {
259 : char path[PATH_MAX];
260 : struct stat st;
261 :
262 : /* use the proc interface to get the device containing the filesystem */
263 4 : if (devresolve_proc(device, path, sizeof(path)) != 0) {
264 : /* LCOV_EXCL_START */
265 : return -1;
266 : /* LCOV_EXCL_STOP */
267 : }
268 :
269 : /* check the device */
270 0 : if (stat(path, &st) != 0) {
271 : /* LCOV_EXCL_START */
272 : log_tag("dereference:%u:%u: failed to stat %s\n", major(device), minor(device), path);
273 : return -1;
274 : /* LCOV_EXCL_STOP */
275 : }
276 :
277 0 : if (major(st.st_rdev) == 0) {
278 : /* LCOV_EXCL_START */
279 : 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));
280 : return -1;
281 : /* LCOV_EXCL_STOP */
282 : }
283 :
284 0 : *new_device = st.st_rdev;
285 0 : log_tag("dereference:%u:%u: found %u:%u\n", major(device), minor(device), major(st.st_rdev), minor(st.st_rdev));
286 0 : return 0;
287 : }
288 : #endif
289 :
290 : /**
291 : * Read a file extracting the specified tag TAG=VALUE format.
292 : * Return !=0 on error.
293 : */
294 : #if HAVE_LINUX_DEVICE
295 165 : static int tagread(const char* path, const char* tag, char* value, size_t value_size)
296 : {
297 : int ret;
298 : char buf[512];
299 : size_t tag_len;
300 : char* i;
301 : char* e;
302 :
303 165 : ret = sysread(path, buf, sizeof(buf));
304 165 : if (ret < 0)
305 0 : return -1;
306 165 : if (ret == sizeof(buf)) {
307 : /* LCOV_EXCL_START */
308 : log_fatal("Too long read '%s'.\n", path);
309 : return -1;
310 : /* LCOV_EXCL_STOP */
311 : }
312 :
313 165 : tag_len = strlen(tag);
314 :
315 2870 : for (i = buf; *i; ++i) {
316 2870 : char* p = i;
317 :
318 : /* start with a space */
319 2870 : if (p != buf) {
320 2705 : if (!isspace(*p))
321 2375 : continue;
322 330 : ++p;
323 : }
324 :
325 495 : if (strncmp(p, tag, tag_len) != 0)
326 330 : continue;
327 165 : p += tag_len;
328 :
329 : /* end with a = */
330 165 : if (*p != '=')
331 0 : continue;
332 165 : ++p;
333 :
334 : /* found */
335 165 : i = p;
336 165 : break;
337 : }
338 165 : if (!*i) {
339 : /* LCOV_EXCL_START */
340 : log_fatal("Missing tag '%s' for '%s'.\n", tag, path);
341 : return -1;
342 : /* LCOV_EXCL_STOP */
343 : }
344 :
345 : /* terminate at the first space */
346 165 : e = i;
347 725 : while (*e != 0 && !isspace(*e))
348 560 : ++e;
349 165 : *e = 0;
350 :
351 165 : if (!*i) {
352 : /* LCOV_EXCL_START */
353 : log_fatal("Empty tag '%s' for '%s'.\n", tag, path);
354 : return -1;
355 : /* LCOV_EXCL_STOP */
356 : }
357 :
358 165 : pathprint(value, value_size, "%s", i);
359 :
360 165 : return 0;
361 : }
362 : #endif
363 :
364 : /**
365 : * Get the device file from the device number.
366 : *
367 : * It uses /sys/dev/block/.../uevent.
368 : *
369 : * For null device (major==0) it fails.
370 : */
371 : #if HAVE_LINUX_DEVICE
372 165 : static int devresolve_sys(dev_t device, char* path, size_t path_size)
373 : {
374 : struct stat st;
375 : char buf[PATH_MAX];
376 :
377 : /* default device path from device number */
378 165 : pathprint(path, path_size, "/sys/dev/block/%u:%u/uevent", major(device), minor(device));
379 :
380 165 : if (tagread(path, "DEVNAME", buf, sizeof(buf)) != 0) {
381 : /* LCOV_EXCL_START */
382 : log_tag("resolve:sys:%u:%u: failed to read DEVNAME tag '%s'\n", major(device), minor(device), path);
383 : return -1;
384 : /* LCOV_EXCL_STOP */
385 : }
386 :
387 : /* set the real device path */
388 165 : pathprint(path, path_size, "/dev/%s", buf);
389 :
390 : /* check the device */
391 165 : if (stat(path, &st) != 0) {
392 : /* LCOV_EXCL_START */
393 : log_tag("resolve:sys:%u:%u: failed to stat '%s'\n", major(device), minor(device), path);
394 : return -1;
395 : /* LCOV_EXCL_STOP */
396 : }
397 165 : if (st.st_rdev != device) {
398 : /* LCOV_EXCL_START */
399 : 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);
400 : return -1;
401 : /* LCOV_EXCL_STOP */
402 : }
403 :
404 165 : log_tag("resolve:sys:%u:%u:%s: found\n", major(device), minor(device), path);
405 :
406 165 : return 0;
407 : }
408 : #endif
409 :
410 : /**
411 : * Get the device file from the device number.
412 : */
413 : #if HAVE_LINUX_DEVICE
414 165 : static int devresolve(uint64_t device, char* path, size_t path_size)
415 : {
416 165 : if (devresolve_sys(device, path, path_size) == 0)
417 165 : return 0;
418 :
419 0 : return -1;
420 : }
421 : #endif
422 :
423 : /**
424 : * Cache used by blkid.
425 : */
426 : #if HAVE_BLKID
427 : static blkid_cache cache = 0;
428 : #endif
429 :
430 : /**
431 : * Get the UUID using the /dev/disk/by-uuid/ links.
432 : * It doesn't require root permission, and the uuid are always updated.
433 : * It doesn't work with Btrfs file-systems that don't export the main UUID
434 : * in /dev/disk/by-uuid/.
435 : */
436 : #if HAVE_LINUX_DEVICE
437 5668 : static int devuuid_dev(uint64_t device, char* uuid, size_t uuid_size)
438 : {
439 : int ret;
440 : DIR* d;
441 : struct dirent* dd;
442 : struct stat st;
443 :
444 : /* scan the UUID directory searching for the device */
445 5668 : d = opendir("/dev/disk/by-uuid");
446 5668 : if (!d) {
447 0 : log_tag("uuid:by-uuid:%u:%u: opendir(/dev/disk/by-uuid) failed, %s\n", major(device), minor(device), strerror(errno));
448 : /* directory missing?, likely we are not in Linux */
449 0 : return -1;
450 : }
451 :
452 90688 : while ((dd = readdir(d)) != 0) {
453 : /* skip "." and ".." files, UUIDs never start with '.' */
454 90688 : if (dd->d_name[0] == '.')
455 11336 : continue;
456 :
457 79352 : ret = fstatat(dirfd(d), dd->d_name, &st, 0);
458 79352 : if (ret != 0) {
459 0 : log_tag("uuid:by-uuid:%u:%u: fstatat(%s) failed, %s\n", major(device), minor(device), dd->d_name, strerror(errno));
460 : /* generic error, ignore and continue the search */
461 0 : continue;
462 : }
463 :
464 : /* if it matches, we have the uuid */
465 79352 : if (S_ISBLK(st.st_mode) && st.st_rdev == (dev_t)device) {
466 : char buf[PATH_MAX];
467 : char path[PATH_MAX];
468 :
469 : /* resolve the link */
470 5668 : pathprint(path, sizeof(path), "/dev/disk/by-uuid/%s", dd->d_name);
471 5668 : ret = readlink(path, buf, sizeof(buf));
472 5668 : if (ret < 0 || ret >= PATH_MAX) {
473 0 : 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));
474 : /* generic error, ignore and continue the search */
475 0 : continue;
476 : }
477 5668 : buf[ret] = 0;
478 :
479 : /* found */
480 5668 : pathcpy(uuid, uuid_size, dd->d_name);
481 :
482 5668 : log_tag("uuid:by-uuid:%u:%u:%s: found %s\n", major(device), minor(device), uuid, buf);
483 :
484 5668 : closedir(d);
485 5668 : return 0;
486 : }
487 : }
488 :
489 0 : log_tag("uuid:by-uuid:%u:%u: /dev/disk/by-uuid doesn't contain a matching block device\n", major(device), minor(device));
490 :
491 : /* not found */
492 0 : closedir(d);
493 0 : return -1;
494 : }
495 : #endif
496 :
497 : /**
498 : * Get the UUID using libblkid.
499 : * It uses a cache to work without root permission, resulting in UUID
500 : * not necessarily recent.
501 : * We could call blkid_probe_all() to refresh the UUID, but it would
502 : * require root permission to read the superblocks, and resulting in
503 : * all the disks spinning.
504 : */
505 : #if HAVE_BLKID
506 0 : static int devuuid_blkid(uint64_t device, char* uuid, size_t uuid_size)
507 : {
508 : char* devname;
509 : char* uuidname;
510 :
511 0 : devname = blkid_devno_to_devname(device);
512 0 : if (!devname) {
513 0 : log_tag("uuid:blkid:%u:%u: blkid_devno_to_devname() failed, %s\n", major(device), minor(device), strerror(errno));
514 : /* device mapping failed */
515 0 : return -1;
516 : }
517 :
518 0 : uuidname = blkid_get_tag_value(cache, "UUID", devname);
519 0 : if (!uuidname) {
520 0 : log_tag("uuid:blkid:%u:%u: blkid_get_tag_value(UUID,%s) failed, %s\n", major(device), minor(device), devname, strerror(errno));
521 : /* uuid mapping failed */
522 0 : free(devname);
523 0 : return -1;
524 : }
525 :
526 0 : pathcpy(uuid, uuid_size, uuidname);
527 :
528 0 : log_tag("uuid:blkid:%u:%u:%s: found %s\n", major(device), minor(device), uuid, devname);
529 :
530 0 : free(devname);
531 0 : free(uuidname);
532 0 : return 0;
533 : }
534 : #endif
535 :
536 : #ifdef __APPLE__
537 : static int devuuid_darwin(const char* path, char* uuid, size_t uuid_size)
538 : {
539 : CFStringRef path_apple = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
540 : DASessionRef session = DASessionCreate(kCFAllocatorDefault);
541 :
542 : CFURLRef path_appler = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_apple, kCFURLPOSIXPathStyle, false);
543 : DADiskRef disk;
544 : do {
545 : disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, path_appler);
546 : if (disk) {
547 : CFRelease(path_appler);
548 : break;
549 : } else {
550 : CFURLRef parent_path_appler = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, path_appler);
551 : CFRelease(path_appler);
552 : path_appler = parent_path_appler;
553 : }
554 : } while (true); // This is guaranteed to succeed eventually because it'll hit `/`.
555 :
556 : CFDictionaryRef description = DADiskCopyDescription(disk);
557 : CFUUIDRef uuid_apple = CFDictionaryGetValue(description, kDADiskDescriptionVolumeUUIDKey);
558 : CFStringRef uuid_string = CFUUIDCreateString(kCFAllocatorDefault, uuid_apple);
559 : bool success = CFStringGetCString(uuid_string, uuid, uuid_size, kCFStringEncodingUTF8);
560 : CFRelease(uuid_string);
561 : CFRelease(description);
562 : CFRelease(disk);
563 : CFRelease(session);
564 : CFRelease(path_apple);
565 :
566 : if (success) {
567 : return 0;
568 : } else {
569 : return 1;
570 : }
571 : }
572 : #endif
573 :
574 5672 : int devuuid(uint64_t device, const char* path, char* uuid, size_t uuid_size)
575 : {
576 : (void)path;
577 : (void)device;
578 :
579 : #ifdef __APPLE__
580 : if (devuuid_darwin(path, uuid, uuid_size) == 0)
581 : return 0;
582 : #endif
583 :
584 : #if HAVE_LINUX_DEVICE
585 : /* if the major is the null device */
586 5672 : if (major(device) == 0) {
587 : /* obtain the real device */
588 4 : if (devdereference(device, &device) != 0) {
589 : /* LCOV_EXCL_START */
590 : return -1;
591 : /* LCOV_EXCL_STOP */
592 : }
593 : }
594 : #endif
595 :
596 : /* first try with the /dev/disk/by-uuid version */
597 : #if HAVE_LINUX_DEVICE
598 5668 : if (devuuid_dev(device, uuid, uuid_size) == 0)
599 5668 : return 0;
600 : #else
601 : log_tag("uuid:by-uuid:%u:%u: by-uuid not supported\n", major(device), minor(device));
602 : #endif
603 :
604 : /* fall back to blkid for other cases */
605 : #if HAVE_BLKID
606 0 : if (devuuid_blkid(device, uuid, uuid_size) == 0)
607 0 : return 0;
608 : #else
609 : log_tag("uuid:blkid:%u:%u: blkid support not compiled in\n", major(device), minor(device));
610 : #endif
611 :
612 0 : log_tag("uuid:notfound:%u:%u:\n", major(device), minor(device));
613 :
614 : /* not supported */
615 : (void)uuid;
616 : (void)uuid_size;
617 0 : return -1;
618 : }
619 :
620 11432 : int filephy(const char* path, uint64_t size, uint64_t* physical)
621 : {
622 : #if HAVE_LINUX_FIEMAP_H
623 : /* In Linux get the real physical address of the file */
624 : /* Note that FIEMAP doesn't require root permission */
625 : int f;
626 : struct {
627 : struct fiemap fiemap;
628 : struct fiemap_extent extent;
629 : } fm;
630 : unsigned int blknum;
631 :
632 11432 : f = open(path, O_RDONLY);
633 11432 : if (f == -1) {
634 0 : return -1;
635 : }
636 :
637 : /* first try with FIEMAP */
638 : /* if works for ext2, ext3, ext4, xfs, btrfs */
639 11432 : memset(&fm, 0, sizeof(fm));
640 11432 : fm.fiemap.fm_start = 0;
641 11432 : fm.fiemap.fm_length = ~0ULL;
642 11432 : fm.fiemap.fm_flags = FIEMAP_FLAG_SYNC; /* required to ensure that just created files report a valid address and not 0 */
643 11432 : fm.fiemap.fm_extent_count = 1; /* we are interested only at the first block */
644 :
645 11432 : if (ioctl(f, FS_IOC_FIEMAP, &fm) != -1) {
646 11432 : uint32_t flags = fm.fiemap.fm_extents[0].fe_flags;
647 11432 : uint64_t offset = fm.fiemap.fm_extents[0].fe_physical;
648 :
649 : /* check some condition for validating the offset */
650 11432 : if (flags & FIEMAP_EXTENT_DATA_INLINE) {
651 : /* if the data is inline, we don't have an offset to report */
652 0 : *physical = FILEPHY_WITHOUT_OFFSET;
653 11432 : } else if (flags & FIEMAP_EXTENT_UNKNOWN) {
654 : /* if the offset is unknown, we don't have an offset to report */
655 0 : *physical = FILEPHY_WITHOUT_OFFSET;
656 11432 : } else if (offset == 0) {
657 : /* 0 is the general fallback for file-systems when */
658 : /* they don't have an offset to report */
659 14 : *physical = FILEPHY_WITHOUT_OFFSET;
660 : } else {
661 : /* finally report the real offset */
662 11418 : *physical = offset + FILEPHY_REAL_OFFSET;
663 : }
664 :
665 11432 : if (close(f) == -1)
666 0 : return -1;
667 11432 : return 0;
668 : }
669 :
670 : /* if the file is empty, FIBMAP doesn't work, and we don't even try to use it */
671 0 : if (size == 0) {
672 0 : *physical = FILEPHY_WITHOUT_OFFSET;
673 0 : if (close(f) == -1)
674 0 : return -1;
675 0 : return 0;
676 : }
677 :
678 : /* then try with FIBMAP */
679 : /* it works for jfs, reiserfs, ntfs-3g */
680 : /* in exfat it always returns 0, that it's anyway better than the fake inodes */
681 0 : blknum = 0; /* first block */
682 0 : if (ioctl(f, FIBMAP, &blknum) != -1) {
683 0 : *physical = blknum + FILEPHY_REAL_OFFSET;
684 0 : if (close(f) == -1)
685 0 : return -1;
686 0 : return 0;
687 : }
688 :
689 : /* otherwise don't use anything, and keep the directory traversal order */
690 : /* at now this should happen only for vfat */
691 : /* and it's surely better than using fake inodes */
692 0 : *physical = FILEPHY_UNREPORTED_OFFSET;
693 0 : if (close(f) == -1)
694 0 : return -1;
695 : #else
696 : /* In a generic Unix use a dummy value for all the files */
697 : /* We don't want to risk to use the inode without knowing */
698 : /* if it really improves performance. */
699 : /* In this way we keep them in the directory traversal order */
700 : /* that at least keeps files in the same directory together. */
701 : /* Note also that in newer file-system with snapshot, like ZFS, */
702 : /* the inode doesn't represent even more the disk position, because files */
703 : /* are not overwritten in place, but rewritten in another location */
704 : /* of the disk. */
705 : *physical = FILEPHY_UNREPORTED_OFFSET;
706 :
707 : (void)path; /* not used here */
708 : (void)size;
709 : #endif
710 :
711 0 : return 0;
712 : }
713 :
714 5483 : int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_hardlinks, uint64_t* total_space, uint64_t* free_space)
715 : {
716 : char type[64];
717 : const char* ptype;
718 :
719 : #if HAVE_STATFS
720 : struct statfs st;
721 :
722 5483 : if (statfs(path, &st) != 0) {
723 : char dir[PATH_MAX];
724 : char* slash;
725 :
726 24 : if (errno != ENOENT) {
727 0 : return -1;
728 : }
729 :
730 : /* if it doesn't exist, we assume a file */
731 : /* and we check for the containing dir */
732 24 : if (strlen(path) + 1 > sizeof(dir)) {
733 0 : errno = ENAMETOOLONG;
734 0 : return -1;
735 : }
736 :
737 24 : strcpy(dir, path);
738 :
739 24 : slash = strrchr(dir, '/');
740 24 : if (!slash)
741 0 : return -1;
742 :
743 24 : *slash = 0;
744 24 : if (statfs(dir, &st) != 0)
745 0 : return -1;
746 : }
747 : #endif
748 :
749 : /* to get the fs type check "man stat" or "stat -f -t FILE" */
750 5483 : if (has_persistent_inode) {
751 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
752 575 : switch (st.f_type) {
753 0 : case 0x65735546 : /* FUSE, "fuseblk" in the stat command */
754 : case 0x4d44 : /* VFAT, "msdos" in the stat command */
755 0 : *has_persistent_inode = 0;
756 0 : break;
757 575 : default :
758 : /* by default assume yes */
759 575 : *has_persistent_inode = 1;
760 575 : break;
761 : }
762 : #else
763 : /* in Unix inodes are persistent by default */
764 : *has_persistent_inode = 1;
765 : #endif
766 : }
767 :
768 5483 : if (has_syncronized_hardlinks) {
769 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
770 575 : switch (st.f_type) {
771 0 : case 0x5346544E : /* NTFS */
772 : case 0x4d44 : /* VFAT, "msdos" in the stat command */
773 0 : *has_syncronized_hardlinks = 0;
774 0 : break;
775 575 : default :
776 : /* by default assume yes */
777 575 : *has_syncronized_hardlinks = 1;
778 575 : break;
779 : }
780 : #else
781 : /* in Unix hardlinks share the same metadata by default */
782 : *has_syncronized_hardlinks = 1;
783 : #endif
784 : }
785 :
786 5483 : if (total_space) {
787 : #if HAVE_STATFS
788 4908 : *total_space = st.f_bsize * (uint64_t)st.f_blocks;
789 : #else
790 : *total_space = 0;
791 : #endif
792 : }
793 :
794 5483 : if (free_space) {
795 : #if HAVE_STATFS
796 4908 : *free_space = st.f_bsize * (uint64_t)st.f_bfree;
797 : #else
798 : *free_space = 0;
799 : #endif
800 : }
801 :
802 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_FSTYPENAME
803 : /* get the filesystem type directly from the struct (Mac OS X) */
804 : (void)type;
805 : ptype = st.f_fstypename;
806 : #elif HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
807 : /* get the filesystem type from f_type (Linux) */
808 : /* from: https://github.com/influxdata/gopsutil/blob/master/disk/disk_linux.go */
809 5483 : switch (st.f_type) {
810 0 : case 0x65735546 : ptype = "fuseblk"; break;
811 0 : case 0x4D44 : ptype = "vfat/msdos"; break;
812 5483 : case 0xEF53 : ptype = "ext2/3/4"; break;
813 0 : case 0x6969 : ptype = "nfs"; break; /* remote */
814 0 : case 0x6E667364 : ptype = "nfsd"; break; /* remote */
815 0 : case 0x517B : ptype = "smb"; break; /* remote */
816 0 : case 0x5346544E : ptype = "ntfs"; break;
817 0 : case 0x52654973 : ptype = "reiserfs"; break;
818 0 : case 0x3153464A : ptype = "jfs"; break;
819 0 : case 0x58465342 : ptype = "xfs"; break;
820 0 : case 0x9123683E : ptype = "btrfs"; break;
821 0 : case 0x2FC12FC1 : ptype = "zfs"; break;
822 0 : default :
823 0 : snprintf(type, sizeof(type), "0x%X", (unsigned)st.f_type);
824 0 : ptype = type;
825 : }
826 : #else
827 : (void)type;
828 : ptype = "unknown";
829 : #endif
830 :
831 5483 : log_tag("statfs:%s: %s \n", ptype, path);
832 :
833 5483 : return 0;
834 : }
835 :
836 3744805 : uint64_t tick(void)
837 : {
838 : #if HAVE_MACH_ABSOLUTE_TIME
839 : /* for Mac OS X */
840 : return mach_absolute_time();
841 : #elif HAVE_CLOCK_GETTIME && (defined(CLOCK_MONOTONIC) || defined(CLOCK_MONOTONIC_RAW))
842 : /* for Linux */
843 : struct timespec tv;
844 :
845 : /* nanosecond precision with clock_gettime() */
846 : #if defined(CLOCK_MONOTONIC_RAW)
847 3744805 : if (clock_gettime(CLOCK_MONOTONIC_RAW, &tv) != 0) {
848 : #else
849 : if (clock_gettime(CLOCK_MONOTONIC, &tv) != 0) {
850 : #endif
851 0 : return 0;
852 : }
853 :
854 3744805 : return tv.tv_sec * 1000000000ULL + tv.tv_nsec;
855 : #else
856 : /* other platforms */
857 : struct timeval tv;
858 :
859 : /* microsecond precision with gettimeofday() */
860 : if (gettimeofday(&tv, 0) != 0) {
861 : return 0;
862 : }
863 :
864 : return tv.tv_sec * 1000000ULL + tv.tv_usec;
865 : #endif
866 : }
867 :
868 3362 : uint64_t tick_ms(void)
869 : {
870 : struct timeval tv;
871 :
872 3362 : if (gettimeofday(&tv, 0) != 0)
873 0 : return 0;
874 :
875 3362 : return tv.tv_sec * 1000ULL + tv.tv_usec / 1000;
876 : }
877 :
878 284 : int randomize(void* ptr, size_t size)
879 : {
880 : int f;
881 : ssize_t ret;
882 :
883 284 : f = open("/dev/urandom", O_RDONLY);
884 284 : if (f == -1)
885 0 : return -1;
886 :
887 284 : ret = read(f, ptr, size);
888 284 : if (ret < 0 || (size_t)ret != size) {
889 0 : close(f);
890 0 : return -1;
891 : }
892 :
893 284 : if (close(f) != 0)
894 0 : return -1;
895 :
896 284 : return 0;
897 : }
898 :
899 : /**
900 : * Read a file extracting the contained device number in %u:%u format.
901 : * Return 0 on error.
902 : */
903 : #if HAVE_LINUX_DEVICE
904 130 : static dev_t devread(const char* path)
905 : {
906 : int f;
907 : int ret;
908 : int len;
909 : char buf[64];
910 : char* e;
911 : unsigned ma;
912 : unsigned mi;
913 :
914 130 : f = open(path, O_RDONLY);
915 130 : if (f == -1) {
916 : /* LCOV_EXCL_START */
917 : log_fatal("Failed to open '%s'.\n", path);
918 : return 0;
919 : /* LCOV_EXCL_STOP */
920 : }
921 :
922 130 : len = read(f, buf, sizeof(buf));
923 130 : if (len < 0) {
924 : /* LCOV_EXCL_START */
925 : close(f);
926 : log_fatal("Failed to read '%s'.\n", path);
927 : return 0;
928 : /* LCOV_EXCL_STOP */
929 : }
930 130 : if (len == sizeof(buf)) {
931 : /* LCOV_EXCL_START */
932 : close(f);
933 : log_fatal("Too long read '%s'.\n", path);
934 : return 0;
935 : /* LCOV_EXCL_STOP */
936 : }
937 :
938 130 : ret = close(f);
939 130 : if (ret != 0) {
940 : /* LCOV_EXCL_START */
941 : log_fatal("Failed to close '%s'.\n", path);
942 : return 0;
943 : /* LCOV_EXCL_STOP */
944 : }
945 :
946 130 : buf[len] = 0;
947 :
948 130 : ma = strtoul(buf, &e, 10);
949 130 : if (*e != ':') {
950 : /* LCOV_EXCL_START */
951 : log_fatal("Invalid format in '%s' for '%s'.\n", path, buf);
952 : return 0;
953 : /* LCOV_EXCL_STOP */
954 : }
955 :
956 130 : mi = strtoul(e + 1, &e, 10);
957 130 : if (*e != 0 && !isspace(*e)) {
958 : /* LCOV_EXCL_START */
959 : log_fatal("Invalid format in '%s' for '%s'.\n", path, buf);
960 : return 0;
961 : /* LCOV_EXCL_STOP */
962 : }
963 :
964 130 : return makedev(ma, mi);
965 : }
966 : #endif
967 :
968 : /**
969 : * Read a device tree filling the specified list of disk_t entries.
970 : */
971 : #if HAVE_LINUX_DEVICE
972 130 : static int devtree(const char* name, const char* smartctl, const int* smartignore, dev_t device, devinfo_t* parent, tommy_list* list)
973 : {
974 : char path[PATH_MAX];
975 : DIR* d;
976 130 : int slaves = 0;
977 :
978 130 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/slaves", major(device), minor(device));
979 :
980 : /* check if there is a slaves list */
981 130 : d = opendir(path);
982 130 : if (d != 0) {
983 : struct dirent* dd;
984 :
985 260 : while ((dd = readdir(d)) != 0) {
986 195 : if (dd->d_name[0] != '.') {
987 : dev_t subdev;
988 :
989 : /* for each slave, expand the full potential tree */
990 65 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/slaves/%s/dev", major(device), minor(device), dd->d_name);
991 :
992 65 : subdev = devread(path);
993 65 : if (!subdev) {
994 : /* LCOV_EXCL_START */
995 : closedir(d);
996 : return -1;
997 : /* LCOV_EXCL_STOP */
998 : }
999 :
1000 65 : if (devtree(name, smartctl, smartignore, subdev, parent, list) != 0) {
1001 : /* LCOV_EXCL_START */
1002 : closedir(d);
1003 : return -1;
1004 : /* LCOV_EXCL_STOP */
1005 : }
1006 :
1007 65 : ++slaves;
1008 : }
1009 : }
1010 :
1011 65 : closedir(d);
1012 : }
1013 :
1014 : /* if no slaves found */
1015 130 : if (!slaves) {
1016 : /* this is a raw device */
1017 : devinfo_t* devinfo;
1018 :
1019 : /* check if it's a real device */
1020 65 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device", major(device), minor(device));
1021 65 : if (access(path, F_OK) != 0) {
1022 : /* get the parent device */
1023 65 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/../dev", major(device), minor(device));
1024 :
1025 65 : device = devread(path);
1026 65 : if (!device) {
1027 : /* LCOV_EXCL_START */
1028 : return -1;
1029 : /* LCOV_EXCL_STOP */
1030 : }
1031 : }
1032 :
1033 : /* get the device file */
1034 65 : if (devresolve(device, path, sizeof(path)) != 0) {
1035 : /* LCOV_EXCL_START */
1036 : log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1037 : return -1;
1038 : /* LCOV_EXCL_STOP */
1039 : }
1040 :
1041 65 : devinfo = calloc_nofail(1, sizeof(devinfo_t));
1042 :
1043 65 : devinfo->device = device;
1044 65 : pathcpy(devinfo->name, sizeof(devinfo->name), name);
1045 65 : pathcpy(devinfo->smartctl, sizeof(devinfo->smartctl), smartctl);
1046 65 : memcpy(devinfo->smartignore, smartignore, sizeof(devinfo->smartignore));
1047 65 : pathcpy(devinfo->file, sizeof(devinfo->file), path);
1048 65 : devinfo->parent = parent;
1049 :
1050 : /* insert in the list */
1051 65 : tommy_list_insert_tail(list, &devinfo->node, devinfo);
1052 : }
1053 :
1054 130 : return 0;
1055 : }
1056 : #endif
1057 :
1058 : /**
1059 : * Scan all the devices.
1060 : *
1061 : * If a device is already in, it's not added again.
1062 : */
1063 : #if HAVE_LINUX_DEVICE
1064 0 : static int devscan(tommy_list* list)
1065 : {
1066 : char dir[PATH_MAX];
1067 : DIR* d;
1068 : struct dirent* dd;
1069 :
1070 0 : pathprint(dir, sizeof(dir), "/sys/dev/block/");
1071 :
1072 : /* check if there is a slaves list */
1073 0 : d = opendir(dir);
1074 0 : if (d == 0) {
1075 : /* LCOV_EXCL_START */
1076 : log_fatal("Failed to open dir '%s'.\n", dir);
1077 : return -1;
1078 : /* LCOV_EXCL_STOP */
1079 : }
1080 :
1081 0 : while ((dd = readdir(d)) != 0) {
1082 : char path[PATH_MAX];
1083 : tommy_node* i;
1084 : dev_t device;
1085 : devinfo_t* devinfo;
1086 :
1087 0 : if (dd->d_name[0] == '.')
1088 0 : continue;
1089 :
1090 0 : pathprint(path, sizeof(path), "/sys/dev/block/%s/device", dd->d_name);
1091 :
1092 : /* check if it's a real device */
1093 0 : if (access(path, F_OK) != 0)
1094 0 : continue;
1095 :
1096 0 : pathprint(path, sizeof(path), "/sys/dev/block/%s/dev", dd->d_name);
1097 :
1098 0 : device = devread(path);
1099 0 : if (!device) {
1100 : /* LCOV_EXCL_START */
1101 : log_tag("scan:skip: Skipping device %s because failed to read its device number.\n", dd->d_name);
1102 : continue;
1103 : /* LCOV_EXCL_STOP */
1104 : }
1105 :
1106 : /* check if already present */
1107 0 : for (i = tommy_list_head(list); i != 0; i = i->next) {
1108 0 : devinfo = i->data;
1109 0 : if (devinfo->device == device)
1110 0 : break;
1111 : }
1112 :
1113 : /* if already present */
1114 0 : if (i != 0)
1115 0 : continue;
1116 :
1117 : /* get the device file */
1118 0 : if (devresolve(device, path, sizeof(path)) != 0) {
1119 : /* LCOV_EXCL_START */
1120 : log_tag("scan:skip: Skipping device %u:%u because failed to resolve.\n", major(device), minor(device));
1121 : continue;
1122 : /* LCOV_EXCL_STOP */
1123 : }
1124 :
1125 0 : devinfo = calloc_nofail(1, sizeof(devinfo_t));
1126 :
1127 0 : devinfo->device = device;
1128 0 : pathcpy(devinfo->file, sizeof(devinfo->file), path);
1129 :
1130 : /* insert in the list */
1131 0 : tommy_list_insert_tail(list, &devinfo->node, devinfo);
1132 : }
1133 :
1134 0 : closedir(d);
1135 0 : return 0;
1136 : }
1137 : #endif
1138 :
1139 : /**
1140 : * Get SMART attributes.
1141 : */
1142 : #if HAVE_LINUX_DEVICE
1143 0 : static int devsmart(dev_t device, const char* name, const char* smartctl, uint64_t* smart, char* serial, char* vendor, char* model)
1144 : {
1145 : char cmd[PATH_MAX + 64];
1146 : char file[PATH_MAX];
1147 : FILE* f;
1148 : int ret;
1149 :
1150 0 : if (devresolve(device, file, sizeof(file)) != 0) {
1151 : /* LCOV_EXCL_START */
1152 : log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1153 : return -1;
1154 : /* LCOV_EXCL_STOP */
1155 : }
1156 :
1157 : /* if there is a custom smartctl command */
1158 0 : if (smartctl[0]) {
1159 : char option[PATH_MAX];
1160 0 : snprintf(option, sizeof(option), smartctl, file);
1161 0 : snprintf(cmd, sizeof(cmd), "smartctl -a %s", option);
1162 : } else {
1163 0 : snprintf(cmd, sizeof(cmd), "smartctl -a %s", file);
1164 : }
1165 :
1166 0 : log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
1167 :
1168 0 : f = popen(cmd, "r");
1169 0 : if (!f) {
1170 : /* LCOV_EXCL_START */
1171 : log_fatal("Failed to run '%s' (from popen).\n", cmd);
1172 : return -1;
1173 : /* LCOV_EXCL_STOP */
1174 : }
1175 :
1176 0 : if (smartctl_attribute(f, file, name, smart, serial, vendor, model) != 0) {
1177 : /* LCOV_EXCL_START */
1178 : pclose(f);
1179 : return -1;
1180 : /* LCOV_EXCL_STOP */
1181 : }
1182 :
1183 0 : ret = pclose(f);
1184 :
1185 0 : log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
1186 :
1187 0 : if (!WIFEXITED(ret)) {
1188 : /* LCOV_EXCL_START */
1189 : log_fatal("Failed to run '%s' (not exited).\n", cmd);
1190 : return -1;
1191 : /* LCOV_EXCL_STOP */
1192 : }
1193 0 : if (WEXITSTATUS(ret) == 127) {
1194 : /* LCOV_EXCL_START */
1195 : log_fatal("Failed to run '%s' (from sh).\n", cmd);
1196 : return -1;
1197 : /* LCOV_EXCL_STOP */
1198 : }
1199 :
1200 : /* store the return smartctl return value */
1201 0 : smart[SMART_FLAGS] = WEXITSTATUS(ret);
1202 :
1203 0 : return 0;
1204 : }
1205 : #endif
1206 :
1207 : /**
1208 : * Get POWER state.
1209 : */
1210 : #if HAVE_LINUX_DEVICE
1211 0 : static int devprobe(dev_t device, const char* name, const char* smartctl, int* power)
1212 : {
1213 : char cmd[PATH_MAX + 64];
1214 : char file[PATH_MAX];
1215 : FILE* f;
1216 : int ret;
1217 :
1218 0 : if (devresolve(device, file, sizeof(file)) != 0) {
1219 : /* LCOV_EXCL_START */
1220 : log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1221 : return -1;
1222 : /* LCOV_EXCL_STOP */
1223 : }
1224 :
1225 : /* if there is a custom smartctl command */
1226 0 : if (smartctl[0]) {
1227 : char option[PATH_MAX];
1228 0 : snprintf(option, sizeof(option), smartctl, file);
1229 0 : snprintf(cmd, sizeof(cmd), "smartctl -n standby,3 -i %s", option);
1230 : } else {
1231 0 : snprintf(cmd, sizeof(cmd), "smartctl -n standby,3 -i %s", file);
1232 : }
1233 :
1234 0 : log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
1235 :
1236 0 : f = popen(cmd, "r");
1237 0 : if (!f) {
1238 : /* LCOV_EXCL_START */
1239 : log_fatal("Failed to run '%s' (from popen).\n", cmd);
1240 : return -1;
1241 : /* LCOV_EXCL_STOP */
1242 : }
1243 :
1244 0 : if (smartctl_flush(f, file, name) != 0) {
1245 : /* LCOV_EXCL_START */
1246 : pclose(f);
1247 : return -1;
1248 : /* LCOV_EXCL_STOP */
1249 : }
1250 :
1251 0 : ret = pclose(f);
1252 :
1253 0 : log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
1254 :
1255 0 : if (!WIFEXITED(ret)) {
1256 : /* LCOV_EXCL_START */
1257 : log_fatal("Failed to run '%s' (not exited).\n", cmd);
1258 : return -1;
1259 : /* LCOV_EXCL_STOP */
1260 : }
1261 0 : if (WEXITSTATUS(ret) == 127) {
1262 : /* LCOV_EXCL_START */
1263 : log_fatal("Failed to run '%s' (from sh).\n", cmd);
1264 : return -1;
1265 : /* LCOV_EXCL_STOP */
1266 : }
1267 0 : if (WEXITSTATUS(ret) == 0) {
1268 0 : *power = POWER_ACTIVE;
1269 0 : } else if (WEXITSTATUS(ret) == 3) {
1270 0 : *power = POWER_STANDBY;
1271 : } else {
1272 : /* LCOV_EXCL_START */
1273 : log_fatal("Failed to run '%s' with return code %xh.\n", cmd, WEXITSTATUS(ret));
1274 : return -1;
1275 : /* LCOV_EXCL_STOP */
1276 : }
1277 :
1278 0 : return 0;
1279 : }
1280 : #endif
1281 :
1282 : /**
1283 : * Spin down a specific device.
1284 : */
1285 : #if HAVE_LINUX_DEVICE
1286 5 : static int devdown(dev_t device, const char* name, const char* smartctl)
1287 : {
1288 : char cmd[PATH_MAX + 64];
1289 : char file[PATH_MAX];
1290 : FILE* f;
1291 : int ret;
1292 :
1293 5 : if (devresolve(device, file, sizeof(file)) != 0) {
1294 : /* LCOV_EXCL_START */
1295 : log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1296 : return -1;
1297 : /* LCOV_EXCL_STOP */
1298 : }
1299 :
1300 : /* if there is a custom smartctl command */
1301 5 : if (smartctl[0]) {
1302 : char option[PATH_MAX];
1303 5 : snprintf(option, sizeof(option), smartctl, file);
1304 5 : snprintf(cmd, sizeof(cmd), "smartctl -s standby,now %s", option);
1305 : } else {
1306 0 : snprintf(cmd, sizeof(cmd), "smartctl -s standby,now %s", file);
1307 : }
1308 :
1309 5 : log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
1310 :
1311 5 : f = popen(cmd, "r");
1312 5 : if (!f) {
1313 : /* LCOV_EXCL_START */
1314 : log_fatal("Failed to run '%s' (from popen).\n", cmd);
1315 : return -1;
1316 : /* LCOV_EXCL_STOP */
1317 : }
1318 :
1319 5 : if (smartctl_flush(f, file, name) != 0) {
1320 : /* LCOV_EXCL_START */
1321 : pclose(f);
1322 : return -1;
1323 : /* LCOV_EXCL_STOP */
1324 : }
1325 :
1326 5 : ret = pclose(f);
1327 :
1328 5 : log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
1329 :
1330 5 : if (!WIFEXITED(ret)) {
1331 : /* LCOV_EXCL_START */
1332 : log_fatal("Failed to run '%s' (not exited).\n", cmd);
1333 : return -1;
1334 : /* LCOV_EXCL_STOP */
1335 : }
1336 5 : if (WEXITSTATUS(ret) == 127) {
1337 : /* LCOV_EXCL_START */
1338 : log_fatal("Failed to run '%s' (from sh).\n", cmd);
1339 : return -1;
1340 : /* LCOV_EXCL_STOP */
1341 : }
1342 0 : if (WEXITSTATUS(ret) != 0) {
1343 : /* LCOV_EXCL_START */
1344 : log_fatal("Failed to run '%s' with return code %xh.\n", cmd, WEXITSTATUS(ret));
1345 : return -1;
1346 : /* LCOV_EXCL_STOP */
1347 : }
1348 :
1349 0 : return 0;
1350 : }
1351 : #endif
1352 :
1353 : /**
1354 : * Spin down a specific device if it's up.
1355 : */
1356 : #if HAVE_LINUX_DEVICE
1357 0 : static int devdownifup(dev_t device, const char* name, const char* smartctl, int* power)
1358 : {
1359 0 : *power = POWER_UNKNOWN;
1360 :
1361 0 : if (devprobe(device, name, smartctl, power) != 0)
1362 0 : return -1;
1363 :
1364 0 : if (*power == POWER_ACTIVE)
1365 0 : return devdown(device, name, smartctl);
1366 :
1367 0 : return 0;
1368 : }
1369 : #endif
1370 :
1371 : /**
1372 : * Spin up a specific device.
1373 : */
1374 : #if HAVE_LINUX_DEVICE
1375 30 : static int devup(dev_t device)
1376 : {
1377 : char file[PATH_MAX];
1378 : int ret;
1379 : int f;
1380 : void* buf;
1381 :
1382 30 : if (devresolve(device, file, sizeof(file)) != 0) {
1383 : /* LCOV_EXCL_START */
1384 : log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1385 : return -1;
1386 : /* LCOV_EXCL_STOP */
1387 : }
1388 :
1389 : /* O_DIRECT requires memory aligned to the block size */
1390 30 : if (posix_memalign(&buf, 512, 512) != 0) {
1391 : /* LCOV_EXCL_START */
1392 : log_fatal("Failed to allocate aligned memory for device '%u:%u'.\n", major(device), minor(device));
1393 : return -1;
1394 : /* LCOV_EXCL_STOP */
1395 : }
1396 :
1397 30 : f = open(file, O_RDONLY | O_DIRECT);
1398 30 : if (f < 0) {
1399 : /* LCOV_EXCL_START */
1400 : free(buf);
1401 : log_fatal("Failed to open device '%u:%u'.\n", major(device), minor(device));
1402 : return -1;
1403 : /* LCOV_EXCL_STOP */
1404 : }
1405 :
1406 0 : ret = read(f, buf, 512);
1407 0 : if (ret < 0) {
1408 : /* LCOV_EXCL_START */
1409 : close(f);
1410 : free(buf);
1411 : log_fatal("Failed to read device '%u:%u'.\n", major(device), minor(device));
1412 : return -1;
1413 : /* LCOV_EXCL_STOP */
1414 : }
1415 :
1416 0 : ret = close(f);
1417 0 : if (ret < 0) {
1418 : /* LCOV_EXCL_START */
1419 : free(buf);
1420 : log_fatal("Failed to close device '%u:%u'.\n", major(device), minor(device));
1421 : return -1;
1422 : /* LCOV_EXCL_STOP */
1423 : }
1424 :
1425 0 : free(buf);
1426 0 : return 0;
1427 : }
1428 : #endif
1429 :
1430 : /**
1431 : * Thread for spinning up.
1432 : *
1433 : * Note that filling up the devinfo object is done inside this thread,
1434 : * to avoid to block the main thread if the device need to be spin up
1435 : * to handle stat/resolve requests.
1436 : */
1437 30 : static void* thread_spinup(void* arg)
1438 : {
1439 : #if HAVE_LINUX_DEVICE
1440 30 : devinfo_t* devinfo = arg;
1441 : uint64_t start;
1442 :
1443 30 : start = tick_ms();
1444 :
1445 30 : if (devup(devinfo->device) != 0) {
1446 : /* LCOV_EXCL_START */
1447 : return (void*)-1;
1448 : /* LCOV_EXCL_STOP */
1449 : }
1450 :
1451 0 : msg_status("Spunup device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, tick_ms() - start);
1452 :
1453 0 : return 0;
1454 : #else
1455 : (void)arg;
1456 : return (void*)-1;
1457 : #endif
1458 : }
1459 :
1460 : /**
1461 : * Thread for spinning down.
1462 : */
1463 5 : static void* thread_spindown(void* arg)
1464 : {
1465 : #if HAVE_LINUX_DEVICE
1466 5 : devinfo_t* devinfo = arg;
1467 : uint64_t start;
1468 :
1469 5 : start = tick_ms();
1470 :
1471 5 : if (devdown(devinfo->device, devinfo->name, devinfo->smartctl) != 0) {
1472 : /* LCOV_EXCL_START */
1473 : return (void*)-1;
1474 : /* LCOV_EXCL_STOP */
1475 : }
1476 :
1477 0 : msg_status("Spundown device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, tick_ms() - start);
1478 :
1479 0 : return 0;
1480 : #else
1481 : (void)arg;
1482 : return (void*)-1;
1483 : #endif
1484 : }
1485 :
1486 : /**
1487 : * Thread for spinning down.
1488 : */
1489 0 : static void* thread_spindownifup(void* arg)
1490 : {
1491 : #if HAVE_LINUX_DEVICE
1492 0 : devinfo_t* devinfo = arg;
1493 : uint64_t start;
1494 : int power;
1495 :
1496 0 : start = tick_ms();
1497 :
1498 0 : if (devdownifup(devinfo->device, devinfo->name, devinfo->smartctl, &power) != 0) {
1499 : /* LCOV_EXCL_START */
1500 : return (void*)-1;
1501 : /* LCOV_EXCL_STOP */
1502 : }
1503 :
1504 0 : if (power == POWER_ACTIVE)
1505 0 : msg_status("Spundown device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, tick_ms() - start);
1506 :
1507 0 : return 0;
1508 : #else
1509 : (void)arg;
1510 : return (void*)-1;
1511 : #endif
1512 : }
1513 :
1514 : /**
1515 : * Thread for getting smart info.
1516 : */
1517 0 : static void* thread_smart(void* arg)
1518 : {
1519 : #if HAVE_LINUX_DEVICE
1520 0 : devinfo_t* devinfo = arg;
1521 :
1522 0 : if (devsmart(devinfo->device, devinfo->name, devinfo->smartctl, devinfo->smart, devinfo->smart_serial, devinfo->smart_vendor, devinfo->smart_model) != 0) {
1523 : /* LCOV_EXCL_START */
1524 : return (void*)-1;
1525 : /* LCOV_EXCL_STOP */
1526 : }
1527 :
1528 0 : return 0;
1529 : #else
1530 : (void)arg;
1531 : return (void*)-1;
1532 : #endif
1533 : }
1534 :
1535 : /**
1536 : * Thread for getting power info.
1537 : */
1538 0 : static void* thread_probe(void* arg)
1539 : {
1540 : #if HAVE_LINUX_DEVICE
1541 0 : devinfo_t* devinfo = arg;
1542 :
1543 0 : if (devprobe(devinfo->device, devinfo->name, devinfo->smartctl, &devinfo->power) != 0) {
1544 : /* LCOV_EXCL_START */
1545 : return (void*)-1;
1546 : /* LCOV_EXCL_STOP */
1547 : }
1548 :
1549 0 : return 0;
1550 : #else
1551 : (void)arg;
1552 : return (void*)-1;
1553 : #endif
1554 : }
1555 :
1556 2 : static int device_thread(tommy_list* list, void* (*func)(void* arg))
1557 : {
1558 2 : int fail = 0;
1559 : tommy_node* i;
1560 :
1561 : #if HAVE_THREAD
1562 : /* start all threads */
1563 37 : for (i = tommy_list_head(list); i != 0; i = i->next) {
1564 35 : devinfo_t* devinfo = i->data;
1565 :
1566 35 : thread_create(&devinfo->thread, func, devinfo);
1567 : }
1568 :
1569 : /* join all threads */
1570 37 : for (i = tommy_list_head(list); i != 0; i = i->next) {
1571 35 : devinfo_t* devinfo = i->data;
1572 : void* retval;
1573 :
1574 35 : thread_join(devinfo->thread, &retval);
1575 :
1576 35 : if (retval != 0)
1577 35 : ++fail;
1578 : }
1579 : #else
1580 : for (i = tommy_list_head(list); i != 0; i = i->next) {
1581 : devinfo_t* devinfo = i->data;
1582 :
1583 : if (func(devinfo) != 0)
1584 : ++fail;
1585 : }
1586 : #endif
1587 2 : if (fail != 0) {
1588 : /* LCOV_EXCL_START */
1589 : return -1;
1590 : /* LCOV_EXCL_STOP */
1591 : }
1592 :
1593 0 : return 0;
1594 : }
1595 :
1596 3 : int devquery(tommy_list* high, tommy_list* low, int operation, int others)
1597 : {
1598 3 : void* (*func)(void* arg) = 0;
1599 :
1600 3 : if (operation == DEVICE_DOWN) {
1601 : /* flush all the disk caches before spinning down */
1602 1 : sync();
1603 : }
1604 :
1605 : #if HAVE_LINUX_DEVICE
1606 : tommy_node* i;
1607 : struct stat st;
1608 :
1609 : /* sysfs interface is required */
1610 3 : if (stat("/sys/dev/block", &st) != 0) {
1611 : /* LCOV_EXCL_START */
1612 : log_fatal("Missing interface /sys/dev/block.\n");
1613 : return -1;
1614 : /* LCOV_EXCL_STOP */
1615 : }
1616 :
1617 : /* for each device */
1618 68 : for (i = tommy_list_head(high); i != 0; i = i->next) {
1619 65 : devinfo_t* devinfo = i->data;
1620 65 : uint64_t device = devinfo->device;
1621 :
1622 : /* if the major is the null device, find the real one */
1623 65 : if (major(device) == 0) {
1624 : /* obtain the real device */
1625 0 : if (devdereference(device, &device) != 0) {
1626 : /* LCOV_EXCL_START */
1627 : log_fatal("Failed to dereference device '%u:%u'.\n", major(device), minor(device));
1628 : return -1;
1629 : /* LCOV_EXCL_STOP */
1630 : }
1631 : }
1632 :
1633 : /* get the device file */
1634 65 : if (devresolve(device, devinfo->file, sizeof(devinfo->file)) != 0) {
1635 : /* LCOV_EXCL_START */
1636 : log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1637 : return -1;
1638 : /* LCOV_EXCL_STOP */
1639 : }
1640 :
1641 : /* expand the tree of devices */
1642 65 : if (devtree(devinfo->name, devinfo->smartctl, devinfo->smartignore, device, devinfo, low) != 0) {
1643 : /* LCOV_EXCL_START */
1644 : log_fatal("Failed to expand device '%u:%u'.\n", major(device), minor(device));
1645 : return -1;
1646 : /* LCOV_EXCL_STOP */
1647 : }
1648 : }
1649 :
1650 : /* add other devices */
1651 3 : if (others) {
1652 0 : if (devscan(low) != 0) {
1653 : /* LCOV_EXCL_START */
1654 : log_fatal("Failed to list other devices.\n");
1655 : return -1;
1656 : /* LCOV_EXCL_STOP */
1657 : }
1658 : }
1659 : #else
1660 : (void)high;
1661 : (void)others;
1662 : #endif
1663 :
1664 3 : switch (operation) {
1665 1 : case DEVICE_UP : func = thread_spinup; break;
1666 1 : case DEVICE_DOWN : func = thread_spindown; break;
1667 0 : case DEVICE_SMART : func = thread_smart; break;
1668 0 : case DEVICE_PROBE : func = thread_probe; break;
1669 0 : case DEVICE_DOWNIFUP : func = thread_spindownifup; break;
1670 : }
1671 :
1672 3 : if (!func)
1673 1 : return 0;
1674 :
1675 2 : return device_thread(low, func);
1676 : }
1677 :
1678 273 : void os_init(int opt)
1679 : {
1680 : #if HAVE_BLKID
1681 : int ret;
1682 273 : ret = blkid_get_cache(&cache, NULL);
1683 273 : if (ret != 0) {
1684 : /* LCOV_EXCL_START */
1685 : log_fatal("WARNING Failed to get blkid cache\n");
1686 : /* LCOV_EXCL_STOP */
1687 : }
1688 : #endif
1689 :
1690 : (void)opt;
1691 273 : }
1692 :
1693 262 : void os_done(void)
1694 : {
1695 : #if HAVE_BLKID
1696 262 : if (cache != 0)
1697 262 : blkid_put_cache(cache);
1698 : #endif
1699 262 : }
1700 :
1701 : /* LCOV_EXCL_START */
1702 : void os_abort(void)
1703 : {
1704 : #if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS
1705 : void* stack[32];
1706 : char** messages;
1707 : size_t size;
1708 : unsigned i;
1709 : #endif
1710 :
1711 : printf("Stacktrace of " PACKAGE " v" VERSION);
1712 : #ifdef _linux
1713 : printf(", linux");
1714 : #endif
1715 : #ifdef __GNUC__
1716 : printf(", gcc " __VERSION__);
1717 : #endif
1718 : printf(", %d-bit", (int)sizeof(void *) * 8);
1719 : printf(", PATH_MAX=%d", PATH_MAX);
1720 : printf("\n");
1721 :
1722 : #if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS
1723 : size = backtrace(stack, 32);
1724 :
1725 : messages = backtrace_symbols(stack, size);
1726 :
1727 : for (i = 1; i < size; ++i) {
1728 : const char* msg;
1729 :
1730 : if (messages)
1731 : msg = messages[i];
1732 : else
1733 : msg = "<unknown>";
1734 :
1735 : printf("[bt] %02u: %s\n", i, msg);
1736 :
1737 : if (messages) {
1738 : int ret;
1739 : char addr2line[1024];
1740 : size_t j = 0;
1741 : while (msg[j] != '(' && msg[j] != ' ' && msg[j] != 0)
1742 : ++j;
1743 :
1744 : snprintf(addr2line, sizeof(addr2line), "addr2line %p -e %.*s", stack[i], (unsigned)j, msg);
1745 :
1746 : ret = system(addr2line);
1747 : if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0)
1748 : printf("exit:%d\n", WEXITSTATUS(ret));
1749 : if (WIFSIGNALED(ret))
1750 : printf("signal:%d\n", WTERMSIG(ret));
1751 : }
1752 : }
1753 : #endif
1754 :
1755 : printf("Please report this error to the SnapRAID Forum:\n");
1756 : printf("https://sourceforge.net/p/snapraid/discussion/1677233/\n");
1757 :
1758 : abort();
1759 : }
1760 : /* LCOV_EXCL_STOP */
1761 :
1762 0 : void os_clear(void)
1763 : {
1764 : /* ANSI codes */
1765 0 : printf("\033[H"); /* cursor at topleft */
1766 0 : printf("\033[2J"); /* clear screen */
1767 0 : }
1768 :
1769 7 : size_t direct_size(void)
1770 : {
1771 : long size;
1772 :
1773 7 : size = sysconf(_SC_PAGESIZE);
1774 :
1775 7 : if (size == -1) {
1776 : /* LCOV_EXCL_START */
1777 : log_fatal("No page size\n");
1778 : exit(EXIT_FAILURE);
1779 : /* LCOV_EXCL_STOP */
1780 : }
1781 :
1782 7 : return size;
1783 : }
1784 :
1785 : #if HAVE_LINUX_DEVICE
1786 :
1787 : /* List of possible ambient temperature labels */
1788 : const char* AMBIENT_LABEL[] = {
1789 : "systin",
1790 : "auxtin",
1791 : "mb",
1792 : "board",
1793 : "motherboard",
1794 : "system",
1795 : "chassis",
1796 : "case",
1797 : "room",
1798 : "ambient",
1799 : 0
1800 : };
1801 :
1802 1 : int ambient_temperature(void)
1803 : {
1804 : DIR* dir;
1805 : struct dirent* entry;
1806 1 : int lowest_temp = 0;
1807 :
1808 1 : dir = opendir("/sys/class/hwmon");
1809 1 : if (!dir)
1810 0 : return 0;
1811 :
1812 : /* iterate through hwmon devices */
1813 5 : while ((entry = readdir(dir)) != NULL) {
1814 : char path[PATH_MAX];
1815 : DIR* hwmon_dir;
1816 : struct dirent* hwmon_entry;
1817 :
1818 4 : if (strncmp(entry->d_name, "hwmon", 5) != 0)
1819 2 : continue;
1820 :
1821 2 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s", entry->d_name);
1822 :
1823 : /* iterate through temp*_input files */
1824 2 : hwmon_dir = opendir(path);
1825 2 : if (!hwmon_dir)
1826 0 : continue;
1827 :
1828 66 : while ((hwmon_entry = readdir(hwmon_dir)) != NULL) {
1829 : char value[128];
1830 : char name[128];
1831 : char label[128];
1832 : char* dash;
1833 : int ret;
1834 : char* e;
1835 : int temp;
1836 :
1837 64 : if (strncmp(hwmon_entry->d_name, "temp", 4) != 0)
1838 63 : continue;
1839 :
1840 18 : dash = strrchr(hwmon_entry->d_name, '_');
1841 18 : if (dash == 0)
1842 0 : continue;
1843 :
1844 18 : if (strcmp(dash, "_input") != 0)
1845 14 : continue;
1846 :
1847 : /* read the temperature */
1848 4 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s/%s", entry->d_name, hwmon_entry->d_name);
1849 :
1850 4 : ret = sysread(path, value, sizeof(value));
1851 4 : if (ret < 0)
1852 0 : continue;
1853 :
1854 4 : temp = strtol(value, &e, 10) / 1000;
1855 4 : if (*e != 0 && !isspace(*e))
1856 0 : continue;
1857 :
1858 : /* cut the file name at "_input" */
1859 4 : *dash = 0;
1860 :
1861 : /* read the corresponding name */
1862 4 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s/name", entry->d_name);
1863 4 : ret = sysread(path, name, sizeof(name));
1864 4 : if (ret < 0) {
1865 : /* fallback to using the hwmon entry */
1866 0 : pathcpy(name, sizeof(name), entry->d_name);
1867 : }
1868 :
1869 : /* read the corresponding label file */
1870 4 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s/%s_label", entry->d_name, hwmon_entry->d_name);
1871 4 : ret = sysread(path, label, sizeof(label));
1872 4 : if (ret < 0) {
1873 : /* fallback to using the temp* name (e.g., temp1, temp2) */
1874 0 : pathcpy(label, sizeof(label), hwmon_entry->d_name);
1875 : }
1876 :
1877 : /* trim spaces */
1878 4 : strtrim(name);
1879 4 : strtrim(label);
1880 :
1881 4 : log_tag("thermal:ambient:device:%s:%s:%s:%s:%d\n", entry->d_name, name, hwmon_entry->d_name, label, temp);
1882 :
1883 : /* check if temperature is in reasonable range */
1884 4 : if (temp < 15 || temp > 40)
1885 3 : continue;
1886 :
1887 : /* lower case */
1888 1 : strlwr(label);
1889 :
1890 : /* check if label matches possible ambient labels */
1891 3 : for (int i = 0; AMBIENT_LABEL[i]; ++i) {
1892 3 : if (worddigitstr(label, AMBIENT_LABEL[i]) != 0) {
1893 1 : log_tag("thermal:ambient:candidate:%d\n", temp);
1894 1 : if (lowest_temp == 0 || lowest_temp > temp)
1895 1 : lowest_temp = temp;
1896 1 : break;
1897 : }
1898 : }
1899 :
1900 : /* accept also generic "temp1" */
1901 1 : if (strcmp(label, "temp1") == 0) {
1902 0 : log_tag("thermal:ambient:candidate:%d\n", temp);
1903 0 : if (lowest_temp == 0 || lowest_temp > temp)
1904 0 : lowest_temp = temp;
1905 : }
1906 : }
1907 :
1908 2 : closedir(hwmon_dir);
1909 : }
1910 :
1911 1 : closedir(dir);
1912 :
1913 1 : return lowest_temp;
1914 : }
1915 : #else
1916 : int ambient_temperature(void)
1917 : {
1918 : return 0;
1919 : }
1920 : #endif
1921 :
1922 : #endif
1923 :
|