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 1785762 : int open_noatime(const char* file, int flags)
32 : {
33 : #ifdef O_NOATIME
34 1785762 : int f = open(file, flags | O_NOATIME);
35 :
36 : /* only root is allowed to use O_NOATIME, in case retry without it */
37 1785762 : if (f == -1 && errno == EPERM)
38 0 : f = open(file, flags);
39 1785762 : 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 : static const char* smartctl_paths[] = {
72 : /* Linux & BSD */
73 : "/usr/sbin/smartctl",
74 : "/sbin/smartctl",
75 : "/usr/local/sbin/smartctl",
76 : "/usr/bin/smartctl",
77 : "/usr/local/bin/smartctl",
78 : /* macOS (Intel & Apple Silicon) */
79 : "/opt/homebrew/sbin/smartctl",
80 : 0
81 : };
82 :
83 71 : const char* find_smartctl(void)
84 : {
85 : int i;
86 :
87 71 : for (i = 0; smartctl_paths[i]; ++i) {
88 71 : if (access(smartctl_paths[i], X_OK) == 0)
89 71 : return smartctl_paths[i];
90 : }
91 :
92 0 : return 0;
93 : }
94 :
95 : /**
96 : * Read a file from sys
97 : *
98 : * Return -1 on error, otherwise the size of data read
99 : */
100 : #if HAVE_LINUX_DEVICE
101 704 : static int sysread(const char* path, char* buf, size_t buf_size)
102 : {
103 : int f;
104 : int ret;
105 : int len;
106 :
107 704 : f = open(path, O_RDONLY);
108 704 : if (f == -1) {
109 : /* LCOV_EXCL_START */
110 : return -1;
111 : /* LCOV_EXCL_STOP */
112 : }
113 :
114 650 : len = read(f, buf, buf_size);
115 650 : if (len < 0) {
116 : /* LCOV_EXCL_START */
117 : close(f);
118 : return -1;
119 : /* LCOV_EXCL_STOP */
120 : }
121 :
122 650 : ret = close(f);
123 650 : if (ret != 0) {
124 : /* LCOV_EXCL_START */
125 : return -1;
126 : /* LCOV_EXCL_STOP */
127 : }
128 :
129 650 : return len;
130 : }
131 : #endif
132 :
133 : /**
134 : * Read a file from sys.
135 : * Trim spaces.
136 : * Always put an ending 0.
137 : * Do not report error on reading.
138 : * Return an error if truncated
139 : *
140 : * Return -1 on error, 0 on success
141 : */
142 : #if HAVE_LINUX_DEVICE
143 102 : static int sysattr(const char* path, char* buf, size_t buf_size)
144 : {
145 : int len;
146 :
147 102 : len = sysread(path, buf, buf_size);
148 102 : if (len < 0) {
149 : /* LCOV_EXCL_START */
150 : return -1;
151 : /* LCOV_EXCL_STOP */
152 : }
153 :
154 48 : if ((size_t)len + 1 > buf_size) {
155 : /* LCOV_EXCL_START */
156 : return -1;
157 : /* LCOV_EXCL_STOP */
158 : }
159 :
160 48 : buf[len] = 0;
161 :
162 48 : strtrim(buf);
163 :
164 48 : return 0;
165 : }
166 : #endif
167 :
168 : /**
169 : * Get the device file from the device number.
170 : *
171 : * It uses /proc/self/mountinfo.
172 : *
173 : * Null devices (major==0) are resolved to the device indicated in mountinfo.
174 : */
175 : #if HAVE_LINUX_DEVICE
176 4 : static int devresolve_proc(uint64_t device, char* path, size_t path_size)
177 : {
178 : FILE* f;
179 : char match[32];
180 :
181 : /* generate the matching string */
182 4 : snprintf(match, sizeof(match), "%u:%u", major(device), minor(device));
183 :
184 4 : f = fopen("/proc/self/mountinfo", "r");
185 4 : if (!f) {
186 0 : log_tag("resolve:proc:%u:%u: failed to open /proc/self/mountinfo\n", major(device), minor(device));
187 0 : return -1;
188 : }
189 :
190 : /*
191 : * mountinfo format
192 : * 0 - mount ID
193 : * 1 - parent ID
194 : * 2 - major:minor
195 : * 3 - root
196 : * 4 - mount point
197 : * 5 - options
198 : * 6 - "-" (separator)
199 : * 7 - fs
200 : * 8 - mount source - /dev/device
201 : */
202 :
203 192 : while (1) {
204 : char buf[256];
205 : char* first_map[8];
206 : unsigned first_mac;
207 : char* second_map[8];
208 : unsigned second_mac;
209 : char* s;
210 : struct stat st;
211 : char* separator;
212 : char* majorminor;
213 : char* mountpoint;
214 : char* fs;
215 : char* mountsource;
216 :
217 196 : s = fgets(buf, sizeof(buf), f);
218 196 : if (s == 0)
219 4 : break;
220 :
221 : /* find the separator position */
222 192 : separator = strstr(s, " - ");
223 192 : if (!separator)
224 0 : continue;
225 :
226 : /* skip the separator */
227 192 : *separator = 0;
228 192 : separator += 3;
229 :
230 : /* split the line */
231 192 : first_mac = strsplit(first_map, 8, s, " \t\r\n");
232 192 : second_mac = strsplit(second_map, 8, separator, " \t\r\n");
233 :
234 : /* if too short, it's the wrong line */
235 192 : if (first_mac < 5)
236 0 : continue;
237 192 : if (second_mac < 2)
238 0 : continue;
239 :
240 192 : majorminor = first_map[2];
241 192 : mountpoint = first_map[4];
242 192 : fs = second_map[0];
243 192 : mountsource = second_map[1];
244 :
245 : /* compare major:minor from mountinfo */
246 192 : if (strcmp(majorminor, match) == 0) {
247 : /*
248 : * Accept only /dev/... mountsource
249 : *
250 : * This excludes ZFS that uses a bare label for mountsource, like "tank".
251 : *
252 : * 410 408 0:193 / /XXX rw,relatime shared:217 - zfs tank/system/data/var/lib/docker/XXX rw,xattr,noacl
253 : *
254 : * Also excludes AUTOFS unmounted devices that point to a fake filesystem
255 : * used to remount them at the first use.
256 : *
257 : * 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
258 : */
259 0 : if (strncmp(mountsource, "/dev/", 5) != 0) {
260 0 : log_tag("resolve:proc:%u:%u: match skipped for not /dev/ mountsource for %s %s\n", major(device), minor(device), fs, mountsource);
261 0 : continue;
262 : }
263 :
264 0 : pathcpy(path, path_size, mountsource);
265 :
266 0 : log_tag("resolve:proc:%u:%u: found device %s matching device %s\n", major(device), minor(device), path, match);
267 :
268 0 : fclose(f);
269 0 : return 0;
270 : }
271 :
272 : /* get the device of the mount point */
273 : /* in Btrfs it could be different than the one in mountinfo */
274 192 : if (stat(mountpoint, &st) == 0 && st.st_dev == device) {
275 0 : if (strncmp(mountsource, "/dev/", 5) != 0) {
276 0 : log_tag("resolve:proc:%u:%u: match skipped for not /dev/ mountsource for %s %s\n", major(device), minor(device), fs, mountsource);
277 0 : continue;
278 : }
279 :
280 0 : pathcpy(path, path_size, mountsource);
281 :
282 0 : log_tag("resolve:proc:%u:%u: found device %s matching mountpoint %s\n", major(device), minor(device), path, mountpoint);
283 :
284 0 : fclose(f);
285 0 : return 0;
286 : }
287 : }
288 :
289 4 : log_tag("resolve:proc:%u:%u: not found\n", major(device), minor(device));
290 :
291 4 : fclose(f);
292 4 : return -1;
293 : }
294 : #endif
295 :
296 : /**
297 : * Get the device of a virtual superblock.
298 : *
299 : * This is intended to resolve the case of Btrfs filesystems that
300 : * create a virtual superblock (major==0) not backed by any low
301 : * level device.
302 : *
303 : * See:
304 : * Bug 711881 - too funny btrfs st_dev numbers
305 : * https://bugzilla.redhat.com/show_bug.cgi?id=711881
306 : */
307 : #if HAVE_LINUX_DEVICE
308 4 : static int devdereference(uint64_t device, uint64_t* new_device)
309 : {
310 : char path[PATH_MAX];
311 : struct stat st;
312 :
313 : /* use the proc interface to get the device containing the filesystem */
314 4 : if (devresolve_proc(device, path, sizeof(path)) != 0) {
315 : /* LCOV_EXCL_START */
316 : return -1;
317 : /* LCOV_EXCL_STOP */
318 : }
319 :
320 : /* check the device */
321 0 : if (stat(path, &st) != 0) {
322 : /* LCOV_EXCL_START */
323 : log_tag("dereference:%u:%u: failed to stat %s\n", major(device), minor(device), path);
324 : return -1;
325 : /* LCOV_EXCL_STOP */
326 : }
327 :
328 0 : if (major(st.st_rdev) == 0) {
329 : /* LCOV_EXCL_START */
330 : 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));
331 : return -1;
332 : /* LCOV_EXCL_STOP */
333 : }
334 :
335 0 : *new_device = st.st_rdev;
336 0 : log_tag("dereference:%u:%u: found %u:%u\n", major(device), minor(device), major(st.st_rdev), minor(st.st_rdev));
337 0 : return 0;
338 : }
339 : #endif
340 :
341 : /**
342 : * Read a file extracting the specified tag TAG=VALUE format.
343 : * Return !=0 on error.
344 : */
345 : #if HAVE_LINUX_DEVICE
346 403 : static int tagread(const char* path, const char* tag, char* value, size_t value_size)
347 : {
348 : int ret;
349 : char buf[512];
350 : size_t tag_len;
351 : char* i;
352 : char* e;
353 :
354 403 : ret = sysread(path, buf, sizeof(buf));
355 403 : if (ret < 0) {
356 : /* LCOV_EXCL_START */
357 : log_fatal(errno, "Failed to read '%s'.\n", path);
358 : return -1;
359 : /* LCOV_EXCL_STOP */
360 : }
361 403 : if ((size_t)ret + 1 > sizeof(buf)) {
362 : /* LCOV_EXCL_START */
363 : log_fatal(EEXTERNAL, "Too long read '%s'.\n", path);
364 : return -1;
365 : /* LCOV_EXCL_STOP */
366 : }
367 :
368 : /* ending 0 */
369 403 : buf[ret] = 0;
370 :
371 403 : tag_len = strlen(tag);
372 :
373 7087 : for (i = buf; *i; ++i) {
374 7087 : char* p = i;
375 :
376 : /* start with a space */
377 7087 : if (p != buf) {
378 6684 : if (!isspace(*p))
379 5878 : continue;
380 806 : ++p;
381 : }
382 :
383 1209 : if (strncmp(p, tag, tag_len) != 0)
384 806 : continue;
385 403 : p += tag_len;
386 :
387 : /* end with a = */
388 403 : if (*p != '=')
389 0 : continue;
390 403 : ++p;
391 :
392 : /* found */
393 403 : i = p;
394 403 : break;
395 : }
396 403 : if (!*i) {
397 : /* LCOV_EXCL_START */
398 : log_fatal(EEXTERNAL, "Missing tag '%s' for '%s'.\n", tag, path);
399 : return -1;
400 : /* LCOV_EXCL_STOP */
401 : }
402 :
403 : /* terminate at the first space */
404 403 : e = i;
405 1781 : while (*e != 0 && !isspace(*e))
406 1378 : ++e;
407 403 : *e = 0;
408 :
409 403 : if (!*i) {
410 : /* LCOV_EXCL_START */
411 : log_fatal(EEXTERNAL, "Empty tag '%s' for '%s'.\n", tag, path);
412 : return -1;
413 : /* LCOV_EXCL_STOP */
414 : }
415 :
416 403 : pathprint(value, value_size, "%s", i);
417 :
418 403 : return 0;
419 : }
420 : #endif
421 :
422 : /**
423 : * Get the device file from the device number.
424 : *
425 : * It uses /sys/dev/block/.../uevent.
426 : *
427 : * For null device (major==0) it fails.
428 : */
429 : #if HAVE_LINUX_DEVICE
430 403 : static int devresolve_sys(dev_t device, char* path, size_t path_size)
431 : {
432 : struct stat st;
433 : char buf[PATH_MAX];
434 :
435 : /* default device path from device number */
436 403 : pathprint(path, path_size, "/sys/dev/block/%u:%u/uevent", major(device), minor(device));
437 :
438 403 : if (tagread(path, "DEVNAME", buf, sizeof(buf)) != 0) {
439 : /* LCOV_EXCL_START */
440 : log_tag("resolve:sys:%u:%u: failed to read DEVNAME tag '%s'\n", major(device), minor(device), path);
441 : return -1;
442 : /* LCOV_EXCL_STOP */
443 : }
444 :
445 : /* set the real device path */
446 403 : pathprint(path, path_size, "/dev/%s", buf);
447 :
448 : /* check the device */
449 403 : if (stat(path, &st) != 0) {
450 : /* LCOV_EXCL_START */
451 : log_tag("resolve:sys:%u:%u: failed to stat '%s'\n", major(device), minor(device), path);
452 : return -1;
453 : /* LCOV_EXCL_STOP */
454 : }
455 403 : if (st.st_rdev != device) {
456 : /* LCOV_EXCL_START */
457 : 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);
458 : return -1;
459 : /* LCOV_EXCL_STOP */
460 : }
461 :
462 403 : log_tag("resolve:sys:%u:%u:%s: found\n", major(device), minor(device), path);
463 :
464 403 : return 0;
465 : }
466 : #endif
467 :
468 : /**
469 : * Get the device file from the device number.
470 : */
471 : #if HAVE_LINUX_DEVICE
472 403 : static int devresolve(uint64_t device, char* path, size_t path_size)
473 : {
474 403 : if (devresolve_sys(device, path, path_size) == 0)
475 403 : return 0;
476 :
477 0 : return -1;
478 : }
479 : #endif
480 :
481 : /**
482 : * Cache used by blkid.
483 : */
484 : #if HAVE_BLKID
485 : static blkid_cache cache = 0;
486 : #endif
487 :
488 : /**
489 : * Get the UUID using the /dev/disk/by-uuid/ links.
490 : * It doesn't require root permission, and the uuid are always updated.
491 : * It doesn't work with Btrfs file-systems that don't export the main UUID
492 : * in /dev/disk/by-uuid/.
493 : */
494 : #if HAVE_LINUX_DEVICE
495 10908 : static int devuuid_dev(uint64_t device, char* uuid, size_t uuid_size)
496 : {
497 : int ret;
498 : DIR* d;
499 : struct dirent* dd;
500 : struct stat st;
501 :
502 : /* scan the UUID directory searching for the device */
503 10908 : d = opendir("/dev/disk/by-uuid");
504 10908 : if (!d) {
505 0 : log_tag("uuid:by-uuid:%u:%u: opendir(/dev/disk/by-uuid) failed, %s\n", major(device), minor(device), strerror(errno));
506 : /* directory missing?, likely we are not in Linux */
507 0 : return -1;
508 : }
509 :
510 10908 : int dir_fd = dirfd(d);
511 10908 : if (dir_fd == -1) {
512 0 : log_tag("uuid:by-uuid:%u:%u: dirfd(/dev/disk/by-uuid) failed, %s\n", major(device), minor(device), strerror(errno));
513 0 : return -1;
514 : }
515 :
516 174036 : while ((dd = readdir(d)) != 0) {
517 : /* skip "." and ".." files, UUIDs never start with '.' */
518 174036 : if (dd->d_name[0] == '.')
519 21816 : continue;
520 :
521 152220 : ret = fstatat(dir_fd, dd->d_name, &st, 0);
522 152220 : if (ret != 0) {
523 0 : log_tag("uuid:by-uuid:%u:%u: fstatat(%s) failed, %s\n", major(device), minor(device), dd->d_name, strerror(errno));
524 : /* generic error, ignore and continue the search */
525 0 : continue;
526 : }
527 :
528 : /* if it matches, we have the uuid */
529 152220 : if (S_ISBLK(st.st_mode) && st.st_rdev == (dev_t)device) {
530 : char buf[PATH_MAX];
531 : char path[PATH_MAX];
532 :
533 : /* resolve the link */
534 10908 : pathprint(path, sizeof(path), "/dev/disk/by-uuid/%s", dd->d_name);
535 10908 : ret = readlink(path, buf, sizeof(buf));
536 10908 : if (ret < 0 || ret >= PATH_MAX) {
537 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));
538 : /* generic error, ignore and continue the search */
539 0 : continue;
540 : }
541 10908 : buf[ret] = 0;
542 :
543 : /* found */
544 10908 : pathcpy(uuid, uuid_size, dd->d_name);
545 :
546 10908 : log_tag("uuid:by-uuid:%u:%u:%s: found %s\n", major(device), minor(device), uuid, buf);
547 :
548 10908 : closedir(d);
549 10908 : return 0;
550 : }
551 : }
552 :
553 0 : log_tag("uuid:by-uuid:%u:%u: /dev/disk/by-uuid doesn't contain a matching block device\n", major(device), minor(device));
554 :
555 : /* not found */
556 0 : closedir(d);
557 0 : return -1;
558 : }
559 : #endif
560 :
561 : /**
562 : * Get the UUID using libblkid.
563 : * It uses a cache to work without root permission, resulting in UUID
564 : * not necessarily recent.
565 : * We could call blkid_probe_all() to refresh the UUID, but it would
566 : * require root permission to read the superblocks, and resulting in
567 : * all the disks spinning.
568 : */
569 : #if HAVE_BLKID
570 0 : static int devuuid_blkid(uint64_t device, char* uuid, size_t uuid_size)
571 : {
572 : char* devname;
573 : char* uuidname;
574 :
575 0 : devname = blkid_devno_to_devname(device);
576 0 : if (!devname) {
577 0 : log_tag("uuid:blkid:%u:%u: blkid_devno_to_devname() failed, %s\n", major(device), minor(device), strerror(errno));
578 : /* device mapping failed */
579 0 : return -1;
580 : }
581 :
582 0 : uuidname = blkid_get_tag_value(cache, "UUID", devname);
583 0 : if (!uuidname) {
584 0 : log_tag("uuid:blkid:%u:%u: blkid_get_tag_value(UUID,%s) failed, %s\n", major(device), minor(device), devname, strerror(errno));
585 : /* uuid mapping failed */
586 0 : free(devname);
587 0 : return -1;
588 : }
589 :
590 0 : pathcpy(uuid, uuid_size, uuidname);
591 :
592 0 : log_tag("uuid:blkid:%u:%u:%s: found %s\n", major(device), minor(device), uuid, devname);
593 :
594 0 : free(devname);
595 0 : free(uuidname);
596 0 : return 0;
597 : }
598 : #endif
599 :
600 :
601 : /**
602 : * Get the LABEL using libblkid.
603 : * It uses a cache to work without root permission, resulting in LABEL
604 : * not necessarily recent.
605 : */
606 : #if HAVE_BLKID
607 5919 : static int devuuid_label(uint64_t device, char* label, size_t label_size)
608 : {
609 : char* devname;
610 : char* labelname;
611 :
612 5919 : devname = blkid_devno_to_devname(device);
613 5919 : if (!devname) {
614 0 : log_tag("label:blkid:%u:%u: blkid_devno_to_devname() failed, %s\n", major(device), minor(device), strerror(errno));
615 : /* device mapping failed */
616 0 : return -1;
617 : }
618 :
619 5919 : labelname = blkid_get_tag_value(cache, "LABEL", devname);
620 5919 : if (!labelname) {
621 0 : log_tag("label:blkid:%u:%u: blkid_get_tag_value(LABEL,%s) failed, %s\n", major(device), minor(device), devname, strerror(errno));
622 0 : free(devname);
623 : /* label mapping failed */
624 0 : return -1;
625 : }
626 :
627 5919 : pathcpy(label, label_size, labelname);
628 :
629 5919 : log_tag("label:blkid:%u:%u:%s: found %s\n", major(device), minor(device), label, devname);
630 :
631 5919 : free(devname);
632 5919 : free(labelname);
633 5919 : return 0;
634 : }
635 : #endif
636 :
637 :
638 : #ifdef __APPLE__
639 : static int devuuid_darwin(const char* path, char* uuid, size_t uuid_size)
640 : {
641 : CFStringRef path_apple = 0;
642 : DASessionRef session = 0;
643 : CFURLRef path_appler = 0;
644 : int result = -1;
645 :
646 : *uuid = 0;
647 :
648 : path_apple = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
649 : if (!path_apple)
650 : goto bail;
651 :
652 : session = DASessionCreate(kCFAllocatorDefault);
653 : if (!session)
654 : goto bail;
655 :
656 : path_appler = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_apple, kCFURLPOSIXPathStyle, false);
657 : if (!path_appler)
658 : goto bail;
659 :
660 : /* find the mount point */
661 : DADiskRef disk = NULL;
662 : while (path_appler) {
663 : disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, path_appler);
664 : if (disk)
665 : break;
666 :
667 : CFURLRef parent = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, path_appler);
668 : if (!parent)
669 : break;
670 :
671 : /* check if we hit the root (parent == child) */
672 : if (CFEqual(parent, path_appler)) {
673 : CFRelease(parent);
674 : break;
675 : }
676 :
677 : CFRelease(path_appler);
678 : path_appler = parent;
679 : }
680 :
681 : /* safely extract the UUID */
682 : if (disk) {
683 : CFDictionaryRef description = DADiskCopyDescription(disk);
684 : if (description) {
685 : /* key might not exist for NTFS/ExFAT on some drivers */
686 : const void* value = (CFUUIDRef)CFDictionaryGetValue(description, kDADiskDescriptionVolumeUUIDKey);
687 : CFStringRef uuid_string = NULL;
688 :
689 : if (value) {
690 : if (CFGetTypeID(value) == CFUUIDGetTypeID()) {
691 : uuid_string = CFUUIDCreateString(kCFAllocatorDefault, (CFUUIDRef)value);
692 : } else if (CFGetTypeID(value) == CFStringGetTypeID()) {
693 : /* if it's already a string, retain it so we can release it later consistently */
694 : uuid_string = (CFStringRef)CFRetain(value);
695 : }
696 : }
697 :
698 : if (uuid_string) {
699 : if (CFStringGetCString(uuid_string, uuid, uuid_size, kCFStringEncodingUTF8)) {
700 : result = 0; /* success */
701 : }
702 : CFRelease(uuid_string);
703 : }
704 :
705 : CFRelease(description);
706 : }
707 : CFRelease(disk);
708 : }
709 :
710 : bail:
711 : /* clean up */
712 : if (path_appler) CFRelease(path_appler);
713 : if (session) CFRelease(session);
714 : if (path_apple) CFRelease(path_apple);
715 :
716 : return result;
717 : }
718 : #endif
719 :
720 10912 : int devuuid(uint64_t device, const char* path, char* uuid, size_t uuid_size)
721 : {
722 : (void)path;
723 : (void)device;
724 :
725 : #ifdef __APPLE__
726 : if (devuuid_darwin(path, uuid, uuid_size) == 0)
727 : return 0;
728 : #endif
729 :
730 : #if HAVE_LINUX_DEVICE
731 : /* if the major is the null device */
732 10912 : if (major(device) == 0) {
733 : /* obtain the real device */
734 4 : if (devdereference(device, &device) != 0) {
735 : /* LCOV_EXCL_START */
736 : return -1;
737 : /* LCOV_EXCL_STOP */
738 : }
739 : }
740 : #endif
741 :
742 : /* first try with the /dev/disk/by-uuid version */
743 : #if HAVE_LINUX_DEVICE
744 10908 : if (devuuid_dev(device, uuid, uuid_size) == 0)
745 10908 : return 0;
746 : #else
747 : log_tag("uuid:by-uuid:%u:%u: by-uuid not supported\n", major(device), minor(device));
748 : #endif
749 :
750 : /* fall back to blkid for other cases */
751 : #if HAVE_BLKID
752 0 : if (devuuid_blkid(device, uuid, uuid_size) == 0)
753 0 : return 0;
754 : #else
755 : log_tag("uuid:blkid:%u:%u: blkid support not compiled in\n", major(device), minor(device));
756 : #endif
757 :
758 0 : log_tag("uuid:notfound:%u:%u:\n", major(device), minor(device));
759 :
760 : /* not supported */
761 : (void)uuid;
762 : (void)uuid_size;
763 0 : return -1;
764 : }
765 :
766 11432 : int filephy(const char* path, uint64_t size, uint64_t* physical)
767 : {
768 : #if HAVE_LINUX_FIEMAP_H
769 : /* In Linux get the real physical address of the file */
770 : /* Note that FIEMAP doesn't require root permission */
771 : int f;
772 : struct fiemap* fiemap;
773 : size_t fiemap_size;
774 : unsigned int blknum;
775 :
776 11432 : f = open(path, O_RDONLY);
777 11432 : if (f == -1) {
778 0 : return -1;
779 : }
780 :
781 : /* first try with FIEMAP */
782 : /* if works for ext2, ext3, ext4, xfs, btrfs */
783 11432 : fiemap_size = sizeof(struct fiemap) + sizeof(struct fiemap_extent);
784 11432 : fiemap = malloc_nofail(fiemap_size);
785 11432 : memset(fiemap, 0, fiemap_size);
786 11432 : fiemap->fm_start = 0;
787 11432 : fiemap->fm_length = ~0ULL;
788 11432 : fiemap->fm_flags = FIEMAP_FLAG_SYNC; /* required to ensure that just created files report a valid address and not 0 */
789 11432 : fiemap->fm_extent_count = 1; /* we are interested only at the first block */
790 :
791 11432 : if (ioctl(f, FS_IOC_FIEMAP, fiemap) != -1) {
792 11432 : uint32_t flags = fiemap->fm_extents[0].fe_flags;
793 11432 : uint64_t offset = fiemap->fm_extents[0].fe_physical;
794 :
795 : /* check some condition for validating the offset */
796 11432 : if (flags & FIEMAP_EXTENT_DATA_INLINE) {
797 : /* if the data is inline, we don't have an offset to report */
798 0 : *physical = FILEPHY_WITHOUT_OFFSET;
799 11432 : } else if (flags & FIEMAP_EXTENT_UNKNOWN) {
800 : /* if the offset is unknown, we don't have an offset to report */
801 0 : *physical = FILEPHY_WITHOUT_OFFSET;
802 11432 : } else if (offset == 0) {
803 : /* 0 is the general fallback for file-systems when */
804 : /* they don't have an offset to report */
805 14 : *physical = FILEPHY_WITHOUT_OFFSET;
806 : } else {
807 : /* finally report the real offset */
808 11418 : *physical = offset + FILEPHY_REAL_OFFSET;
809 : }
810 :
811 11432 : free(fiemap);
812 :
813 11432 : if (close(f) == -1)
814 0 : return -1;
815 11432 : return 0;
816 : }
817 :
818 0 : free(fiemap);
819 :
820 : /* if the file is empty, FIBMAP doesn't work, and we don't even try to use it */
821 0 : if (size == 0) {
822 0 : *physical = FILEPHY_WITHOUT_OFFSET;
823 0 : if (close(f) == -1)
824 0 : return -1;
825 0 : return 0;
826 : }
827 :
828 : /* then try with FIBMAP */
829 : /* it works for jfs, reiserfs, ntfs-3g */
830 : /* in exfat it always returns 0, that it's anyway better than the fake inodes */
831 0 : blknum = 0; /* first block */
832 0 : if (ioctl(f, FIBMAP, &blknum) != -1) {
833 0 : *physical = blknum + FILEPHY_REAL_OFFSET;
834 0 : if (close(f) == -1)
835 0 : return -1;
836 0 : return 0;
837 : }
838 :
839 : /* otherwise don't use anything, and keep the directory traversal order */
840 : /* at now this should happen only for vfat */
841 : /* and it's surely better than using fake inodes */
842 0 : *physical = FILEPHY_UNREPORTED_OFFSET;
843 0 : if (close(f) == -1)
844 0 : return -1;
845 : #else
846 : /* In a generic Unix use a dummy value for all the files */
847 : /* We don't want to risk to use the inode without knowing */
848 : /* if it really improves performance. */
849 : /* In this way we keep them in the directory traversal order */
850 : /* that at least keeps files in the same directory together. */
851 : /* Note also that in newer file-system with snapshot, like ZFS, */
852 : /* the inode doesn't represent even more the disk position, because files */
853 : /* are not overwritten in place, but rewritten in another location */
854 : /* of the disk. */
855 : *physical = FILEPHY_UNREPORTED_OFFSET;
856 :
857 : (void)path; /* not used here */
858 : (void)size;
859 : #endif
860 :
861 0 : return 0;
862 : }
863 :
864 : /* from man statfs */
865 : #define ADFS_SUPER_MAGIC 0xadf5
866 : #define AFFS_SUPER_MAGIC 0xADFF
867 : #define BDEVFS_MAGIC 0x62646576
868 : #define BEFS_SUPER_MAGIC 0x42465331
869 : #define BFS_MAGIC 0x1BADFACE
870 : #define BINFMTFS_MAGIC 0x42494e4d
871 : #define BTRFS_SUPER_MAGIC 0x9123683E
872 : #define CGROUP_SUPER_MAGIC 0x27e0eb
873 : #define CIFS_MAGIC_NUMBER 0xFF534D42
874 : #define CODA_SUPER_MAGIC 0x73757245
875 : #define COH_SUPER_MAGIC 0x012FF7B7
876 : #define CRAMFS_MAGIC 0x28cd3d45
877 : #define DEBUGFS_MAGIC 0x64626720
878 : #define DEVFS_SUPER_MAGIC 0x1373
879 : #define DEVPTS_SUPER_MAGIC 0x1cd1
880 : #define EFIVARFS_MAGIC 0xde5e81e4
881 : #define EFS_SUPER_MAGIC 0x00414A53
882 : #define EXT_SUPER_MAGIC 0x137D
883 : #define EXT2_OLD_SUPER_MAGIC 0xEF51
884 : #define EXT4_SUPER_MAGIC 0xEF53 /* also ext2/ext3 */
885 : #define FUSE_SUPER_MAGIC 0x65735546
886 : #define FUTEXFS_SUPER_MAGIC 0xBAD1DEA
887 : #define HFS_SUPER_MAGIC 0x4244
888 : #define HFSPLUS_SUPER_MAGIC 0x482b
889 : #define HOSTFS_SUPER_MAGIC 0x00c0ffee
890 : #define HPFS_SUPER_MAGIC 0xF995E849
891 : #define HUGETLBFS_MAGIC 0x958458f6
892 : #define ISOFS_SUPER_MAGIC 0x9660
893 : #define JFFS2_SUPER_MAGIC 0x72b6
894 : #define JFS_SUPER_MAGIC 0x3153464a
895 : #define MINIX_SUPER_MAGIC 0x137F
896 : #define MINIX_SUPER_MAGIC2 0x138F
897 : #define MINIX2_SUPER_MAGIC 0x2468
898 : #define MINIX2_SUPER_MAGIC2 0x2478
899 : #define MINIX3_SUPER_MAGIC 0x4d5a
900 : #define MQUEUE_MAGIC 0x19800202
901 : #define MSDOS_SUPER_MAGIC 0x4d44
902 : #define NCP_SUPER_MAGIC 0x564c
903 : #define NFS_SUPER_MAGIC 0x6969
904 : #define NILFS_SUPER_MAGIC 0x3434
905 : #define NTFS_SB_MAGIC 0x5346544e
906 : #define OCFS2_SUPER_MAGIC 0x7461636f
907 : #define OPENPROM_SUPER_MAGIC 0x9fa1
908 : #define PIPEFS_MAGIC 0x50495045
909 : #define PROC_SUPER_MAGIC 0x9fa0
910 : #define PSTOREFS_MAGIC 0x6165676C
911 : #define QNX4_SUPER_MAGIC 0x002f
912 : #define QNX6_SUPER_MAGIC 0x68191122
913 : #define RAMFS_MAGIC 0x858458f6
914 : #define REISERFS_SUPER_MAGIC 0x52654973
915 : #define ROMFS_MAGIC 0x7275
916 : #define SELINUX_MAGIC 0xf97cff8c
917 : #define SMACK_MAGIC 0x43415d53
918 : #define SMB_SUPER_MAGIC 0x517B
919 : #define SOCKFS_MAGIC 0x534F434B
920 : #define SQUASHFS_MAGIC 0x73717368
921 : #define SYSFS_MAGIC 0x62656572
922 : #define SYSV2_SUPER_MAGIC 0x012FF7B6
923 : #define SYSV4_SUPER_MAGIC 0x012FF7B5
924 : #define TMPFS_MAGIC 0x01021994
925 : #define UDF_SUPER_MAGIC 0x15013346
926 : #define UFS_MAGIC 0x00011954
927 : #define USBDEVICE_SUPER_MAGIC 0x9fa2
928 : #define V9FS_MAGIC 0x01021997
929 : #define VXFS_SUPER_MAGIC 0xa501FCF5
930 : #define XENFS_SUPER_MAGIC 0xabba1974
931 : #define XENIX_SUPER_MAGIC 0x012FF7B4
932 : #define XFS_SUPER_MAGIC 0x58465342
933 : #define _XIAFS_SUPER_MAGIC 0x012FD16D
934 : #define AFS_SUPER_MAGIC 0x5346414F
935 : #define AUFS_SUPER_MAGIC 0x61756673
936 : #define ANON_INODE_FS_SUPER_MAGIC 0x09041934
937 : #define CEPH_SUPER_MAGIC 0x00C36400
938 : #define ECRYPTFS_SUPER_MAGIC 0xF15F
939 : #define FAT_SUPER_MAGIC 0x4006
940 : #define FHGFS_SUPER_MAGIC 0x19830326
941 : #define FUSEBLK_SUPER_MAGIC 0x65735546
942 : #define FUSECTL_SUPER_MAGIC 0x65735543
943 : #define GFS_SUPER_MAGIC 0x1161970
944 : #define GPFS_SUPER_MAGIC 0x47504653
945 : #define MTD_INODE_FS_SUPER_MAGIC 0x11307854
946 : #define INOTIFYFS_SUPER_MAGIC 0x2BAD1DEA
947 : #define ISOFS_R_WIN_SUPER_MAGIC 0x4004
948 : #define ISOFS_WIN_SUPER_MAGIC 0x4000
949 : #define JFFS_SUPER_MAGIC 0x07C0
950 : #define KAFS_SUPER_MAGIC 0x6B414653
951 : #define LUSTRE_SUPER_MAGIC 0x0BD00BD0
952 : #define NFSD_SUPER_MAGIC 0x6E667364
953 : #define PANFS_SUPER_MAGIC 0xAAD7AAEA
954 : #define RPC_PIPEFS_SUPER_MAGIC 0x67596969
955 : #define SECURITYFS_SUPER_MAGIC 0x73636673
956 : #define UFS_BYTESWAPPED_SUPER_MAGIC 0x54190100
957 : #define VMHGFS_SUPER_MAGIC 0xBACBACBC
958 : #define VZFS_SUPER_MAGIC 0x565A4653
959 : #define ZFS_SUPER_MAGIC 0x2FC12FC1
960 :
961 : struct filesystem_entry {
962 : unsigned id;
963 : const char* name;
964 : int remote;
965 : } FILESYSTEMS[] = {
966 : { ADFS_SUPER_MAGIC, "adfs", 0 },
967 : { AFFS_SUPER_MAGIC, "affs", 0 },
968 : { AFS_SUPER_MAGIC, "afs", 1 },
969 : { AUFS_SUPER_MAGIC, "aufs", 1 },
970 : { BEFS_SUPER_MAGIC, "befs", 0 },
971 : { BDEVFS_MAGIC, "bdevfs", 0 },
972 : { BFS_MAGIC, "bfs", 0 },
973 : { BINFMTFS_MAGIC, "binfmt_misc", 0 },
974 : { BTRFS_SUPER_MAGIC, "btrfs", 0 },
975 : { CEPH_SUPER_MAGIC, "ceph", 1 },
976 : { CGROUP_SUPER_MAGIC, "cgroupfs", 0 },
977 : { CIFS_MAGIC_NUMBER, "cifs", 1 },
978 : { CODA_SUPER_MAGIC, "coda", 1 },
979 : { COH_SUPER_MAGIC, "coh", 0 },
980 : { CRAMFS_MAGIC, "cramfs", 0 },
981 : { DEBUGFS_MAGIC, "debugfs", 0 },
982 : { DEVFS_SUPER_MAGIC, "devfs", 0 },
983 : { DEVPTS_SUPER_MAGIC, "devpts", 0 },
984 : { ECRYPTFS_SUPER_MAGIC, "ecryptfs", 0 },
985 : { EFS_SUPER_MAGIC, "efs", 0 },
986 : { EXT_SUPER_MAGIC, "ext", 0 },
987 : { EXT4_SUPER_MAGIC, "ext4", 0 },
988 : { EXT2_OLD_SUPER_MAGIC, "ext2", 0 },
989 : { FAT_SUPER_MAGIC, "fat", 0 },
990 : { FHGFS_SUPER_MAGIC, "fhgfs", 1 },
991 : { FUSEBLK_SUPER_MAGIC, "fuseblk", 1 },
992 : { FUSECTL_SUPER_MAGIC, "fusectl", 1 },
993 : { FUTEXFS_SUPER_MAGIC, "futexfs", 0 },
994 : { GFS_SUPER_MAGIC, "gfs/gfs2", 1 },
995 : { GPFS_SUPER_MAGIC, "gpfs", 1 },
996 : { HFS_SUPER_MAGIC, "hfs", 0 },
997 : { HFSPLUS_SUPER_MAGIC, "hfsplus", 0 },
998 : { HPFS_SUPER_MAGIC, "hpfs", 0 },
999 : { HUGETLBFS_MAGIC, "hugetlbfs", 0 },
1000 : { MTD_INODE_FS_SUPER_MAGIC, "inodefs", 0 },
1001 : { INOTIFYFS_SUPER_MAGIC, "inotifyfs", 0 },
1002 : { ISOFS_SUPER_MAGIC, "isofs", 0 },
1003 : { ISOFS_R_WIN_SUPER_MAGIC, "isofs", 0 },
1004 : { ISOFS_WIN_SUPER_MAGIC, "isofs", 0 },
1005 : { JFFS_SUPER_MAGIC, "jffs", 0 },
1006 : { JFFS2_SUPER_MAGIC, "jffs2", 0 },
1007 : { JFS_SUPER_MAGIC, "jfs", 0 },
1008 : { KAFS_SUPER_MAGIC, "k-afs", 1 },
1009 : { LUSTRE_SUPER_MAGIC, "lustre", 1 },
1010 : { MINIX_SUPER_MAGIC, "minix", 0 },
1011 : { MINIX_SUPER_MAGIC2, "minix", 0 },
1012 : { MINIX2_SUPER_MAGIC, "minix2", 0 },
1013 : { MINIX2_SUPER_MAGIC2, "minix2", 0 },
1014 : { MINIX3_SUPER_MAGIC, "minix3", 0 },
1015 : { MQUEUE_MAGIC, "mqueue", 0 },
1016 : { MSDOS_SUPER_MAGIC, "msdos", 0 },
1017 : { NCP_SUPER_MAGIC, "novell", 1 },
1018 : { NFS_SUPER_MAGIC, "nfs", 1 },
1019 : { NFSD_SUPER_MAGIC, "nfsd", 1 },
1020 : { NILFS_SUPER_MAGIC, "nilfs", 0 },
1021 : { NTFS_SB_MAGIC, "ntfs", 0 },
1022 : { OPENPROM_SUPER_MAGIC, "openprom", 0 },
1023 : { OCFS2_SUPER_MAGIC, "ocfs2", 1 },
1024 : { PANFS_SUPER_MAGIC, "panfs", 1 },
1025 : { PIPEFS_MAGIC, "pipefs", 1 },
1026 : { PROC_SUPER_MAGIC, "proc", 0 },
1027 : { PSTOREFS_MAGIC, "pstorefs", 0 },
1028 : { QNX4_SUPER_MAGIC, "qnx4", 0 },
1029 : { QNX6_SUPER_MAGIC, "qnx6", 0 },
1030 : { RAMFS_MAGIC, "ramfs", 0 },
1031 : { REISERFS_SUPER_MAGIC, "reiserfs", 0 },
1032 : { ROMFS_MAGIC, "romfs", 0 },
1033 : { RPC_PIPEFS_SUPER_MAGIC, "rpc_pipefs", 0 },
1034 : { SECURITYFS_SUPER_MAGIC, "securityfs", 0 },
1035 : { SELINUX_MAGIC, "selinux", 0 },
1036 : { SMB_SUPER_MAGIC, "smb", 1 },
1037 : { SOCKFS_MAGIC, "sockfs", 0 },
1038 : { SQUASHFS_MAGIC, "squashfs", 0 },
1039 : { SYSFS_MAGIC, "sysfs", 0 },
1040 : { SYSV2_SUPER_MAGIC, "sysv2", 0 },
1041 : { SYSV4_SUPER_MAGIC, "sysv4", 0 },
1042 : { TMPFS_MAGIC, "tmpfs", 0 },
1043 : { UDF_SUPER_MAGIC, "udf", 0 },
1044 : { UFS_MAGIC, "ufs", 0 },
1045 : { UFS_BYTESWAPPED_SUPER_MAGIC, "ufs", 0 },
1046 : { USBDEVICE_SUPER_MAGIC, "usbdevfs", 0 },
1047 : { V9FS_MAGIC, "v9fs", 0 },
1048 : { VMHGFS_SUPER_MAGIC, "vmhgfs", 1 },
1049 : { VXFS_SUPER_MAGIC, "vxfs", 0 },
1050 : { VZFS_SUPER_MAGIC, "vzfs", 0 },
1051 : { XENFS_SUPER_MAGIC, "xenfs", 0 },
1052 : { XENIX_SUPER_MAGIC, "xenix", 0 },
1053 : { XFS_SUPER_MAGIC, "xfs", 0 },
1054 : { _XIAFS_SUPER_MAGIC, "xia", 0 },
1055 : { ZFS_SUPER_MAGIC, "zfs", 0 },
1056 : { 0 }
1057 : };
1058 :
1059 6596 : int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_hardlinks, uint64_t* total_space, uint64_t* free_space, char* fstype, size_t fstype_size, char* fslabel, size_t fslabel_size)
1060 : {
1061 : #if HAVE_STATFS
1062 : struct statfs st;
1063 :
1064 6596 : if (statfs(path, &st) != 0) {
1065 : char dir[PATH_MAX];
1066 : char* slash;
1067 :
1068 48 : if (errno != ENOENT) {
1069 0 : return -1;
1070 : }
1071 :
1072 : /* if it doesn't exist, we assume a file */
1073 : /* and we check for the containing dir */
1074 48 : if (strlen(path) + 1 > sizeof(dir)) {
1075 0 : errno = ENAMETOOLONG;
1076 0 : return -1;
1077 : }
1078 :
1079 48 : strcpy(dir, path);
1080 :
1081 48 : slash = strrchr(dir, '/');
1082 48 : if (!slash)
1083 0 : return -1;
1084 :
1085 48 : *slash = 0;
1086 48 : if (statfs(dir, &st) != 0)
1087 0 : return -1;
1088 : }
1089 : #endif
1090 :
1091 : /* to get the fs type check "man stat" or "stat -f -t FILE" */
1092 6596 : if (has_persistent_inode) {
1093 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
1094 629 : switch (st.f_type) {
1095 0 : case FUSEBLK_SUPER_MAGIC : /* FUSE, "fuseblk" in the stat command */
1096 : case MSDOS_SUPER_MAGIC : /* VFAT, "msdos" in the stat command */
1097 0 : *has_persistent_inode = 0;
1098 0 : break;
1099 629 : default :
1100 : /* by default assume yes */
1101 629 : *has_persistent_inode = 1;
1102 629 : break;
1103 : }
1104 : #else
1105 : /* in Unix inodes are persistent by default */
1106 : *has_persistent_inode = 1;
1107 : #endif
1108 : }
1109 :
1110 6596 : if (has_syncronized_hardlinks) {
1111 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
1112 629 : switch (st.f_type) {
1113 0 : case NTFS_SB_MAGIC : /* NTFS */
1114 : case MSDOS_SUPER_MAGIC : /* VFAT, "msdos" in the stat command */
1115 0 : *has_syncronized_hardlinks = 0;
1116 0 : break;
1117 629 : default :
1118 : /* by default assume yes */
1119 629 : *has_syncronized_hardlinks = 1;
1120 629 : break;
1121 : }
1122 : #else
1123 : /* in Unix hardlinks share the same metadata by default */
1124 : *has_syncronized_hardlinks = 1;
1125 : #endif
1126 : }
1127 :
1128 6596 : if (total_space) {
1129 : #if HAVE_STATFS
1130 5967 : *total_space = st.f_bsize * (uint64_t)st.f_blocks;
1131 : #else
1132 : *total_space = 0;
1133 : #endif
1134 : }
1135 :
1136 6596 : if (free_space) {
1137 : #if HAVE_STATFS
1138 5967 : *free_space = st.f_bsize * (uint64_t)st.f_bfree;
1139 : #else
1140 : *free_space = 0;
1141 : #endif
1142 : }
1143 :
1144 6596 : const char* ptype = 0;
1145 :
1146 : #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_FSTYPENAME
1147 : /* get the filesystem type directly from the struct (Mac OS X) */
1148 : ptype = st.f_fstypename;
1149 : #elif HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
1150 : /* get the filesystem type from f_type (Linux) */
1151 : /* from: https://github.com/influxdata/gopsutil/blob/master/disk/disk_linux.go */
1152 145112 : for (int i = 0; FILESYSTEMS[i].id != 0; ++i) {
1153 145112 : if (st.f_type == FILESYSTEMS[i].id) {
1154 6596 : ptype = FILESYSTEMS[i].name;
1155 6596 : break;
1156 : }
1157 : }
1158 : #endif
1159 :
1160 6596 : if (fstype) {
1161 5967 : fstype[0] = 0;
1162 5967 : if (ptype)
1163 5967 : snprintf(fstype, fstype_size, "%s", ptype);
1164 : }
1165 :
1166 6596 : if (fslabel) {
1167 5967 : fslabel[0] = 0;
1168 : #if HAVE_BLKID
1169 : struct stat fst;
1170 5967 : if (stat(path, &fst) == 0)
1171 5919 : devuuid_label(fst.st_dev, fslabel, fslabel_size);
1172 : #else
1173 : (void)fslabel_size;
1174 : #endif
1175 : }
1176 :
1177 6596 : return 0;
1178 : }
1179 :
1180 4046785 : uint64_t os_tick(void)
1181 : {
1182 : #if HAVE_MACH_ABSOLUTE_TIME
1183 : /* for Mac OS X */
1184 : return mach_absolute_time();
1185 : #elif HAVE_CLOCK_GETTIME && (defined(CLOCK_MONOTONIC) || defined(CLOCK_MONOTONIC_RAW))
1186 : /* for Linux */
1187 : struct timespec tv;
1188 :
1189 : /* nanosecond precision with clock_gettime() */
1190 : #if defined(CLOCK_MONOTONIC_RAW)
1191 4046785 : if (clock_gettime(CLOCK_MONOTONIC_RAW, &tv) != 0) {
1192 : #else
1193 : if (clock_gettime(CLOCK_MONOTONIC, &tv) != 0) {
1194 : #endif
1195 0 : return 0;
1196 : }
1197 :
1198 4046785 : return tv.tv_sec * 1000000000ULL + tv.tv_nsec;
1199 : #else
1200 : /* other platforms */
1201 : struct timeval tv;
1202 :
1203 : /* microsecond precision with gettimeofday() */
1204 : if (gettimeofday(&tv, 0) != 0) {
1205 : return 0;
1206 : }
1207 :
1208 : return tv.tv_sec * 1000000ULL + tv.tv_usec;
1209 : #endif
1210 : }
1211 :
1212 3721 : uint64_t os_tick_ms(void)
1213 : {
1214 : struct timeval tv;
1215 :
1216 3721 : if (gettimeofday(&tv, 0) != 0)
1217 0 : return 0;
1218 :
1219 3721 : return tv.tv_sec * 1000ULL + tv.tv_usec / 1000;
1220 : }
1221 :
1222 308 : int randomize(void* ptr, size_t size)
1223 : {
1224 : int f;
1225 : ssize_t ret;
1226 :
1227 308 : f = open("/dev/urandom", O_RDONLY);
1228 308 : if (f == -1)
1229 0 : return -1;
1230 :
1231 308 : ret = read(f, ptr, size);
1232 308 : if (ret < 0 || (size_t)ret != size) {
1233 0 : close(f);
1234 0 : return -1;
1235 : }
1236 :
1237 308 : if (close(f) != 0)
1238 0 : return -1;
1239 :
1240 308 : return 0;
1241 : }
1242 :
1243 : /**
1244 : * Read a file extracting the contained device number in %u:%u format.
1245 : * Return 0 on error.
1246 : */
1247 : #if HAVE_LINUX_DEVICE
1248 286 : static dev_t devread(const char* path)
1249 : {
1250 : int f;
1251 : int ret;
1252 : int len;
1253 : char buf[64];
1254 : char* e;
1255 : unsigned ma;
1256 : unsigned mi;
1257 :
1258 286 : f = open(path, O_RDONLY);
1259 286 : if (f == -1) {
1260 : /* LCOV_EXCL_START */
1261 : log_fatal(errno, "Failed to open '%s'.\n", path);
1262 : return 0;
1263 : /* LCOV_EXCL_STOP */
1264 : }
1265 :
1266 286 : len = read(f, buf, sizeof(buf));
1267 286 : if (len < 0) {
1268 : /* LCOV_EXCL_START */
1269 : close(f);
1270 : log_fatal(errno, "Failed to read '%s'.\n", path);
1271 : return 0;
1272 : /* LCOV_EXCL_STOP */
1273 : }
1274 286 : if (len == sizeof(buf)) {
1275 : /* LCOV_EXCL_START */
1276 : close(f);
1277 : log_fatal(EEXTERNAL, "Too long read '%s'.\n", path);
1278 : return 0;
1279 : /* LCOV_EXCL_STOP */
1280 : }
1281 :
1282 286 : ret = close(f);
1283 286 : if (ret != 0) {
1284 : /* LCOV_EXCL_START */
1285 : log_fatal(errno, "Failed to close '%s'.\n", path);
1286 : return 0;
1287 : /* LCOV_EXCL_STOP */
1288 : }
1289 :
1290 286 : buf[len] = 0;
1291 :
1292 286 : ma = strtoul(buf, &e, 10);
1293 286 : if (*e != ':') {
1294 : /* LCOV_EXCL_START */
1295 : log_fatal(EEXTERNAL, "Invalid format in '%s' for '%s'.\n", path, buf);
1296 : return 0;
1297 : /* LCOV_EXCL_STOP */
1298 : }
1299 :
1300 286 : mi = strtoul(e + 1, &e, 10);
1301 286 : if (*e != 0 && !isspace(*e)) {
1302 : /* LCOV_EXCL_START */
1303 : log_fatal(EEXTERNAL, "Invalid format in '%s' for '%s'.\n", path, buf);
1304 : return 0;
1305 : /* LCOV_EXCL_STOP */
1306 : }
1307 :
1308 286 : return makedev(ma, mi);
1309 : }
1310 : #endif
1311 :
1312 : /**
1313 : * Read a device tree filling the specified list of disk_t entries.
1314 : */
1315 : #if HAVE_LINUX_DEVICE
1316 286 : static int devtree(devinfo_t* parent, dev_t device, tommy_list* list)
1317 : {
1318 : char path[PATH_MAX];
1319 : DIR* d;
1320 286 : int slaves = 0;
1321 :
1322 286 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/slaves", major(device), minor(device));
1323 :
1324 : /* check if there is a slaves list */
1325 286 : d = opendir(path);
1326 286 : if (d != 0) {
1327 : struct dirent* dd;
1328 :
1329 564 : while ((dd = readdir(d)) != 0) {
1330 423 : if (dd->d_name[0] != '.') {
1331 : dev_t subdev;
1332 :
1333 : /* for each slave, expand the full potential tree */
1334 141 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/slaves/%s/dev", major(device), minor(device), dd->d_name);
1335 :
1336 141 : subdev = devread(path);
1337 141 : if (!subdev) {
1338 : /* LCOV_EXCL_START */
1339 : closedir(d);
1340 : return -1;
1341 : /* LCOV_EXCL_STOP */
1342 : }
1343 :
1344 141 : if (devtree(parent, subdev, list) != 0) {
1345 : /* LCOV_EXCL_START */
1346 : closedir(d);
1347 : return -1;
1348 : /* LCOV_EXCL_STOP */
1349 : }
1350 :
1351 141 : ++slaves;
1352 : }
1353 : }
1354 :
1355 141 : closedir(d);
1356 : }
1357 :
1358 : /* if no slaves found */
1359 286 : if (!slaves) {
1360 : /* this is a raw device */
1361 : devinfo_t* devinfo;
1362 :
1363 : /* check if it's a real device */
1364 145 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device", major(device), minor(device));
1365 145 : if (access(path, F_OK) != 0) {
1366 : /* get the parent device */
1367 145 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/../dev", major(device), minor(device));
1368 :
1369 145 : device = devread(path);
1370 145 : if (!device) {
1371 : /* LCOV_EXCL_START */
1372 : return -1;
1373 : /* LCOV_EXCL_STOP */
1374 : }
1375 : }
1376 :
1377 : /* get the device file */
1378 145 : if (devresolve(device, path, sizeof(path)) != 0) {
1379 : /* LCOV_EXCL_START */
1380 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1381 : return -1;
1382 : /* LCOV_EXCL_STOP */
1383 : }
1384 :
1385 145 : devinfo = calloc_nofail(1, sizeof(devinfo_t));
1386 :
1387 145 : devinfo->device = device;
1388 145 : pathcpy(devinfo->name, sizeof(devinfo->name), parent->name);
1389 145 : pathcpy(devinfo->smartctl, sizeof(devinfo->smartctl), parent->smartctl);
1390 145 : memcpy(devinfo->smartignore, parent->smartignore, sizeof(devinfo->smartignore));
1391 145 : pathcpy(devinfo->file, sizeof(devinfo->file), path);
1392 145 : devinfo->parent = parent;
1393 :
1394 : /* insert in the list */
1395 145 : tommy_list_insert_tail(list, &devinfo->node, devinfo);
1396 : }
1397 :
1398 286 : return 0;
1399 : }
1400 : #endif
1401 :
1402 : /**
1403 : * Compute disk usage by aggregating access statistics.
1404 : */
1405 : #if HAVE_LINUX_DEVICE
1406 145 : static int devstat(dev_t device, uint64_t* count)
1407 : {
1408 : char path[PATH_MAX];
1409 : char buf[512];
1410 : int token;
1411 : int ret;
1412 : char* i;
1413 :
1414 145 : *count = 0;
1415 :
1416 145 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/stat", major(device), minor(device));
1417 :
1418 145 : ret = sysread(path, buf, sizeof(buf));
1419 145 : if (ret < 0) {
1420 : /* LCOV_EXCL_START */
1421 : log_fatal(errno, "Failed to read '%s'.\n", path);
1422 : return -1;
1423 : /* LCOV_EXCL_STOP */
1424 : }
1425 145 : if ((size_t)ret + 1 > sizeof(buf)) {
1426 : /* LCOV_EXCL_START */
1427 : log_fatal(EEXTERNAL, "Too long read '%s'.\n", path);
1428 : return -1;
1429 : /* LCOV_EXCL_STOP */
1430 : }
1431 :
1432 : /* ending 0 */
1433 145 : buf[ret] = 0;
1434 :
1435 145 : i = buf;
1436 145 : token = 1; /* token number */
1437 725 : while (*i) {
1438 : char* n;
1439 : char* e;
1440 : unsigned long long v;
1441 :
1442 : /* skip spaces */
1443 2206 : while (*i && isspace(*i))
1444 1481 : ++i;
1445 :
1446 : /* read digits */
1447 725 : n = i;
1448 5251 : while (*i && isdigit(*i))
1449 4526 : ++i;
1450 :
1451 725 : if (i == n) /* if no digit, abort */
1452 145 : break;
1453 725 : if (*i == 0 || !isspace(*i)) /* if no space, abort */
1454 : break;
1455 725 : *i++ = 0; /* put a terminator */
1456 :
1457 725 : v = strtoull(n, &e, 10);
1458 725 : if (*e != 0)
1459 0 : break;
1460 :
1461 : /* sum reads and writes completed */
1462 725 : if (token == 1 || token == 5) {
1463 290 : *count += v;
1464 290 : if (token == 5)
1465 145 : break; /* stop here */
1466 : }
1467 :
1468 580 : ++token;
1469 : }
1470 :
1471 145 : return 0;
1472 : }
1473 : #endif
1474 :
1475 : /**
1476 : * Get SMART attributes.
1477 : */
1478 : #if HAVE_LINUX_DEVICE
1479 26 : static int devsmart(dev_t device, const char* name, const char* smartctl, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* interface)
1480 : {
1481 : char cmd[PATH_MAX + 64];
1482 : char file[PATH_MAX];
1483 : FILE* f;
1484 : int ret;
1485 : const char* x;
1486 :
1487 26 : x = find_smartctl();
1488 26 : if (!x) {
1489 : /* LCOV_EXCL_START */
1490 : log_fatal(EEXTERNAL, "Cannot find smartctl.\n");
1491 : return -1;
1492 : /* LCOV_EXCL_STOP */
1493 : }
1494 :
1495 26 : if (devresolve(device, file, sizeof(file)) != 0) {
1496 : /* LCOV_EXCL_START */
1497 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1498 : return -1;
1499 : /* LCOV_EXCL_STOP */
1500 : }
1501 :
1502 : /* if there is a custom smartctl command */
1503 26 : if (smartctl[0]) {
1504 : char option[PATH_MAX];
1505 0 : snprintf(option, sizeof(option), smartctl, file);
1506 0 : snprintf(cmd, sizeof(cmd), "%s -a %s", x, option);
1507 : } else {
1508 26 : snprintf(cmd, sizeof(cmd), "%s -a %s", x, file);
1509 : }
1510 :
1511 26 : log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
1512 :
1513 26 : f = popen(cmd, "r");
1514 26 : if (!f) {
1515 : /* LCOV_EXCL_START */
1516 : log_tag("device:%s:%s:shell\n", file, name);
1517 : log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd);
1518 : return -1;
1519 : /* LCOV_EXCL_STOP */
1520 : }
1521 :
1522 26 : if (smartctl_attribute(f, file, name, smart, info, serial, family, model, interface) != 0) {
1523 : /* LCOV_EXCL_START */
1524 : pclose(f);
1525 : log_tag("device:%s:%s:shell\n", file, name);
1526 : return -1;
1527 : /* LCOV_EXCL_STOP */
1528 : }
1529 :
1530 26 : ret = pclose(f);
1531 :
1532 26 : log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
1533 :
1534 26 : if (!WIFEXITED(ret)) {
1535 : /* LCOV_EXCL_START */
1536 : log_tag("device:%s:%s:abort\n", file, name);
1537 : log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd);
1538 : return -1;
1539 : /* LCOV_EXCL_STOP */
1540 : }
1541 26 : if (WEXITSTATUS(ret) == 127) {
1542 : /* LCOV_EXCL_START */
1543 : log_tag("device:%s:%s:shell\n", file, name);
1544 : log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd);
1545 : return -1;
1546 : /* LCOV_EXCL_STOP */
1547 : }
1548 :
1549 : /* store the return smartctl return value */
1550 26 : smart[SMART_FLAGS].raw = WEXITSTATUS(ret);
1551 :
1552 26 : return 0;
1553 : }
1554 : #endif
1555 :
1556 : /**
1557 : * Get device attributes.
1558 : */
1559 : #if HAVE_LINUX_DEVICE
1560 54 : static void devattr(dev_t device, uint64_t* info, char* serial, char* family, char* model, char* interface)
1561 : {
1562 : char path[PATH_MAX];
1563 : char buf[512];
1564 : int ret;
1565 :
1566 : (void)family; /* not available, smartctl uses an internal database to get it */
1567 :
1568 54 : if (info[INFO_SIZE] == SMART_UNASSIGNED) {
1569 12 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/size", major(device), minor(device));
1570 12 : if (sysattr(path, buf, sizeof(buf)) == 0) {
1571 : char* e;
1572 : uint64_t v;
1573 12 : v = strtoul(buf, &e, 10);
1574 12 : if (*e == 0)
1575 12 : info[INFO_SIZE] = v * 512;
1576 : }
1577 : }
1578 :
1579 54 : if (info[INFO_ROTATION_RATE] == SMART_UNASSIGNED) {
1580 12 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/queue/rotational", major(device), minor(device));
1581 12 : if (sysattr(path, buf, sizeof(buf)) == 0) {
1582 : char* e;
1583 : uint64_t v;
1584 12 : v = strtoul(buf, &e, 10);
1585 12 : if (*e == 0)
1586 12 : info[INFO_ROTATION_RATE] = v;
1587 : }
1588 : }
1589 :
1590 54 : if (*model == 0) {
1591 12 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device/model", major(device), minor(device));
1592 12 : if (sysattr(path, buf, sizeof(buf)) == 0) {
1593 12 : if (buf[0] != 0)
1594 12 : pathcpy(model, SMART_MAX, buf);
1595 : }
1596 : }
1597 :
1598 54 : if (*serial == 0) {
1599 54 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device/serial", major(device), minor(device));
1600 54 : if (sysattr(path, buf, sizeof(buf)) == 0) {
1601 0 : if (buf[0] != 0)
1602 0 : pathcpy(serial, SMART_MAX, buf);
1603 : }
1604 : }
1605 :
1606 54 : if (*serial == 0) {
1607 : // --- Page 0x80: Unit Serial Number ---
1608 54 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device/vpd_pg80", major(device), minor(device));
1609 54 : ret = sysread(path, buf, sizeof(buf));
1610 54 : if (ret > 4) {
1611 54 : unsigned len = (unsigned char)buf[3];
1612 54 : if (4 + len <= (size_t)ret && 4 + len + 1 <= sizeof(buf)) {
1613 54 : char* attr = buf + 4;
1614 54 : attr[len] = 0;
1615 54 : strtrim(attr);
1616 54 : if (*attr)
1617 54 : pathcpy(serial, SMART_MAX, attr);
1618 : }
1619 : }
1620 : }
1621 :
1622 : /* always override interface if it's usb */
1623 54 : pathprint(path, sizeof(path), "/sys/dev/block/%u:%u", major(device), minor(device));
1624 54 : ret = readlink(path, buf, sizeof(buf));
1625 54 : if (ret > 0 && (unsigned)ret < sizeof(buf)) {
1626 54 : buf[ret] = 0;
1627 54 : if (strstr(buf, "usb") != 0)
1628 0 : strcpy(interface, "USB");
1629 : }
1630 54 : }
1631 : #endif
1632 :
1633 : /**
1634 : * Get POWER state.
1635 : */
1636 : #if HAVE_LINUX_DEVICE
1637 28 : static int devprobe(dev_t device, const char* name, const char* smartctl, int* power, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* interface)
1638 : {
1639 : char cmd[PATH_MAX + 64];
1640 : char file[PATH_MAX];
1641 : FILE* f;
1642 : int ret;
1643 : const char* x;
1644 :
1645 28 : x = find_smartctl();
1646 28 : if (!x) {
1647 : /* LCOV_EXCL_START */
1648 : log_fatal(EEXTERNAL, "Cannot find smartctl.\n");
1649 : return -1;
1650 : /* LCOV_EXCL_STOP */
1651 : }
1652 :
1653 28 : if (devresolve(device, file, sizeof(file)) != 0) {
1654 : /* LCOV_EXCL_START */
1655 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1656 : return -1;
1657 : /* LCOV_EXCL_STOP */
1658 : }
1659 :
1660 : /* if there is a custom smartctl command */
1661 28 : if (smartctl[0]) {
1662 : char option[PATH_MAX];
1663 0 : snprintf(option, sizeof(option), smartctl, file);
1664 0 : snprintf(cmd, sizeof(cmd), "%s -n standby,3 -a %s", x, option);
1665 : } else {
1666 28 : snprintf(cmd, sizeof(cmd), "%s -n standby,3 -a %s", x, file);
1667 : }
1668 :
1669 28 : log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
1670 :
1671 28 : f = popen(cmd, "r");
1672 28 : if (!f) {
1673 : /* LCOV_EXCL_START */
1674 : log_tag("device:%s:%s:shell\n", file, name);
1675 : log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd);
1676 : return -1;
1677 : /* LCOV_EXCL_STOP */
1678 : }
1679 :
1680 28 : if (smartctl_attribute(f, file, name, smart, info, serial, family, model, interface) != 0) {
1681 : /* LCOV_EXCL_START */
1682 : pclose(f);
1683 : log_tag("device:%s:%s:shell\n", file, name);
1684 : return -1;
1685 : /* LCOV_EXCL_STOP */
1686 : }
1687 :
1688 28 : ret = pclose(f);
1689 :
1690 28 : log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
1691 :
1692 28 : if (!WIFEXITED(ret)) {
1693 : /* LCOV_EXCL_START */
1694 : log_tag("device:%s:%s:abort\n", file, name);
1695 : log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd);
1696 : return -1;
1697 : /* LCOV_EXCL_STOP */
1698 : }
1699 28 : if (WEXITSTATUS(ret) == 127) {
1700 : /* LCOV_EXCL_START */
1701 : log_tag("device:%s:%s:shell\n", file, name);
1702 : log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd);
1703 : return -1;
1704 : /* LCOV_EXCL_STOP */
1705 : }
1706 :
1707 28 : if (WEXITSTATUS(ret) == 3) {
1708 12 : log_tag("attr:%s:%s:power:standby\n", file, name);
1709 12 : *power = POWER_STANDBY;
1710 : } else {
1711 16 : log_tag("attr:%s:%s:power:active\n", file, name);
1712 16 : *power = POWER_ACTIVE;
1713 :
1714 : /* store the smartctl return value */
1715 16 : if (smart)
1716 16 : smart[SMART_FLAGS].raw = WEXITSTATUS(ret);
1717 : }
1718 :
1719 28 : return 0;
1720 : }
1721 : #endif
1722 :
1723 : /**
1724 : * Spin down a specific device.
1725 : */
1726 : #if HAVE_LINUX_DEVICE
1727 17 : static int devdown(dev_t device, const char* name, const char* smartctl)
1728 : {
1729 : char cmd[PATH_MAX + 64];
1730 : char file[PATH_MAX];
1731 : FILE* f;
1732 : int ret;
1733 : const char* x;
1734 :
1735 17 : x = find_smartctl();
1736 17 : if (!x) {
1737 : /* LCOV_EXCL_START */
1738 : log_fatal(EEXTERNAL, "Cannot find smartctl.\n");
1739 : return -1;
1740 : /* LCOV_EXCL_STOP */
1741 : }
1742 :
1743 17 : if (devresolve(device, file, sizeof(file)) != 0) {
1744 : /* LCOV_EXCL_START */
1745 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1746 : return -1;
1747 : /* LCOV_EXCL_STOP */
1748 : }
1749 :
1750 : /* if there is a custom smartctl command */
1751 17 : if (smartctl[0]) {
1752 : char option[PATH_MAX];
1753 5 : snprintf(option, sizeof(option), smartctl, file);
1754 5 : snprintf(cmd, sizeof(cmd), "%s -s standby,now %s", x, option);
1755 : } else {
1756 12 : snprintf(cmd, sizeof(cmd), "%s -s standby,now %s", x, file);
1757 : }
1758 :
1759 17 : log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
1760 :
1761 17 : f = popen(cmd, "r");
1762 17 : if (!f) {
1763 : /* LCOV_EXCL_START */
1764 : log_tag("device:%s:%s:shell\n", file, name);
1765 : log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd);
1766 : return -1;
1767 : /* LCOV_EXCL_STOP */
1768 : }
1769 :
1770 17 : if (smartctl_flush(f, file, name) != 0) {
1771 : /* LCOV_EXCL_START */
1772 : pclose(f);
1773 : log_tag("device:%s:%s:shell\n", file, name);
1774 : return -1;
1775 : /* LCOV_EXCL_STOP */
1776 : }
1777 :
1778 17 : ret = pclose(f);
1779 :
1780 17 : log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
1781 :
1782 17 : if (!WIFEXITED(ret)) {
1783 : /* LCOV_EXCL_START */
1784 : log_tag("device:%s:%s:abort\n", file, name);
1785 : log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd);
1786 : return -1;
1787 : /* LCOV_EXCL_STOP */
1788 : }
1789 17 : if (WEXITSTATUS(ret) == 127) {
1790 : /* LCOV_EXCL_START */
1791 : log_tag("device:%s:%s:shell\n", file, name);
1792 : log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd);
1793 : return -1;
1794 : /* LCOV_EXCL_STOP */
1795 : }
1796 17 : if (WEXITSTATUS(ret) != 0) {
1797 : /* LCOV_EXCL_START */
1798 : log_tag("device:%s:%s:exit:%d\n", file, name, WEXITSTATUS(ret));
1799 : log_fatal(EEXTERNAL, "Failed to run '%s' with return code %xh.\n", cmd, WEXITSTATUS(ret));
1800 : return -1;
1801 : /* LCOV_EXCL_STOP */
1802 : }
1803 :
1804 12 : log_tag("attr:%s:%s:power:down\n", file, name);
1805 :
1806 12 : return 0;
1807 : }
1808 : #endif
1809 :
1810 : /**
1811 : * Spin down a specific device if it's up.
1812 : */
1813 : #if HAVE_LINUX_DEVICE
1814 0 : static int devdownifup(dev_t device, const char* name, const char* smartctl, int* power)
1815 : {
1816 0 : *power = POWER_UNKNOWN;
1817 :
1818 0 : if (devprobe(device, name, smartctl, power, 0, 0, 0, 0, 0, 0) != 0)
1819 0 : return -1;
1820 :
1821 0 : if (*power == POWER_ACTIVE)
1822 0 : return devdown(device, name, smartctl);
1823 :
1824 0 : return 0;
1825 : }
1826 : #endif
1827 :
1828 : /**
1829 : * Spin up a specific device.
1830 : */
1831 : #if HAVE_LINUX_DEVICE
1832 42 : static int devup(dev_t device, const char* name)
1833 : {
1834 : char file[PATH_MAX];
1835 : int ret;
1836 : int f;
1837 : void* buf;
1838 : uint64_t size;
1839 : uint64_t offset;
1840 :
1841 42 : if (devresolve(device, file, sizeof(file)) != 0) {
1842 : /* LCOV_EXCL_START */
1843 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device));
1844 : return -1;
1845 : /* LCOV_EXCL_STOP */
1846 : }
1847 :
1848 : /* O_DIRECT requires memory aligned to the block size */
1849 42 : if (posix_memalign(&buf, 4096, 4096) != 0) {
1850 : /* LCOV_EXCL_START */
1851 : log_fatal(errno, "Failed to allocate aligned memory for device '%u:%u'.\n", major(device), minor(device));
1852 : return -1;
1853 : /* LCOV_EXCL_STOP */
1854 : }
1855 :
1856 42 : f = open(file, O_RDONLY | O_DIRECT);
1857 42 : if (f < 0) {
1858 : /* LCOV_EXCL_START */
1859 : free(buf);
1860 : log_tag("device:%s:%s:error:%d\n", file, name, errno);
1861 : log_fatal(errno, "Failed to open device '%u:%u'.\n", major(device), minor(device));
1862 : return -1;
1863 : /* LCOV_EXCL_STOP */
1864 : }
1865 :
1866 12 : if (ioctl(f, BLKGETSIZE64, &size) < 0) {
1867 : /* LCOV_EXCL_START */
1868 : close(f);
1869 : free(buf);
1870 : log_tag("device:%s:%s:error:%d\n", file, name, errno);
1871 : log_fatal(errno, "Failed to get device size '%u:%u'.\n", major(device), minor(device));
1872 : return -1;
1873 : /* LCOV_EXCL_STOP */
1874 : }
1875 :
1876 : /* select a random offset */
1877 12 : offset = (random_u64() % (size / 4096)) * 4096;
1878 :
1879 : #if HAVE_POSIX_FADVISE
1880 : /* clear cache */
1881 12 : ret = posix_fadvise(f, offset, 4096, POSIX_FADV_DONTNEED);
1882 12 : if (ret != 0) {
1883 : /* LCOV_EXCL_START */
1884 : close(f);
1885 : free(buf);
1886 : log_tag("device:%s:%s:error:%d\n", file, name, errno);
1887 : log_fatal(errno, "Failed to advise device '%u:%u'.\n", major(device), minor(device));
1888 : return -1;
1889 : /* LCOV_EXCL_STOP */
1890 : }
1891 : #endif
1892 :
1893 12 : ret = pread(f, buf, 4096, offset);
1894 12 : if (ret < 0) {
1895 : /* LCOV_EXCL_START */
1896 : close(f);
1897 : free(buf);
1898 : log_tag("device:%s:%s:error:%d\n", file, name, errno);
1899 : log_fatal(errno, "Failed to read device '%u:%u'.\n", major(device), minor(device));
1900 : return -1;
1901 : /* LCOV_EXCL_STOP */
1902 : }
1903 :
1904 12 : ret = close(f);
1905 12 : if (ret < 0) {
1906 : /* LCOV_EXCL_START */
1907 : free(buf);
1908 : log_tag("device:%s:%s:error:%d\n", file, name, errno);
1909 : log_fatal(errno, "Failed to close device '%u:%u'.\n", major(device), minor(device));
1910 : return -1;
1911 : /* LCOV_EXCL_STOP */
1912 : }
1913 :
1914 12 : log_tag("attr:%s:%s:power:up\n", file, name);
1915 :
1916 12 : free(buf);
1917 12 : return 0;
1918 : }
1919 : #endif
1920 :
1921 : /**
1922 : * Thread for spinning up.
1923 : *
1924 : * Note that filling up the devinfo object is done inside this thread,
1925 : * to avoid to block the main thread if the device need to be spin up
1926 : * to handle stat/resolve requests.
1927 : */
1928 42 : static void* thread_spinup(void* arg)
1929 : {
1930 : #if HAVE_LINUX_DEVICE
1931 42 : devinfo_t* devinfo = arg;
1932 : uint64_t start;
1933 :
1934 42 : start = os_tick_ms();
1935 :
1936 42 : if (devup(devinfo->device, devinfo->name) != 0) {
1937 : /* LCOV_EXCL_START */
1938 : return (void*)-1;
1939 : /* LCOV_EXCL_STOP */
1940 : }
1941 :
1942 12 : msg_status("Spunup device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start);
1943 :
1944 : /* after the spin up, get SMART info */
1945 12 : if (devsmart(devinfo->device, devinfo->name, devinfo->smartctl, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) {
1946 : /* LCOV_EXCL_START */
1947 : return (void*)-1;
1948 : /* LCOV_EXCL_STOP */
1949 : }
1950 :
1951 : /*
1952 : * Retrieve some attributes directly from the system.
1953 : *
1954 : * smartctl intentionally skips queries on devices in standby mode
1955 : * to prevent accidentally spinning them up.
1956 : */
1957 12 : devattr(devinfo->device, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf);
1958 :
1959 12 : return 0;
1960 : #else
1961 : (void)arg;
1962 : return (void*)-1;
1963 : #endif
1964 : }
1965 :
1966 : /**
1967 : * Thread for spinning down.
1968 : */
1969 17 : static void* thread_spindown(void* arg)
1970 : {
1971 : #if HAVE_LINUX_DEVICE
1972 17 : devinfo_t* devinfo = arg;
1973 : uint64_t start;
1974 :
1975 17 : start = os_tick_ms();
1976 :
1977 17 : if (devdown(devinfo->device, devinfo->name, devinfo->smartctl) != 0) {
1978 : /* LCOV_EXCL_START */
1979 : return (void*)-1;
1980 : /* LCOV_EXCL_STOP */
1981 : }
1982 :
1983 12 : msg_status("Spundown device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start);
1984 :
1985 12 : return 0;
1986 : #else
1987 : (void)arg;
1988 : return (void*)-1;
1989 : #endif
1990 : }
1991 :
1992 : /**
1993 : * Thread for spinning down.
1994 : */
1995 0 : static void* thread_spindownifup(void* arg)
1996 : {
1997 : #if HAVE_LINUX_DEVICE
1998 0 : devinfo_t* devinfo = arg;
1999 : uint64_t start;
2000 : int power;
2001 :
2002 0 : start = os_tick_ms();
2003 :
2004 0 : if (devdownifup(devinfo->device, devinfo->name, devinfo->smartctl, &power) != 0) {
2005 : /* LCOV_EXCL_START */
2006 : return (void*)-1;
2007 : /* LCOV_EXCL_STOP */
2008 : }
2009 :
2010 0 : if (power == POWER_ACTIVE)
2011 0 : msg_status("Spundown device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start);
2012 :
2013 0 : return 0;
2014 : #else
2015 : (void)arg;
2016 : return (void*)-1;
2017 : #endif
2018 : }
2019 :
2020 : /**
2021 : * Thread for getting smart info.
2022 : */
2023 14 : static void* thread_smart(void* arg)
2024 : {
2025 : #if HAVE_LINUX_DEVICE
2026 14 : devinfo_t* devinfo = arg;
2027 :
2028 14 : if (devsmart(devinfo->device, devinfo->name, devinfo->smartctl, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) {
2029 : /* LCOV_EXCL_START */
2030 : return (void*)-1;
2031 : /* LCOV_EXCL_STOP */
2032 : }
2033 :
2034 : /*
2035 : * Retrieve some attributes directly from the system.
2036 : *
2037 : * smartctl intentionally skips queries on devices in standby mode
2038 : * to prevent accidentally spinning them up.
2039 : */
2040 14 : devattr(devinfo->device, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf);
2041 :
2042 14 : return 0;
2043 : #else
2044 : (void)arg;
2045 : return (void*)-1;
2046 : #endif
2047 : }
2048 :
2049 : /**
2050 : * Thread for getting power info.
2051 : */
2052 28 : static void* thread_probe(void* arg)
2053 : {
2054 : #if HAVE_LINUX_DEVICE
2055 28 : devinfo_t* devinfo = arg;
2056 :
2057 28 : if (devprobe(devinfo->device, devinfo->name, devinfo->smartctl, &devinfo->power, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) {
2058 : /* LCOV_EXCL_START */
2059 : return (void*)-1;
2060 : /* LCOV_EXCL_STOP */
2061 : }
2062 :
2063 : /*
2064 : * Retrieve some attributes directly from the system.
2065 : *
2066 : * smartctl intentionally skips queries on devices in standby mode
2067 : * to prevent accidentally spinning them up.
2068 : */
2069 28 : devattr(devinfo->device, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf);
2070 :
2071 28 : return 0;
2072 : #else
2073 : (void)arg;
2074 : return (void*)-1;
2075 : #endif
2076 : }
2077 :
2078 7 : static int device_thread(tommy_list* list, void* (*func)(void* arg))
2079 : {
2080 7 : int fail = 0;
2081 : tommy_node* i;
2082 :
2083 : #if HAVE_THREAD
2084 : /* start all threads */
2085 108 : for (i = tommy_list_head(list); i != 0; i = i->next) {
2086 101 : devinfo_t* devinfo = i->data;
2087 :
2088 101 : thread_create(&devinfo->thread, func, devinfo);
2089 : }
2090 :
2091 : /* join all threads */
2092 108 : for (i = tommy_list_head(list); i != 0; i = i->next) {
2093 101 : devinfo_t* devinfo = i->data;
2094 : void* retval;
2095 :
2096 101 : thread_join(devinfo->thread, &retval);
2097 :
2098 101 : if (retval != 0)
2099 35 : ++fail;
2100 : }
2101 : #else
2102 : for (i = tommy_list_head(list); i != 0; i = i->next) {
2103 : devinfo_t* devinfo = i->data;
2104 :
2105 : if (func(devinfo) != 0)
2106 : ++fail;
2107 : }
2108 : #endif
2109 7 : if (fail != 0) {
2110 : /* LCOV_EXCL_START */
2111 : return -1;
2112 : /* LCOV_EXCL_STOP */
2113 : }
2114 :
2115 5 : return 0;
2116 : }
2117 :
2118 9 : int devquery(tommy_list* high, tommy_list* low, int operation)
2119 : {
2120 9 : void* (*func)(void* arg) = 0;
2121 :
2122 : #if HAVE_LINUX_DEVICE
2123 : tommy_node* i;
2124 : struct stat st;
2125 :
2126 : /* sysfs interface is required */
2127 9 : if (stat("/sys/dev/block", &st) != 0) {
2128 : /* LCOV_EXCL_START */
2129 : log_fatal(EEXTERNAL, "Missing interface /sys/dev/block.\n");
2130 : return -1;
2131 : /* LCOV_EXCL_STOP */
2132 : }
2133 :
2134 : /* for each high device */
2135 154 : for (i = tommy_list_head(high); i != 0; i = i->next) {
2136 145 : devinfo_t* devinfo = i->data;
2137 145 : uint64_t device = devinfo->device;
2138 : uint64_t access_stat;
2139 :
2140 : #if HAVE_SYNCFS
2141 145 : if (operation == DEVICE_DOWN) {
2142 : /* flush the high level filesystem before spinning down */
2143 17 : int f = open(devinfo->mount, O_RDONLY);
2144 17 : if (f >= 0) {
2145 17 : syncfs(f);
2146 17 : close(f);
2147 : }
2148 : }
2149 : #endif
2150 :
2151 : /* if the major is the null device, find the real one */
2152 145 : if (major(device) == 0) {
2153 : /* obtain the real device */
2154 0 : if (devdereference(device, &device) != 0) {
2155 : /* LCOV_EXCL_START */
2156 : log_fatal(EEXTERNAL, "Failed to dereference device '%u:%u'.\n", major(device), minor(device));
2157 : return -1;
2158 : /* LCOV_EXCL_STOP */
2159 : }
2160 : }
2161 :
2162 : /* retrieve access stat for the high level device */
2163 145 : if (devstat(device, &access_stat) == 0) {
2164 : /* cumulate access stat in the first split */
2165 145 : if (devinfo->split)
2166 39 : devinfo->split->access_stat += access_stat;
2167 : else
2168 106 : devinfo->access_stat += access_stat;
2169 : }
2170 :
2171 : /* get the device file */
2172 145 : if (devresolve(device, devinfo->file, sizeof(devinfo->file)) != 0) {
2173 : /* LCOV_EXCL_START */
2174 : log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device));
2175 : return -1;
2176 : /* LCOV_EXCL_STOP */
2177 : }
2178 :
2179 : /* expand the tree of devices */
2180 145 : if (devtree(devinfo, device, low) != 0) {
2181 : /* LCOV_EXCL_START */
2182 : log_fatal(EEXTERNAL, "Failed to expand device '%u:%u'.\n", major(device), minor(device));
2183 : return -1;
2184 : /* LCOV_EXCL_STOP */
2185 : }
2186 : }
2187 : #else
2188 : (void)high;
2189 : #endif
2190 :
2191 9 : switch (operation) {
2192 2 : case DEVICE_UP : func = thread_spinup; break;
2193 2 : case DEVICE_DOWN : func = thread_spindown; break;
2194 1 : case DEVICE_SMART : func = thread_smart; break;
2195 2 : case DEVICE_PROBE : func = thread_probe; break;
2196 0 : case DEVICE_DOWNIFUP : func = thread_spindownifup; break;
2197 : }
2198 :
2199 9 : if (!func)
2200 2 : return 0;
2201 :
2202 7 : return device_thread(low, func);
2203 : }
2204 :
2205 297 : void os_init(int opt)
2206 : {
2207 : #if HAVE_BLKID
2208 : int ret;
2209 297 : ret = blkid_get_cache(&cache, NULL);
2210 297 : if (ret != 0) {
2211 : /* LCOV_EXCL_START */
2212 : log_fatal(EEXTERNAL, "WARNING Failed to get blkid cache\n");
2213 : /* LCOV_EXCL_STOP */
2214 : }
2215 : #endif
2216 :
2217 : (void)opt;
2218 297 : }
2219 :
2220 286 : void os_done(void)
2221 : {
2222 : #if HAVE_BLKID
2223 286 : if (cache != 0)
2224 286 : blkid_put_cache(cache);
2225 : #endif
2226 286 : }
2227 :
2228 : /* LCOV_EXCL_START */
2229 : void os_abort(void)
2230 : {
2231 : #if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS
2232 : void* stack[32];
2233 : char** messages;
2234 : size_t size;
2235 : unsigned i;
2236 : #endif
2237 :
2238 : printf("Stacktrace of " PACKAGE " v" VERSION);
2239 : #ifdef _linux
2240 : printf(", linux");
2241 : #endif
2242 : #ifdef __GNUC__
2243 : printf(", gcc " __VERSION__);
2244 : #endif
2245 : printf(", %d-bit", (int)sizeof(void*) * 8);
2246 : printf(", PATH_MAX=%d", PATH_MAX);
2247 : printf("\n");
2248 :
2249 : #if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS
2250 : size = backtrace(stack, 32);
2251 :
2252 : messages = backtrace_symbols(stack, size);
2253 :
2254 : for (i = 1; i < size; ++i) {
2255 : const char* msg;
2256 :
2257 : if (messages)
2258 : msg = messages[i];
2259 : else
2260 : msg = "<unknown>";
2261 :
2262 : printf("[bt] %02u: %s\n", i, msg);
2263 :
2264 : if (messages) {
2265 : int ret;
2266 : char addr2line[1024];
2267 : size_t j = 0;
2268 : while (msg[j] != '(' && msg[j] != ' ' && msg[j] != 0)
2269 : ++j;
2270 :
2271 : snprintf(addr2line, sizeof(addr2line), "addr2line %p -e %.*s", stack[i], (unsigned)j, msg);
2272 :
2273 : ret = system(addr2line);
2274 : if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0)
2275 : printf("exit:%d\n", WEXITSTATUS(ret));
2276 : if (WIFSIGNALED(ret))
2277 : printf("signal:%d\n", WTERMSIG(ret));
2278 : }
2279 : }
2280 : #endif
2281 :
2282 : printf("Please report this error to the SnapRAID Forum:\n");
2283 : printf("https://sourceforge.net/p/snapraid/discussion/1677233/\n");
2284 :
2285 : abort();
2286 : }
2287 : /* LCOV_EXCL_STOP */
2288 :
2289 0 : void os_clear(void)
2290 : {
2291 : /* ANSI codes */
2292 0 : printf("\033[H"); /* cursor at topleft */
2293 0 : printf("\033[2J"); /* clear screen */
2294 0 : }
2295 :
2296 135 : size_t direct_size(void)
2297 : {
2298 : long size;
2299 :
2300 135 : size = sysconf(_SC_PAGESIZE);
2301 :
2302 135 : if (size == -1) {
2303 : /* LCOV_EXCL_START */
2304 : log_fatal(EEXTERNAL, "No page size\n");
2305 : exit(EXIT_FAILURE);
2306 : /* LCOV_EXCL_STOP */
2307 : }
2308 :
2309 135 : return size;
2310 : }
2311 :
2312 : #if HAVE_LINUX_DEVICE
2313 :
2314 : /* List of possible ambient temperature labels */
2315 : const char* AMBIENT_LABEL[] = {
2316 : "systin",
2317 : "auxtin",
2318 : "mb",
2319 : "m/b",
2320 : "board",
2321 : "motherboard",
2322 : "system",
2323 : "chassis",
2324 : "case",
2325 : "room",
2326 : "ambient",
2327 : 0
2328 : };
2329 :
2330 1 : int ambient_temperature(void)
2331 : {
2332 : DIR* dir;
2333 : struct dirent* entry;
2334 1 : int lowest_temp = 0;
2335 :
2336 1 : dir = opendir("/sys/class/hwmon");
2337 1 : if (!dir)
2338 0 : return 0;
2339 :
2340 : /* iterate through hwmon devices */
2341 5 : while ((entry = readdir(dir)) != NULL) {
2342 : char path[PATH_MAX];
2343 : DIR* hwmon_dir;
2344 : struct dirent* hwmon_entry;
2345 :
2346 4 : if (strncmp(entry->d_name, "hwmon", 5) != 0)
2347 2 : continue;
2348 :
2349 2 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s", entry->d_name);
2350 :
2351 : /* iterate through temp*_input files */
2352 2 : hwmon_dir = opendir(path);
2353 2 : if (!hwmon_dir)
2354 0 : continue;
2355 :
2356 66 : while ((hwmon_entry = readdir(hwmon_dir)) != NULL) {
2357 : char value[128];
2358 : char name[128];
2359 : char label[128];
2360 : char* dash;
2361 : char* e;
2362 : long temp;
2363 :
2364 64 : if (strncmp(hwmon_entry->d_name, "temp", 4) != 0)
2365 63 : continue;
2366 :
2367 18 : dash = strrchr(hwmon_entry->d_name, '_');
2368 18 : if (dash == 0)
2369 0 : continue;
2370 :
2371 18 : if (strcmp(dash, "_input") != 0)
2372 14 : continue;
2373 :
2374 : /* read the temperature */
2375 4 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s/%s", entry->d_name, hwmon_entry->d_name);
2376 :
2377 4 : if (sysattr(path, value, sizeof(value)) != 0)
2378 0 : continue;
2379 :
2380 4 : temp = strtol(value, &e, 10) / 1000;
2381 4 : if (*e != 0 && !isspace(*e))
2382 0 : continue;
2383 :
2384 : /* cut the file name at "_input" */
2385 4 : *dash = 0;
2386 :
2387 : /* read the corresponding name */
2388 4 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s/name", entry->d_name);
2389 4 : if (sysattr(path, name, sizeof(name)) != 0) {
2390 : /* fallback to using the hwmon name */
2391 0 : pathcpy(name, sizeof(name), entry->d_name);
2392 : }
2393 :
2394 : /* read the corresponding label file */
2395 4 : pathprint(path, sizeof(path), "/sys/class/hwmon/%s/%s_label", entry->d_name, hwmon_entry->d_name);
2396 4 : if (sysattr(path, label, sizeof(label)) != 0) {
2397 : /* fallback to using the temp* name (e.g., temp1, temp2) */
2398 0 : pathcpy(label, sizeof(label), hwmon_entry->d_name);
2399 : }
2400 :
2401 4 : log_tag("thermal:ambient:device:%s:%s:%s:%s:%ld\n", entry->d_name, name, hwmon_entry->d_name, label, temp);
2402 :
2403 : /* check if temperature is in reasonable range */
2404 4 : if (temp < 15 || temp > 40)
2405 3 : continue;
2406 :
2407 : /* lower case */
2408 1 : strlwr(label);
2409 :
2410 : /* check if label matches possible ambient labels */
2411 3 : for (int i = 0; AMBIENT_LABEL[i]; ++i) {
2412 3 : if (worddigitstr(label, AMBIENT_LABEL[i]) != 0) {
2413 1 : log_tag("thermal:ambient:candidate:%ld\n", temp);
2414 1 : if (lowest_temp == 0 || lowest_temp > temp)
2415 1 : lowest_temp = temp;
2416 1 : break;
2417 : }
2418 : }
2419 :
2420 : /* accept also generic "temp1" */
2421 1 : if (strcmp(label, "temp1") == 0) {
2422 0 : log_tag("thermal:ambient:candidate:%ld\n", temp);
2423 0 : if (lowest_temp == 0 || lowest_temp > temp)
2424 0 : lowest_temp = temp;
2425 : }
2426 : }
2427 :
2428 2 : closedir(hwmon_dir);
2429 : }
2430 :
2431 1 : closedir(dir);
2432 :
2433 1 : return lowest_temp;
2434 : }
2435 : #else
2436 : int ambient_temperature(void)
2437 : {
2438 : return 0;
2439 : }
2440 : #endif
2441 :
2442 : #endif
2443 :
|