// SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2013 Andrea Mazzoleni #include "portable.h" #ifndef __MINGW32__ /* Only for Unix */ #include "support.h" /****************************************************************************/ /* access */ #if !HAVE_EACCESS /** * Check effective user's permissions for a file. * Conceptually identical to access(), but uses the effective UID/GID. */ int eaccess(const char* pathname, int mode) { return faccessat(AT_FDCWD, pathname, mode, AT_EACCESS); } #endif /** * Exit codes. */ int exit_success = 0; int exit_failure = 1; int exit_sync_needed = 2; #if HAVE_POSIX_FADVISE /** * Wrapper around posix_fadvise() that handles ENOENT by converting it to * ENOSYS. * * Why can posix_fadvise() return ENOENT? * * According to POSIX and the Linux kernel's standard fadvise64 implementation * (mm/fadvise.c), posix_fadvise() should only return EBADF (bad file * descriptor), ESPIPE (fd refers to a pipe or FIFO), or EINVAL (invalid * advice value or arguments). ENOENT is never generated by the kernel's * generic path. * * However, the VFS layer allows individual filesystems to provide their own * .fadvise() method, which is called instead of (or in addition to) the * generic implementation. Certain filesystem drivers can return ENOENT in * this method for reasons that are completely unrelated to the file not * existing (the file is clearly open, we have a valid fd): * * - NFS: A stale NFS inode can be revalidated against the server while * fadvise is being processed. If the server-side file has been removed or * renamed, the revalidation discovers the inode is gone and returns * -ENOENT, even though the client still holds an open file descriptor. * * - FUSE-based filesystems (mergerfs, sshfs, rclone, etc.): The FUSE daemon * receives the fadvise request and may forward it to an underlying backend. * If that backend cannot find the file (e.g., the backing path has changed, * the FUSE daemon is partially initialised, or the daemon translates the * request and the target layer returns an error), the daemon may return * ENOENT to the kernel. * * - Overlayfs (used by Docker, Podman, LXC): The overlayfs .fadvise() * implementation delegates to the lower or upper layer. If the lookup * through the overlay stack fails to find the real inode (e.g., the upper * directory entry is absent and the lower copy is in a weird state), the * delegation can return ENOENT. * * - Other network or virtual filesystems: Any out-of-tree or in-tree * filesystem that implements .fadvise() and performs a path or inode * lookup as part of processing the hint can propagate ENOENT if that * internal lookup fails. * * Because posix_fadvise() is always an advisory hint, it never affects * correctness, only performance, it is safe to treat unexpected errors, * including ENOENT, as "not supported" (ENOSYS) and continue. */ int posix_fadvise_wrapper(int fd, off_t offset, off_t len, int advice) { int ret = posix_fadvise(fd, offset, len, advice); if (ret == ENOENT) return ENOSYS; return ret; } #endif int open_noatime(const char* file, int flags) { #ifdef O_NOATIME int f = open(file, flags | O_NOATIME); /* only root is allowed to use O_NOATIME, in case retry without it */ if (f == -1 && errno == EPERM) f = open(file, flags); return f; #else return open(file, flags); #endif } int dirent_hidden(struct dirent* dd) { return dd->d_name[0] == '.'; } const char* stat_desc(struct stat* st) { if (S_ISREG(st->st_mode)) return "regular"; if (S_ISDIR(st->st_mode)) return "directory"; if (S_ISCHR(st->st_mode)) return "character"; if (S_ISBLK(st->st_mode)) return "block-device"; if (S_ISFIFO(st->st_mode)) return "fifo"; if (S_ISLNK(st->st_mode)) return "symlink"; if (S_ISSOCK(st->st_mode)) return "socket"; return "unknown"; } static const char* smartctl_paths[] = { /* Linux & BSD */ "/usr/sbin/smartctl", "/sbin/smartctl", "/usr/local/sbin/smartctl", "/usr/bin/smartctl", "/usr/local/bin/smartctl", /* macOS (Intel & Apple Silicon) */ "/opt/homebrew/sbin/smartctl", 0 }; const char* find_smartctl(void) { int i; for (i = 0; smartctl_paths[i]; ++i) { if (eaccess(smartctl_paths[i], X_OK) == 0) return smartctl_paths[i]; } return 0; } /** * Read a file from sys * * Return -1 on error, otherwise the size of data read */ #if HAVE_LINUX_DEVICE static int sysread(const char* path, char* buf, size_t buf_size) { int f; int ret; int len; f = open(path, O_RDONLY); if (f == -1) { /* LCOV_EXCL_START */ return -1; /* LCOV_EXCL_STOP */ } len = read(f, buf, buf_size); if (len < 0) { /* LCOV_EXCL_START */ close(f); return -1; /* LCOV_EXCL_STOP */ } ret = close(f); if (ret != 0) { /* LCOV_EXCL_START */ return -1; /* LCOV_EXCL_STOP */ } return len; } #endif #if HAVE_LINUX_DEVICE /* * sysread_vpd_pg83 — parse raw binary VPD page 0x83 (Device Identification) * from a sysfs vpd_pg83 file and extract the first NAA (Network Address * Authority) designator for the Logical Unit (ASSOCIATION == 0x00). */ static int sysattr_vpd_pg80(const char* path, char* dst, size_t dst_size) { unsigned char buf[512]; int ret = sysread(path, (char*)buf, sizeof(buf)); /* need at least the header */ if (ret < 4) return -1; /* validate page code */ if (buf[1] != 0x80) return -1; /* clamp to what was actually read */ size_t page_len = buf[3]; size_t available = (size_t)ret - 4; if (available < page_len) page_len = available; /* if empty */ if (page_len == 0) return -1; /* if too bit to store the 0 termination */ if (4 + page_len + 1 > sizeof(buf)) return -1; char* attr = (char*)buf + 4; attr[page_len] = 0; strtrim(attr); /* if empty */ if (!*attr) return -1; pathcpy(dst, dst_size, attr); return 0; } #endif /* * sysread_vpd_pg83 — parse raw binary VPD page 0x83 (Device Identification) * from a sysfs vpd_pg83 file and extract the first NAA (Network Address * Authority) designator for the Logical Unit (ASSOCIATION == 0x00). * * The result is written into dst as a lowercase hex string, * e.g.: "5000c500abcdef01" (8-byte / NAA type 1,2,3,5) * "6000c500abcdef010000000000000000" (16-byte / NAA type 6) * * VPD page 0x83 wire format (SPC-4 §7.7.2) * * Page header — 4 bytes: * [0] peripheral qualifier (7:5) | device type (4:0) * [1] page code = 0x83 * [2] PAGE LENGTH MSB } 16-bit big-endian: number of bytes that follow * [3] PAGE LENGTH LSB } the header (i.e. total descriptor bytes) * * Then a sequence of Designation Descriptors: * [+0] PROTOCOL ID (7:4) | CODE SET (3:0) * 0x1 = binary, 0x2 = ASCII, 0x3 = UTF-8 * [+1] PIV (7) | reserved (6) | ASSOCIATION (5:4) | DESIGNATOR TYPE (3:0) * ASSOCIATION: 0x0 = logical unit ← we want only this * 0x1 = target port * 0x2 = target device * DESIGNATOR TYPE: 0x3 = NAA ← we want only this * [+2] reserved * [+3] DESIGNATOR LENGTH = N (bytes that follow in this descriptor) * [+4 … +4+N-1] DESIGNATOR (binary for NAA) * * NAA subtypes (top nibble of designator byte 0) * * 0x1 NAA IEEE Extended → 8 bytes * 0x2 NAA Locally Assigned → 8 bytes * 0x3 NAA IEEE Registered → 8 bytes (most common on SATA/SAS) * 0x5 NAA IEEE Registered → 8 bytes * 0x6 NAA IEEE Registered Ext. → 16 bytes * others: reserved * * The DESIGNATOR LENGTH from the descriptor header must agree with the * expected length for the NAA subtype. If they disagree, the descriptor * is malformed and must be skipped. * * Returns 0 and fills dst on success, -1 if no valid NAA LU descriptor found. */ #if HAVE_LINUX_DEVICE static int sysattr_vpd_pg83(const char* path, char* dst, size_t dst_size) { unsigned char buf[4096]; int ret = sysread(path, (char*)buf, sizeof(buf)); if (ret < 4) return -1; /* validate page code */ if (buf[1] != 0x83) return -1; size_t page_len = ((size_t)buf[2] << 8) | buf[3]; /* clamp to what was actually read */ size_t available = (size_t)ret - 4; if (page_len > available) page_len = available; /* if empty */ if (page_len == 0) return -1; /* * Walk every Designation Descriptor looking for the first NAA descriptor * that is associated with the Logical Unit (ASSOCIATION == 0x00). * * We take the first one found rather than scoring: all NAA subtypes are * globally unique by construction; the first LU-associated NAA is the * canonical device WWN. */ size_t offset = 0; while (offset + 4 <= page_len) { const unsigned char* desc = buf + 4 + offset; unsigned code_set = desc[0] & 0x0f; unsigned assoc = (desc[1] >> 4) & 0x03; unsigned dtype = desc[1] & 0x0f; size_t id_len = desc[3]; /* bounds check: the full descriptor must lie within page_len */ if (offset + 4 + id_len > page_len) break; /* malformed page — stop iterating */ /* skip anything that is not an LU-associated NAA descriptor */ if (assoc != 0x00 || dtype != 0x03) goto next; /* * NAA is always binary (CODE SET == 0x01). * Reject if the device mis-advertises it as ASCII (defensive). */ if (code_set != 0x01) goto next; /* if empty */ if (id_len == 0) goto next; /* * Validate NAA subtype vs. expected byte length. * The NAA value is the top nibble of the first byte of the * designator (NOT the descriptor header byte). */ unsigned naa = (desc[4] >> 4) & 0x0f; size_t expected = 0; switch (naa) { case 0x1 : /* IEEE Extended */ case 0x2 : /* Locally Assigned */ case 0x3 : /* IEEE Registered */ case 0x5 : /* IEEE Registered */ expected = 8; break; case 0x6 : /* IEEE Registered Extended */ expected = 16; break; default : goto next; } /* DESIGNATOR LENGTH must match the NAA subtype exactly */ if (id_len != expected) goto next; if (dst_size < id_len * 2 + 1) return -1; for (size_t i = 0; i < id_len; i++) snprintf(dst + i * 2, 3, "%02x", desc[4 + i]); dst[id_len * 2] = 0; return 0; next: offset += 4 + id_len; } return -1; } #endif /** * Read a file from sys. * Trim spaces. * Always put an ending 0. * Do not report error on reading. * Return an error if truncated * * Return -1 on error, 0 on success */ #if HAVE_LINUX_DEVICE static int sysattr(const char* path, char* buf, size_t buf_size) { int len; len = sysread(path, buf, buf_size); if (len < 0) { /* LCOV_EXCL_START */ return -1; /* LCOV_EXCL_STOP */ } if ((size_t)len + 1 > buf_size) { /* LCOV_EXCL_START */ return -1; /* LCOV_EXCL_STOP */ } buf[len] = 0; strtrim(buf); return 0; } #endif /** * Get the device file from the device number. * * It uses /proc/self/mountinfo. * * Null devices (major==0) are resolved to the device indicated in mountinfo. */ #if HAVE_LINUX_DEVICE static int devresolve_proc(uint64_t device, char* path, size_t path_size) { FILE* f; char match[32]; /* generate the matching string */ snprintf(match, sizeof(match), "%u:%u", major(device), minor(device)); f = fopen("/proc/self/mountinfo", "r"); if (!f) { log_tag("resolve:proc:%u:%u: failed to open /proc/self/mountinfo\n", major(device), minor(device)); return -1; } /* * mountinfo format * 0 - mount ID * 1 - parent ID * 2 - major:minor * 3 - root * 4 - mount point * 5 - options * 6 - "-" (separator) * 7 - fs * 8 - mount source - /dev/device */ while (1) { char buf[256]; char* first_map[8]; unsigned first_mac; char* second_map[8]; unsigned second_mac; char* s; struct stat st; char* separator; char* majorminor; char* mountpoint; char* fs; char* mountsource; s = fgets(buf, sizeof(buf), f); if (s == 0) break; /* find the separator position */ separator = strstr(s, " - "); if (!separator) continue; /* skip the separator */ *separator = 0; separator += 3; /* split the line */ first_mac = strsplit(first_map, 8, s, " \t\r\n"); second_mac = strsplit(second_map, 8, separator, " \t\r\n"); /* if too short, it's the wrong line */ if (first_mac < 5) continue; if (second_mac < 2) continue; majorminor = first_map[2]; mountpoint = first_map[4]; fs = second_map[0]; mountsource = second_map[1]; /* compare major:minor from mountinfo */ if (strcmp(majorminor, match) == 0) { /* * Accept only /dev/... mountsource * * This excludes ZFS that uses a bare label for mountsource, like "tank". * * 410 408 0:193 / /XXX rw,relatime shared:217 - zfs tank/system/data/var/lib/docker/XXX rw,xattr,noacl * * Also excludes AUTOFS unmounted devices that point to a fake filesystem * used to remount them at the first use. * * 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 */ if (strncmp(mountsource, "/dev/", 5) != 0) { log_tag("resolve:proc:%u:%u: match skipped for not /dev/ mountsource for %s %s\n", major(device), minor(device), fs, mountsource); continue; } pathcpy(path, path_size, mountsource); log_tag("resolve:proc:%u:%u: found device %s matching device %s\n", major(device), minor(device), path, match); fclose(f); return 0; } /* get the device of the mount point */ /* in Btrfs it could be different than the one in mountinfo */ if (stat(mountpoint, &st) == 0 && st.st_dev == device) { if (strncmp(mountsource, "/dev/", 5) != 0) { log_tag("resolve:proc:%u:%u: match skipped for not /dev/ mountsource for %s %s\n", major(device), minor(device), fs, mountsource); continue; } pathcpy(path, path_size, mountsource); log_tag("resolve:proc:%u:%u: found device %s matching mountpoint %s\n", major(device), minor(device), path, mountpoint); fclose(f); return 0; } } log_tag("resolve:proc:%u:%u: not found\n", major(device), minor(device)); fclose(f); return -1; } #endif /** * Get the device of a virtual superblock. * * This is intended to resolve the case of Btrfs filesystems that * create a virtual superblock (major==0) not backed by any low * level device. * * See: * Bug 711881 - too funny btrfs st_dev numbers * https://bugzilla.redhat.com/show_bug.cgi?id=711881 */ #if HAVE_LINUX_DEVICE static int devdereference(uint64_t device, uint64_t* new_device) { char path[PATH_MAX]; struct stat st; /* use the proc interface to get the device containing the filesystem */ if (devresolve_proc(device, path, sizeof(path)) != 0) { /* LCOV_EXCL_START */ return -1; /* LCOV_EXCL_STOP */ } /* check the device */ if (stat(path, &st) != 0) { /* LCOV_EXCL_START */ log_tag("dereference:%u:%u: failed to stat %s\n", major(device), minor(device), path); return -1; /* LCOV_EXCL_STOP */ } if (major(st.st_rdev) == 0) { /* LCOV_EXCL_START */ 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)); return -1; /* LCOV_EXCL_STOP */ } *new_device = st.st_rdev; log_tag("dereference:%u:%u: found %u:%u\n", major(device), minor(device), major(st.st_rdev), minor(st.st_rdev)); return 0; } #endif /** * Read a file extracting the specified tag TAG=VALUE format. * Return !=0 on error. */ #if HAVE_LINUX_DEVICE static int tagread(const char* path, const char* tag, char* value, size_t value_size) { int ret; char buf[512]; size_t tag_len; char* i; char* e; ret = sysread(path, buf, sizeof(buf)); if (ret < 0) { /* LCOV_EXCL_START */ log_fatal(errno, "Failed to read '%s'.\n", path); return -1; /* LCOV_EXCL_STOP */ } if ((size_t)ret + 1 > sizeof(buf)) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Too long read '%s'.\n", path); return -1; /* LCOV_EXCL_STOP */ } /* ending 0 */ buf[ret] = 0; tag_len = strlen(tag); for (i = buf; *i; ++i) { char* p = i; /* start with a space */ if (p != buf) { if (!isspace((unsigned char)*p)) continue; ++p; } if (strncmp(p, tag, tag_len) != 0) continue; p += tag_len; /* end with a = */ if (*p != '=') continue; ++p; /* found */ i = p; break; } if (!*i) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Missing tag '%s' for '%s'.\n", tag, path); return -1; /* LCOV_EXCL_STOP */ } /* terminate at the first space */ e = i; while (*e != 0 && !isspace((unsigned char)*e)) ++e; *e = 0; if (!*i) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Empty tag '%s' for '%s'.\n", tag, path); return -1; /* LCOV_EXCL_STOP */ } pathprint(value, value_size, "%s", i); return 0; } #endif /** * Get the device file from the device number. * * It uses /sys/dev/block/.../uevent. * * For null device (major==0) it fails. */ #if HAVE_LINUX_DEVICE static int devresolve_sys(dev_t device, char* path, size_t path_size) { struct stat st; char buf[PATH_MAX]; /* default device path from device number */ pathprint(path, path_size, "/sys/dev/block/%u:%u/uevent", major(device), minor(device)); if (tagread(path, "DEVNAME", buf, sizeof(buf)) != 0) { /* LCOV_EXCL_START */ log_tag("resolve:sys:%u:%u: failed to read DEVNAME tag '%s'\n", major(device), minor(device), path); return -1; /* LCOV_EXCL_STOP */ } /* set the real device path */ pathprint(path, path_size, "/dev/%s", buf); /* check the device */ if (stat(path, &st) != 0) { /* LCOV_EXCL_START */ log_tag("resolve:sys:%u:%u: failed to stat '%s'\n", major(device), minor(device), path); return -1; /* LCOV_EXCL_STOP */ } if (st.st_rdev != device) { /* LCOV_EXCL_START */ 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); return -1; /* LCOV_EXCL_STOP */ } log_tag("resolve:sys:%u:%u:%s: found\n", major(device), minor(device), path); return 0; } #endif /** * Get the device file from the device number. */ #if HAVE_LINUX_DEVICE static int devresolve(uint64_t device, char* path, size_t path_size) { if (devresolve_sys(device, path, path_size) == 0) return 0; return -1; } #endif /** * Cache used by blkid. */ #if HAVE_BLKID static blkid_cache cache = 0; #endif /** * Get the UUID using the /dev/disk/by-uuid/ links. * It doesn't require root permission, and the uuid are always updated. * It doesn't work with Btrfs file-systems that don't export the main UUID * in /dev/disk/by-uuid/. */ #if HAVE_LINUX_DEVICE static int devuuid_dev(uint64_t device, char* uuid, size_t uuid_size) { int ret; DIR* d; struct dirent* dd; struct stat st; /* scan the UUID directory searching for the device */ d = opendir("/dev/disk/by-uuid"); if (!d) { log_tag("uuid:by-uuid:%u:%u: opendir(/dev/disk/by-uuid) failed, %s\n", major(device), minor(device), strerror(errno)); /* directory missing?, likely we are not in Linux */ return -1; } int dir_fd = dirfd(d); if (dir_fd == -1) { log_tag("uuid:by-uuid:%u:%u: dirfd(/dev/disk/by-uuid) failed, %s\n", major(device), minor(device), strerror(errno)); closedir(d); return -1; } while ((dd = readdir(d)) != 0) { /* skip "." and ".." files, UUIDs never start with '.' */ if (dd->d_name[0] == '.') continue; ret = fstatat(dir_fd, dd->d_name, &st, 0); if (ret != 0) { log_tag("uuid:by-uuid:%u:%u: fstatat(%s) failed, %s\n", major(device), minor(device), dd->d_name, strerror(errno)); /* generic error, ignore and continue the search */ continue; } /* if it matches, we have the uuid */ if (S_ISBLK(st.st_mode) && st.st_rdev == (dev_t)device) { char buf[PATH_MAX]; char path[PATH_MAX]; /* resolve the link */ pathprint(path, sizeof(path), "/dev/disk/by-uuid/%s", dd->d_name); ret = readlink(path, buf, sizeof(buf)); if (ret < 0 || ret >= PATH_MAX) { 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)); /* generic error, ignore and continue the search */ continue; } buf[ret] = 0; /* found */ pathcpy(uuid, uuid_size, dd->d_name); log_tag("uuid:by-uuid:%u:%u:%s: found %s\n", major(device), minor(device), uuid, buf); closedir(d); return 0; } } log_tag("uuid:by-uuid:%u:%u: /dev/disk/by-uuid doesn't contain a matching block device\n", major(device), minor(device)); /* not found */ closedir(d); return -1; } #endif /** * Get the UUID using libblkid. * It uses a cache to work without root permission, resulting in UUID * not necessarily recent. * We could call blkid_probe_all() to refresh the UUID, but it would * require root permission to read the superblocks, and resulting in * all the disks spinning. */ #if HAVE_BLKID static int devuuid_blkid(uint64_t device, char* uuid, size_t uuid_size) { char* devname; char* uuidname; devname = blkid_devno_to_devname(device); if (!devname) { log_tag("uuid:blkid:%u:%u: blkid_devno_to_devname() failed, %s\n", major(device), minor(device), strerror(errno)); /* device mapping failed */ return -1; } uuidname = blkid_get_tag_value(cache, "UUID", devname); if (!uuidname) { log_tag("uuid:blkid:%u:%u: blkid_get_tag_value(UUID,%s) failed, %s\n", major(device), minor(device), devname, strerror(errno)); /* uuid mapping failed */ free(devname); return -1; } pathcpy(uuid, uuid_size, uuidname); log_tag("uuid:blkid:%u:%u:%s: found %s\n", major(device), minor(device), uuid, devname); free(devname); free(uuidname); return 0; } #endif /** * Get the LABEL using libblkid. * It uses a cache to work without root permission, resulting in LABEL * not necessarily recent. */ #if HAVE_BLKID static int devuuid_label(uint64_t device, char* label, size_t label_size) { char* devname; char* labelname; devname = blkid_devno_to_devname(device); if (!devname) { log_tag("label:blkid:%u:%u: blkid_devno_to_devname() failed, %s\n", major(device), minor(device), strerror(errno)); /* device mapping failed */ return -1; } labelname = blkid_get_tag_value(cache, "LABEL", devname); if (!labelname) { log_tag("label:blkid:%u:%u: blkid_get_tag_value(LABEL,%s) failed, %s\n", major(device), minor(device), devname, strerror(errno)); free(devname); /* label mapping failed */ return -1; } pathcpy(label, label_size, labelname); log_tag("label:blkid:%u:%u:%s: found %s\n", major(device), minor(device), label, devname); free(devname); free(labelname); return 0; } #endif #ifdef __APPLE__ static int devuuid_darwin(const char* path, char* uuid, size_t uuid_size) { CFStringRef path_apple = 0; DASessionRef session = 0; CFURLRef path_appler = 0; int result = -1; *uuid = 0; path_apple = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8); if (!path_apple) goto bail; session = DASessionCreate(kCFAllocatorDefault); if (!session) goto bail; path_appler = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_apple, kCFURLPOSIXPathStyle, false); if (!path_appler) goto bail; /* find the mount point */ DADiskRef disk = NULL; while (path_appler) { disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, path_appler); if (disk) break; CFURLRef parent = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, path_appler); if (!parent) break; /* check if we hit the root (parent == child) */ if (CFEqual(parent, path_appler)) { CFRelease(parent); break; } CFRelease(path_appler); path_appler = parent; } /* safely extract the UUID */ if (disk) { CFDictionaryRef description = DADiskCopyDescription(disk); if (description) { /* key might not exist for NTFS/ExFAT on some drivers */ const void* value = (CFUUIDRef)CFDictionaryGetValue(description, kDADiskDescriptionVolumeUUIDKey); CFStringRef uuid_string = NULL; if (value) { if (CFGetTypeID(value) == CFUUIDGetTypeID()) { uuid_string = CFUUIDCreateString(kCFAllocatorDefault, (CFUUIDRef)value); } else if (CFGetTypeID(value) == CFStringGetTypeID()) { /* if it's already a string, retain it so we can release it later consistently */ uuid_string = (CFStringRef)CFRetain(value); } } if (uuid_string) { if (CFStringGetCString(uuid_string, uuid, uuid_size, kCFStringEncodingUTF8)) { result = 0; /* success */ } CFRelease(uuid_string); } CFRelease(description); } CFRelease(disk); } bail: /* clean up */ if (path_appler) CFRelease(path_appler); if (session) CFRelease(session); if (path_apple) CFRelease(path_apple); return result; } #endif int devuuid(uint64_t device, const char* path, char* uuid, size_t uuid_size) { (void)path; (void)device; #ifdef __APPLE__ if (devuuid_darwin(path, uuid, uuid_size) == 0) return 0; #endif #if HAVE_LINUX_DEVICE /* if the major is the null device */ if (major(device) == 0) { /* obtain the real device */ if (devdereference(device, &device) != 0) { /* LCOV_EXCL_START */ return -1; /* LCOV_EXCL_STOP */ } } #endif /* first try with the /dev/disk/by-uuid version */ #if HAVE_LINUX_DEVICE if (devuuid_dev(device, uuid, uuid_size) == 0) return 0; #else log_tag("uuid:by-uuid:%u:%u: by-uuid not supported\n", major(device), minor(device)); #endif /* fall back to blkid for other cases */ #if HAVE_BLKID if (devuuid_blkid(device, uuid, uuid_size) == 0) return 0; #else log_tag("uuid:blkid:%u:%u: blkid support not compiled in\n", major(device), minor(device)); #endif log_tag("uuid:notfound:%u:%u:\n", major(device), minor(device)); /* not supported */ (void)uuid; (void)uuid_size; return -1; } int filephy(const char* path, uint64_t size, uint64_t* physical) { #if HAVE_LINUX_FIEMAP_H /* In Linux get the real physical address of the file */ /* Note that FIEMAP doesn't require root permission */ int f; struct fiemap* fiemap; size_t fiemap_size; unsigned int blknum; f = open(path, O_RDONLY); if (f == -1) { return -1; } /* first try with FIEMAP */ /* if works for ext2, ext3, ext4, xfs, btrfs */ fiemap_size = sizeof(struct fiemap) + sizeof(struct fiemap_extent); fiemap = malloc_nofail(fiemap_size); memset(fiemap, 0, fiemap_size); fiemap->fm_start = 0; fiemap->fm_length = ~0ULL; fiemap->fm_flags = FIEMAP_FLAG_SYNC; /* required to ensure that just created files report a valid address and not 0 */ fiemap->fm_extent_count = 1; /* we are interested only at the first block */ if (ioctl(f, FS_IOC_FIEMAP, fiemap) != -1) { uint32_t flags = fiemap->fm_extents[0].fe_flags; uint64_t offset = fiemap->fm_extents[0].fe_physical; /* check some condition for validating the offset */ if (flags & FIEMAP_EXTENT_DATA_INLINE) { /* if the data is inline, we don't have an offset to report */ *physical = FILEPHY_WITHOUT_OFFSET; } else if (flags & FIEMAP_EXTENT_UNKNOWN) { /* if the offset is unknown, we don't have an offset to report */ *physical = FILEPHY_WITHOUT_OFFSET; } else if (offset == 0) { /* 0 is the general fallback for file-systems when */ /* they don't have an offset to report */ *physical = FILEPHY_WITHOUT_OFFSET; } else { /* finally report the real offset */ *physical = offset + FILEPHY_REAL_OFFSET; } free(fiemap); if (close(f) == -1) return -1; return 0; } free(fiemap); /* if the file is empty, FIBMAP doesn't work, and we don't even try to use it */ if (size == 0) { *physical = FILEPHY_WITHOUT_OFFSET; if (close(f) == -1) return -1; return 0; } /* then try with FIBMAP */ /* it works for jfs, reiserfs, ntfs-3g */ /* in exfat it always returns 0, that it's anyway better than the fake inodes */ blknum = 0; /* first block */ if (ioctl(f, FIBMAP, &blknum) != -1) { *physical = blknum + FILEPHY_REAL_OFFSET; if (close(f) == -1) return -1; return 0; } /* otherwise don't use anything, and keep the directory traversal order */ /* at now this should happen only for vfat */ /* and it's surely better than using fake inodes */ *physical = FILEPHY_UNREPORTED_OFFSET; if (close(f) == -1) return -1; #else /* In a generic Unix use a dummy value for all the files */ /* We don't want to risk to use the inode without knowing */ /* if it really improves performance. */ /* In this way we keep them in the directory traversal order */ /* that at least keeps files in the same directory together. */ /* Note also that in newer file-system with snapshot, like ZFS, */ /* the inode doesn't represent even more the disk position, because files */ /* are not overwritten in place, but rewritten in another location */ /* of the disk. */ *physical = FILEPHY_UNREPORTED_OFFSET; (void)path; /* not used here */ (void)size; #endif return 0; } /* from man statfs */ #define ADFS_SUPER_MAGIC 0xadf5 #define AFFS_SUPER_MAGIC 0xADFF #define BDEVFS_MAGIC 0x62646576 #define BEFS_SUPER_MAGIC 0x42465331 #define BFS_MAGIC 0x1BADFACE #define BINFMTFS_MAGIC 0x42494e4d #define BTRFS_SUPER_MAGIC 0x9123683E #define CGROUP_SUPER_MAGIC 0x27e0eb #define CIFS_MAGIC_NUMBER 0xFF534D42 #define CODA_SUPER_MAGIC 0x73757245 #define COH_SUPER_MAGIC 0x012FF7B7 #define CRAMFS_MAGIC 0x28cd3d45 #define DEBUGFS_MAGIC 0x64626720 #define DEVFS_SUPER_MAGIC 0x1373 #define DEVPTS_SUPER_MAGIC 0x1cd1 #define EFIVARFS_MAGIC 0xde5e81e4 #define EFS_SUPER_MAGIC 0x00414A53 #define EXT_SUPER_MAGIC 0x137D #define EXT2_OLD_SUPER_MAGIC 0xEF51 #define EXT4_SUPER_MAGIC 0xEF53 /* also ext2/ext3 */ #define FUSE_SUPER_MAGIC 0x65735546 #define FUTEXFS_SUPER_MAGIC 0xBAD1DEA #define HFS_SUPER_MAGIC 0x4244 #define HFSPLUS_SUPER_MAGIC 0x482b #define HOSTFS_SUPER_MAGIC 0x00c0ffee #define HPFS_SUPER_MAGIC 0xF995E849 #define HUGETLBFS_MAGIC 0x958458f6 #define ISOFS_SUPER_MAGIC 0x9660 #define JFFS2_SUPER_MAGIC 0x72b6 #define JFS_SUPER_MAGIC 0x3153464a #define MINIX_SUPER_MAGIC 0x137F #define MINIX_SUPER_MAGIC2 0x138F #define MINIX2_SUPER_MAGIC 0x2468 #define MINIX2_SUPER_MAGIC2 0x2478 #define MINIX3_SUPER_MAGIC 0x4d5a #define MQUEUE_MAGIC 0x19800202 #define MSDOS_SUPER_MAGIC 0x4d44 #define NCP_SUPER_MAGIC 0x564c #define NFS_SUPER_MAGIC 0x6969 #define NILFS_SUPER_MAGIC 0x3434 #define NTFS_SB_MAGIC 0x5346544e #define OCFS2_SUPER_MAGIC 0x7461636f #define OPENPROM_SUPER_MAGIC 0x9fa1 #define PIPEFS_MAGIC 0x50495045 #define PROC_SUPER_MAGIC 0x9fa0 #define PSTOREFS_MAGIC 0x6165676C #define QNX4_SUPER_MAGIC 0x002f #define QNX6_SUPER_MAGIC 0x68191122 #define RAMFS_MAGIC 0x858458f6 #define REISERFS_SUPER_MAGIC 0x52654973 #define ROMFS_MAGIC 0x7275 #define SELINUX_MAGIC 0xf97cff8c #define SMACK_MAGIC 0x43415d53 #define SMB_SUPER_MAGIC 0x517B #define SOCKFS_MAGIC 0x534F434B #define SQUASHFS_MAGIC 0x73717368 #define SYSFS_MAGIC 0x62656572 #define SYSV2_SUPER_MAGIC 0x012FF7B6 #define SYSV4_SUPER_MAGIC 0x012FF7B5 #define TMPFS_MAGIC 0x01021994 #define UDF_SUPER_MAGIC 0x15013346 #define UFS_MAGIC 0x00011954 #define USBDEVICE_SUPER_MAGIC 0x9fa2 #define V9FS_MAGIC 0x01021997 #define VXFS_SUPER_MAGIC 0xa501FCF5 #define XENFS_SUPER_MAGIC 0xabba1974 #define XENIX_SUPER_MAGIC 0x012FF7B4 #define XFS_SUPER_MAGIC 0x58465342 #define _XIAFS_SUPER_MAGIC 0x012FD16D #define AFS_SUPER_MAGIC 0x5346414F #define AUFS_SUPER_MAGIC 0x61756673 #define ANON_INODE_FS_SUPER_MAGIC 0x09041934 #define CEPH_SUPER_MAGIC 0x00C36400 #define ECRYPTFS_SUPER_MAGIC 0xF15F #define FAT_SUPER_MAGIC 0x4006 #define FHGFS_SUPER_MAGIC 0x19830326 #define FUSEBLK_SUPER_MAGIC 0x65735546 #define FUSECTL_SUPER_MAGIC 0x65735543 #define GFS_SUPER_MAGIC 0x1161970 #define GPFS_SUPER_MAGIC 0x47504653 #define MTD_INODE_FS_SUPER_MAGIC 0x11307854 #define INOTIFYFS_SUPER_MAGIC 0x2BAD1DEA #define ISOFS_R_WIN_SUPER_MAGIC 0x4004 #define ISOFS_WIN_SUPER_MAGIC 0x4000 #define JFFS_SUPER_MAGIC 0x07C0 #define KAFS_SUPER_MAGIC 0x6B414653 #define LUSTRE_SUPER_MAGIC 0x0BD00BD0 #define NFSD_SUPER_MAGIC 0x6E667364 #define PANFS_SUPER_MAGIC 0xAAD7AAEA #define RPC_PIPEFS_SUPER_MAGIC 0x67596969 #define SECURITYFS_SUPER_MAGIC 0x73636673 #define UFS_BYTESWAPPED_SUPER_MAGIC 0x54190100 #define VMHGFS_SUPER_MAGIC 0xBACBACBC #define VZFS_SUPER_MAGIC 0x565A4653 #define ZFS_SUPER_MAGIC 0x2FC12FC1 struct filesystem_entry { unsigned id; const char* name; int remote; } FILESYSTEMS[] = { { ADFS_SUPER_MAGIC, "adfs", 0 }, { AFFS_SUPER_MAGIC, "affs", 0 }, { AFS_SUPER_MAGIC, "afs", 1 }, { AUFS_SUPER_MAGIC, "aufs", 1 }, { BEFS_SUPER_MAGIC, "befs", 0 }, { BDEVFS_MAGIC, "bdevfs", 0 }, { BFS_MAGIC, "bfs", 0 }, { BINFMTFS_MAGIC, "binfmt_misc", 0 }, { BTRFS_SUPER_MAGIC, "btrfs", 0 }, { CEPH_SUPER_MAGIC, "ceph", 1 }, { CGROUP_SUPER_MAGIC, "cgroupfs", 0 }, { CIFS_MAGIC_NUMBER, "cifs", 1 }, { CODA_SUPER_MAGIC, "coda", 1 }, { COH_SUPER_MAGIC, "coh", 0 }, { CRAMFS_MAGIC, "cramfs", 0 }, { DEBUGFS_MAGIC, "debugfs", 0 }, { DEVFS_SUPER_MAGIC, "devfs", 0 }, { DEVPTS_SUPER_MAGIC, "devpts", 0 }, { ECRYPTFS_SUPER_MAGIC, "ecryptfs", 0 }, { EFS_SUPER_MAGIC, "efs", 0 }, { EXT_SUPER_MAGIC, "ext", 0 }, { EXT4_SUPER_MAGIC, "ext4", 0 }, { EXT2_OLD_SUPER_MAGIC, "ext2", 0 }, { FAT_SUPER_MAGIC, "fat", 0 }, { FHGFS_SUPER_MAGIC, "fhgfs", 1 }, { FUSEBLK_SUPER_MAGIC, "fuseblk", 1 }, { FUSECTL_SUPER_MAGIC, "fusectl", 1 }, { FUTEXFS_SUPER_MAGIC, "futexfs", 0 }, { GFS_SUPER_MAGIC, "gfs/gfs2", 1 }, { GPFS_SUPER_MAGIC, "gpfs", 1 }, { HFS_SUPER_MAGIC, "hfs", 0 }, { HFSPLUS_SUPER_MAGIC, "hfsplus", 0 }, { HPFS_SUPER_MAGIC, "hpfs", 0 }, { HUGETLBFS_MAGIC, "hugetlbfs", 0 }, { MTD_INODE_FS_SUPER_MAGIC, "inodefs", 0 }, { INOTIFYFS_SUPER_MAGIC, "inotifyfs", 0 }, { ISOFS_SUPER_MAGIC, "isofs", 0 }, { ISOFS_R_WIN_SUPER_MAGIC, "isofs", 0 }, { ISOFS_WIN_SUPER_MAGIC, "isofs", 0 }, { JFFS_SUPER_MAGIC, "jffs", 0 }, { JFFS2_SUPER_MAGIC, "jffs2", 0 }, { JFS_SUPER_MAGIC, "jfs", 0 }, { KAFS_SUPER_MAGIC, "k-afs", 1 }, { LUSTRE_SUPER_MAGIC, "lustre", 1 }, { MINIX_SUPER_MAGIC, "minix", 0 }, { MINIX_SUPER_MAGIC2, "minix", 0 }, { MINIX2_SUPER_MAGIC, "minix2", 0 }, { MINIX2_SUPER_MAGIC2, "minix2", 0 }, { MINIX3_SUPER_MAGIC, "minix3", 0 }, { MQUEUE_MAGIC, "mqueue", 0 }, { MSDOS_SUPER_MAGIC, "msdos", 0 }, { NCP_SUPER_MAGIC, "novell", 1 }, { NFS_SUPER_MAGIC, "nfs", 1 }, { NFSD_SUPER_MAGIC, "nfsd", 1 }, { NILFS_SUPER_MAGIC, "nilfs", 0 }, { NTFS_SB_MAGIC, "ntfs", 0 }, { OPENPROM_SUPER_MAGIC, "openprom", 0 }, { OCFS2_SUPER_MAGIC, "ocfs2", 1 }, { PANFS_SUPER_MAGIC, "panfs", 1 }, { PIPEFS_MAGIC, "pipefs", 1 }, { PROC_SUPER_MAGIC, "proc", 0 }, { PSTOREFS_MAGIC, "pstorefs", 0 }, { QNX4_SUPER_MAGIC, "qnx4", 0 }, { QNX6_SUPER_MAGIC, "qnx6", 0 }, { RAMFS_MAGIC, "ramfs", 0 }, { REISERFS_SUPER_MAGIC, "reiserfs", 0 }, { ROMFS_MAGIC, "romfs", 0 }, { RPC_PIPEFS_SUPER_MAGIC, "rpc_pipefs", 0 }, { SECURITYFS_SUPER_MAGIC, "securityfs", 0 }, { SELINUX_MAGIC, "selinux", 0 }, { SMB_SUPER_MAGIC, "smb", 1 }, { SOCKFS_MAGIC, "sockfs", 0 }, { SQUASHFS_MAGIC, "squashfs", 0 }, { SYSFS_MAGIC, "sysfs", 0 }, { SYSV2_SUPER_MAGIC, "sysv2", 0 }, { SYSV4_SUPER_MAGIC, "sysv4", 0 }, { TMPFS_MAGIC, "tmpfs", 0 }, { UDF_SUPER_MAGIC, "udf", 0 }, { UFS_MAGIC, "ufs", 0 }, { UFS_BYTESWAPPED_SUPER_MAGIC, "ufs", 0 }, { USBDEVICE_SUPER_MAGIC, "usbdevfs", 0 }, { V9FS_MAGIC, "v9fs", 0 }, { VMHGFS_SUPER_MAGIC, "vmhgfs", 1 }, { VXFS_SUPER_MAGIC, "vxfs", 0 }, { VZFS_SUPER_MAGIC, "vzfs", 0 }, { XENFS_SUPER_MAGIC, "xenfs", 0 }, { XENIX_SUPER_MAGIC, "xenix", 0 }, { XFS_SUPER_MAGIC, "xfs", 0 }, { _XIAFS_SUPER_MAGIC, "xia", 0 }, { ZFS_SUPER_MAGIC, "zfs", 0 }, { 0 } }; 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) { #if HAVE_STATFS struct statfs st; if (statfs(path, &st) != 0) { char dir[PATH_MAX]; char* slash; if (errno != ENOENT) { return -1; } /* if it doesn't exist, we assume a file */ /* and we check for the containing dir */ if (strlen(path) + 1 > sizeof(dir)) { errno = ENAMETOOLONG; return -1; } strcpy(dir, path); slash = strrchr(dir, '/'); if (!slash) return -1; *slash = 0; if (statfs(dir, &st) != 0) return -1; } #endif /* to get the fs type check "man stat" or "stat -f -t FILE" */ if (has_persistent_inode) { #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE switch (st.f_type) { case FUSEBLK_SUPER_MAGIC : /* FUSE, "fuseblk" in the stat command */ case MSDOS_SUPER_MAGIC : /* VFAT, "msdos" in the stat command */ *has_persistent_inode = 0; break; default : /* by default assume yes */ *has_persistent_inode = 1; break; } #else /* in Unix inodes are persistent by default */ *has_persistent_inode = 1; #endif } if (has_syncronized_hardlinks) { #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE switch (st.f_type) { case NTFS_SB_MAGIC : /* NTFS */ case MSDOS_SUPER_MAGIC : /* VFAT, "msdos" in the stat command */ *has_syncronized_hardlinks = 0; break; default : /* by default assume yes */ *has_syncronized_hardlinks = 1; break; } #else /* in Unix hardlinks share the same metadata by default */ *has_syncronized_hardlinks = 1; #endif } if (total_space) { #if HAVE_STATFS *total_space = st.f_bsize * (uint64_t)st.f_blocks; #else *total_space = 0; #endif } if (free_space) { #if HAVE_STATFS *free_space = st.f_bsize * (uint64_t)st.f_bfree; #else *free_space = 0; #endif } const char* ptype = 0; #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_FSTYPENAME /* get the filesystem type directly from the struct (Mac OS X) */ ptype = st.f_fstypename; #elif HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE /* get the filesystem type from f_type (Linux) */ /* from: https://github.com/influxdata/gopsutil/blob/master/disk/disk_linux.go */ for (int i = 0; FILESYSTEMS[i].id != 0; ++i) { if (st.f_type == FILESYSTEMS[i].id) { ptype = FILESYSTEMS[i].name; break; } } #endif if (fstype) { fstype[0] = 0; if (ptype) snprintf(fstype, fstype_size, "%s", ptype); } if (fslabel) { fslabel[0] = 0; #if HAVE_BLKID struct stat fst; if (stat(path, &fst) == 0) devuuid_label(fst.st_dev, fslabel, fslabel_size); #else (void)fslabel_size; #endif } return 0; } uint64_t os_tick(void) { #if HAVE_MACH_ABSOLUTE_TIME /* for Mac OS X */ return mach_absolute_time(); #elif HAVE_CLOCK_GETTIME && (defined(CLOCK_MONOTONIC) || defined(CLOCK_MONOTONIC_RAW)) /* for Linux */ struct timespec tv; /* nanosecond precision with clock_gettime() */ #if defined(CLOCK_MONOTONIC_RAW) if (clock_gettime(CLOCK_MONOTONIC_RAW, &tv) != 0) { #else if (clock_gettime(CLOCK_MONOTONIC, &tv) != 0) { #endif return 0; } return tv.tv_sec * 1000000000ULL + tv.tv_nsec; #else /* other platforms */ struct timeval tv; /* microsecond precision with gettimeofday() */ if (gettimeofday(&tv, 0) != 0) { return 0; } return tv.tv_sec * 1000000ULL + tv.tv_usec; #endif } uint64_t os_tick_ms(void) { struct timeval tv; if (gettimeofday(&tv, 0) != 0) return 0; return tv.tv_sec * 1000ULL + tv.tv_usec / 1000; } int randomize(void* ptr, size_t size) { int f; ssize_t ret; f = open("/dev/urandom", O_RDONLY); if (f == -1) return -1; ret = read(f, ptr, size); if (ret < 0 || (size_t)ret != size) { close(f); return -1; } if (close(f) != 0) return -1; return 0; } /** * Read a file extracting the contained device number in %u:%u format. * Return 0 on error. */ #if HAVE_LINUX_DEVICE static dev_t devread(const char* path) { int f; int ret; int len; char buf[64]; char* e; unsigned ma; unsigned mi; f = open(path, O_RDONLY); if (f == -1) { /* LCOV_EXCL_START */ log_fatal(errno, "Failed to open '%s'.\n", path); return 0; /* LCOV_EXCL_STOP */ } len = read(f, buf, sizeof(buf)); if (len < 0) { /* LCOV_EXCL_START */ close(f); log_fatal(errno, "Failed to read '%s'.\n", path); return 0; /* LCOV_EXCL_STOP */ } if (len == sizeof(buf)) { /* LCOV_EXCL_START */ close(f); log_fatal(EEXTERNAL, "Too long read '%s'.\n", path); return 0; /* LCOV_EXCL_STOP */ } ret = close(f); if (ret != 0) { /* LCOV_EXCL_START */ log_fatal(errno, "Failed to close '%s'.\n", path); return 0; /* LCOV_EXCL_STOP */ } buf[len] = 0; ma = strtoul(buf, &e, 10); if (*e != ':') { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Invalid format in '%s' for '%s'.\n", path, buf); return 0; /* LCOV_EXCL_STOP */ } mi = strtoul(e + 1, &e, 10); if (*e != 0 && !isspace((unsigned char)*e)) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Invalid format in '%s' for '%s'.\n", path, buf); return 0; /* LCOV_EXCL_STOP */ } return makedev(ma, mi); } #endif /** * Read a device tree filling the specified list of disk_t entries. */ #if HAVE_LINUX_DEVICE static int devtree(devinfo_t* parent, dev_t device, tommy_list* list) { char path[PATH_MAX]; DIR* d; int slaves = 0; pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/slaves", major(device), minor(device)); /* check if there is a slaves list */ d = opendir(path); if (d != 0) { struct dirent* dd; while ((dd = readdir(d)) != 0) { if (dd->d_name[0] != '.') { dev_t subdev; /* for each slave, expand the full potential tree */ pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/slaves/%s/dev", major(device), minor(device), dd->d_name); subdev = devread(path); if (!subdev) { /* LCOV_EXCL_START */ closedir(d); return -1; /* LCOV_EXCL_STOP */ } if (devtree(parent, subdev, list) != 0) { /* LCOV_EXCL_START */ closedir(d); return -1; /* LCOV_EXCL_STOP */ } ++slaves; } } closedir(d); } /* if no slaves found */ if (!slaves) { /* this is a raw device */ devinfo_t* devinfo; /* check if it's a real device */ pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device", major(device), minor(device)); if (eaccess(path, F_OK) != 0) { /* get the parent device */ pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/../dev", major(device), minor(device)); device = devread(path); if (!device) { /* LCOV_EXCL_START */ return -1; /* LCOV_EXCL_STOP */ } } /* get the device file */ if (devresolve(device, path, sizeof(path)) != 0) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } devinfo = calloc_nofail(1, sizeof(devinfo_t)); devinfo->device = device; pathcpy(devinfo->name, sizeof(devinfo->name), parent->name); pathcpy(devinfo->smartctl, sizeof(devinfo->smartctl), parent->smartctl); memcpy(devinfo->smartignore, parent->smartignore, sizeof(devinfo->smartignore)); pathcpy(devinfo->file, sizeof(devinfo->file), path); devinfo->parent = parent; /* insert in the list */ tommy_list_insert_tail(list, &devinfo->node, devinfo); } return 0; } #endif /** * Compute disk usage by aggregating access statistics. */ #if HAVE_LINUX_DEVICE static int devstat(dev_t device, uint64_t* count) { char path[PATH_MAX]; char buf[512]; int token; int ret; char* i; *count = 0; pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/stat", major(device), minor(device)); ret = sysread(path, buf, sizeof(buf)); if (ret < 0) { /* LCOV_EXCL_START */ log_fatal(errno, "Failed to read '%s'.\n", path); return -1; /* LCOV_EXCL_STOP */ } if ((size_t)ret + 1 > sizeof(buf)) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Too long read '%s'.\n", path); return -1; /* LCOV_EXCL_STOP */ } /* ending 0 */ buf[ret] = 0; i = buf; token = 1; /* token number */ while (*i) { char* n; char* e; unsigned long long v; /* skip spaces */ while (*i && isspace((unsigned char)*i)) ++i; /* read digits */ n = i; while (*i && isdigit((unsigned char)*i)) ++i; if (i == n) /* if no digit, abort */ break; if (*i == 0 || !isspace((unsigned char)*i)) /* if no space, abort */ break; *i++ = 0; /* put a terminator */ v = strtoull(n, &e, 10); if (*e != 0) break; /* sum reads and writes completed */ if (token == 1 || token == 5) { *count += v; if (token == 5) break; /* stop here */ } ++token; } return 0; } #endif /** * Get SMART attributes. */ #if HAVE_LINUX_DEVICE 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) { char cmd[PATH_MAX + 64]; char file[PATH_MAX]; FILE* f; int ret; const char* x; x = find_smartctl(); if (!x) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Cannot find smartctl.\n"); return -1; /* LCOV_EXCL_STOP */ } if (devresolve(device, file, sizeof(file)) != 0) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } /* if there is a custom smartctl command */ if (smartctl[0]) { char option[PATH_MAX]; snprintf(option, sizeof(option), smartctl, file); snprintf(cmd, sizeof(cmd), "%s -a %s", x, option); } else { snprintf(cmd, sizeof(cmd), "%s -a %s", x, file); } log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd); f = popen(cmd, "r"); if (!f) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:shell\n", file, name); log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd); return -1; /* LCOV_EXCL_STOP */ } if (smartctl_attribute(f, file, name, smart, info, serial, family, model, interface) != 0) { /* LCOV_EXCL_START */ pclose(f); log_tag("device:%s:%s:shell\n", file, name); return -1; /* LCOV_EXCL_STOP */ } ret = pclose(f); log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret); if (!WIFEXITED(ret)) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:abort\n", file, name); log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd); return -1; /* LCOV_EXCL_STOP */ } if (WEXITSTATUS(ret) == 127) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:shell\n", file, name); log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd); return -1; /* LCOV_EXCL_STOP */ } /* store the return smartctl return value */ smart[SMART_FLAGS].raw = WEXITSTATUS(ret); return 0; } #endif /** * Get device attributes. */ #if HAVE_LINUX_DEVICE static void devattr(dev_t device, uint64_t* info, char* serial, char* family, char* model, char* interface) { char path[PATH_MAX]; char buf[512]; int ret; (void)family; /* not available, smartctl uses an internal database to get it */ if (info[INFO_SIZE] == SMART_UNASSIGNED) { pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/size", major(device), minor(device)); if (sysattr(path, buf, sizeof(buf)) == 0) { char* e; uint64_t v; v = strtoul(buf, &e, 10); if (*e == 0) info[INFO_SIZE] = v * 512; } } if (info[INFO_ROTATION_RATE] == SMART_UNASSIGNED) { pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/queue/rotational", major(device), minor(device)); if (sysattr(path, buf, sizeof(buf)) == 0) { char* e; uint64_t v; v = strtoul(buf, &e, 10); if (*e == 0) info[INFO_ROTATION_RATE] = v; } } if (*model == 0) { pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device/model", major(device), minor(device)); if (sysattr(path, buf, sizeof(buf)) == 0) { if (buf[0] != 0) pathcpy(model, SMART_MAX, buf); } } if (*serial == 0) { pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device/serial", major(device), minor(device)); if (sysattr(path, buf, sizeof(buf)) == 0) { if (buf[0] != 0) pathcpy(serial, SMART_MAX, buf); } } if (*serial == 0) { pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/device/vpd_pg80", major(device), minor(device)); if (sysattr_vpd_pg80(path, buf, sizeof(buf)) == 0) { if (buf[0] != 0) pathcpy(serial, SMART_MAX, buf); } } /* always override interface if it's usb */ pathprint(path, sizeof(path), "/sys/dev/block/%u:%u", major(device), minor(device)); ret = readlink(path, buf, sizeof(buf)); if (ret > 0 && (unsigned)ret < sizeof(buf)) { buf[ret] = 0; if (strstr(buf, "usb") != 0) strcpy(interface, "USB"); } } #endif /** * Get POWER state. */ #if HAVE_LINUX_DEVICE 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) { char cmd[PATH_MAX + 64]; char file[PATH_MAX]; FILE* f; int ret; const char* x; x = find_smartctl(); if (!x) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Cannot find smartctl.\n"); return -1; /* LCOV_EXCL_STOP */ } if (devresolve(device, file, sizeof(file)) != 0) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } /* if there is a custom smartctl command */ if (smartctl[0]) { char option[PATH_MAX]; snprintf(option, sizeof(option), smartctl, file); snprintf(cmd, sizeof(cmd), "%s -n standby,3 -a %s", x, option); } else { snprintf(cmd, sizeof(cmd), "%s -n standby,3 -a %s", x, file); } log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd); f = popen(cmd, "r"); if (!f) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:shell\n", file, name); log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd); return -1; /* LCOV_EXCL_STOP */ } if (smartctl_attribute(f, file, name, smart, info, serial, family, model, interface) != 0) { /* LCOV_EXCL_START */ pclose(f); log_tag("device:%s:%s:shell\n", file, name); return -1; /* LCOV_EXCL_STOP */ } ret = pclose(f); log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret); if (!WIFEXITED(ret)) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:abort\n", file, name); log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd); return -1; /* LCOV_EXCL_STOP */ } if (WEXITSTATUS(ret) == 127) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:shell\n", file, name); log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd); return -1; /* LCOV_EXCL_STOP */ } if (WEXITSTATUS(ret) == 3) { log_tag("attr:%s:%s:power:standby\n", file, name); *power = POWER_STANDBY; } else { log_tag("attr:%s:%s:power:active\n", file, name); *power = POWER_ACTIVE; /* store the smartctl return value */ if (smart) smart[SMART_FLAGS].raw = WEXITSTATUS(ret); } return 0; } #endif /** * Spin down a specific device. */ #if HAVE_LINUX_DEVICE static int devdown(dev_t device, const char* name, const char* smartctl) { char cmd[PATH_MAX + 64]; char file[PATH_MAX]; FILE* f; int ret; const char* x; x = find_smartctl(); if (!x) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Cannot find smartctl.\n"); return -1; /* LCOV_EXCL_STOP */ } if (devresolve(device, file, sizeof(file)) != 0) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } /* if there is a custom smartctl command */ if (smartctl[0]) { char option[PATH_MAX]; snprintf(option, sizeof(option), smartctl, file); snprintf(cmd, sizeof(cmd), "%s -s standby,now %s", x, option); } else { snprintf(cmd, sizeof(cmd), "%s -s standby,now %s", x, file); } log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd); f = popen(cmd, "r"); if (!f) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:shell\n", file, name); log_fatal(EEXTERNAL, "Failed to run '%s' (from popen).\n", cmd); return -1; /* LCOV_EXCL_STOP */ } if (smartctl_flush(f, file, name) != 0) { /* LCOV_EXCL_START */ pclose(f); log_tag("device:%s:%s:shell\n", file, name); return -1; /* LCOV_EXCL_STOP */ } ret = pclose(f); log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret); if (!WIFEXITED(ret)) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:abort\n", file, name); log_fatal(EEXTERNAL, "Failed to run '%s' (not exited).\n", cmd); return -1; /* LCOV_EXCL_STOP */ } if (WEXITSTATUS(ret) == 127) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:shell\n", file, name); log_fatal(EEXTERNAL, "Failed to run '%s' (from sh).\n", cmd); return -1; /* LCOV_EXCL_STOP */ } if (WEXITSTATUS(ret) != 0) { /* LCOV_EXCL_START */ log_tag("device:%s:%s:exit:%d\n", file, name, WEXITSTATUS(ret)); log_fatal(EEXTERNAL, "Failed to run '%s' with return code %xh.\n", cmd, WEXITSTATUS(ret)); return -1; /* LCOV_EXCL_STOP */ } log_tag("attr:%s:%s:power:down\n", file, name); return 0; } #endif /** * Spin down a specific device if it's up. */ #if HAVE_LINUX_DEVICE static int devdownifup(dev_t device, const char* name, const char* smartctl, int* power) { *power = POWER_UNKNOWN; if (devprobe(device, name, smartctl, power, 0, 0, 0, 0, 0, 0) != 0) return -1; if (*power == POWER_ACTIVE) return devdown(device, name, smartctl); return 0; } #endif /** * Spin up a specific device. */ #if HAVE_LINUX_DEVICE static int devup(dev_t device, const char* name) { char file[PATH_MAX]; int ret; int f; void* buf; uint64_t size; uint64_t offset; if (devresolve(device, file, sizeof(file)) != 0) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } /* O_DIRECT requires memory aligned to the block size */ if (posix_memalign(&buf, 4096, 4096) != 0) { /* LCOV_EXCL_START */ log_fatal(errno, "Failed to allocate aligned memory for device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } f = open(file, O_RDONLY | O_DIRECT); if (f < 0) { /* LCOV_EXCL_START */ free(buf); log_tag("device:%s:%s:error:%d\n", file, name, errno); log_fatal(errno, "Failed to open device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } if (ioctl(f, BLKGETSIZE64, &size) < 0) { /* LCOV_EXCL_START */ close(f); free(buf); log_tag("device:%s:%s:error:%d\n", file, name, errno); log_fatal(errno, "Failed to get device size '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } /* select a random offset */ uint64_t blocks = size / 4096; offset = blocks > 0 ? (random_u64() % blocks) * 4096 : 0; #if HAVE_POSIX_FADVISE /* clear cache */ ret = posix_fadvise_wrapper(f, offset, 4096, POSIX_FADV_DONTNEED); if (ret == ENOSYS) { /* call is not supported */ ret = 0; } if (ret != 0) { /* LCOV_EXCL_START */ close(f); free(buf); log_tag("device:%s:%s:error:%d\n", file, name, errno); log_fatal(errno, "Failed to advise device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } #endif ret = pread(f, buf, 4096, offset); if (ret < 0) { /* LCOV_EXCL_START */ close(f); free(buf); log_tag("device:%s:%s:error:%d\n", file, name, errno); log_fatal(errno, "Failed to read device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } ret = close(f); if (ret < 0) { /* LCOV_EXCL_START */ free(buf); log_tag("device:%s:%s:error:%d\n", file, name, errno); log_fatal(errno, "Failed to close device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } log_tag("attr:%s:%s:power:up\n", file, name); free(buf); return 0; } #endif /** * Thread for spinning up. * * Note that filling up the devinfo object is done inside this thread, * to avoid to block the main thread if the device need to be spin up * to handle stat/resolve requests. */ static void* thread_spinup(void* arg) { #if HAVE_LINUX_DEVICE devinfo_t* devinfo = arg; uint64_t start; start = os_tick_ms(); if (devup(devinfo->device, devinfo->name) != 0) { /* LCOV_EXCL_START */ return (void*)-1; /* LCOV_EXCL_STOP */ } msg_status("Spunup device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start); /* after the spin up, get SMART info */ if (devsmart(devinfo->device, devinfo->name, devinfo->smartctl, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) { /* LCOV_EXCL_START */ return (void*)-1; /* LCOV_EXCL_STOP */ } /* * Retrieve some attributes directly from the system. * * smartctl intentionally skips queries on devices in standby mode * to prevent accidentally spinning them up. */ devattr(devinfo->device, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf); return 0; #else (void)arg; return (void*)-1; #endif } /** * Thread for spinning down. */ static void* thread_spindown(void* arg) { #if HAVE_LINUX_DEVICE devinfo_t* devinfo = arg; uint64_t start; start = os_tick_ms(); if (devdown(devinfo->device, devinfo->name, devinfo->smartctl) != 0) { /* LCOV_EXCL_START */ return (void*)-1; /* LCOV_EXCL_STOP */ } msg_status("Spundown device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start); return 0; #else (void)arg; return (void*)-1; #endif } /** * Thread for spinning down. */ static void* thread_spindownifup(void* arg) { #if HAVE_LINUX_DEVICE devinfo_t* devinfo = arg; uint64_t start; int power; start = os_tick_ms(); if (devdownifup(devinfo->device, devinfo->name, devinfo->smartctl, &power) != 0) { /* LCOV_EXCL_START */ return (void*)-1; /* LCOV_EXCL_STOP */ } if (power == POWER_ACTIVE) msg_status("Spundown device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start); return 0; #else (void)arg; return (void*)-1; #endif } /** * Thread for getting smart info. */ static void* thread_smart(void* arg) { #if HAVE_LINUX_DEVICE devinfo_t* devinfo = arg; if (devsmart(devinfo->device, devinfo->name, devinfo->smartctl, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) { /* LCOV_EXCL_START */ return (void*)-1; /* LCOV_EXCL_STOP */ } /* * Retrieve some attributes directly from the system. * * smartctl intentionally skips queries on devices in standby mode * to prevent accidentally spinning them up. */ devattr(devinfo->device, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf); return 0; #else (void)arg; return (void*)-1; #endif } /** * Thread for getting power info. */ static void* thread_probe(void* arg) { #if HAVE_LINUX_DEVICE devinfo_t* devinfo = arg; if (devprobe(devinfo->device, devinfo->name, devinfo->smartctl, &devinfo->power, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) { /* LCOV_EXCL_START */ return (void*)-1; /* LCOV_EXCL_STOP */ } /* * Retrieve some attributes directly from the system. * * smartctl intentionally skips queries on devices in standby mode * to prevent accidentally spinning them up. */ devattr(devinfo->device, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf); return 0; #else (void)arg; return (void*)-1; #endif } static int device_thread(tommy_list* list, void* (*func)(void* arg)) { int fail = 0; tommy_node* i; #if HAVE_THREAD /* start all threads */ for (i = tommy_list_head(list); i != 0; i = i->next) { devinfo_t* devinfo = i->data; thread_create(&devinfo->thread, func, devinfo); } /* join all threads */ for (i = tommy_list_head(list); i != 0; i = i->next) { devinfo_t* devinfo = i->data; void* retval; thread_join(devinfo->thread, &retval); if (retval != 0) ++fail; } #else for (i = tommy_list_head(list); i != 0; i = i->next) { devinfo_t* devinfo = i->data; if (func(devinfo) != 0) ++fail; } #endif if (fail != 0) { /* LCOV_EXCL_START */ return -1; /* LCOV_EXCL_STOP */ } return 0; } int devquery(tommy_list* high, tommy_list* low, int operation) { void* (*func)(void* arg) = 0; #if HAVE_LINUX_DEVICE tommy_node* i; struct stat st; /* sysfs interface is required */ if (stat("/sys/dev/block", &st) != 0) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Missing interface /sys/dev/block.\n"); return -1; /* LCOV_EXCL_STOP */ } /* for each high device */ for (i = tommy_list_head(high); i != 0; i = i->next) { devinfo_t* devinfo = i->data; uint64_t device = devinfo->device; uint64_t access_stat; #if HAVE_SYNCFS if (operation == DEVICE_DOWN) { /* flush the high level filesystem before spinning down */ int f = open(devinfo->mount, O_RDONLY); if (f >= 0) { syncfs(f); close(f); } } #endif /* if the major is the null device, find the real one */ if (major(device) == 0) { /* obtain the real device */ if (devdereference(device, &device) != 0) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Failed to dereference device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } } /* retrieve access stat for the high level device */ if (devstat(device, &access_stat) == 0) { /* cumulate access stat in the first split */ if (devinfo->split) devinfo->split->access_stat += access_stat; else devinfo->access_stat += access_stat; } /* get the device file */ if (devresolve(device, devinfo->file, sizeof(devinfo->file)) != 0) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } /* expand the tree of devices */ if (devtree(devinfo, device, low) != 0) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "Failed to expand device '%u:%u'.\n", major(device), minor(device)); return -1; /* LCOV_EXCL_STOP */ } } #else (void)high; #endif switch (operation) { case DEVICE_UP : func = thread_spinup; break; case DEVICE_DOWN : func = thread_spindown; break; case DEVICE_SMART : func = thread_smart; break; case DEVICE_PROBE : func = thread_probe; break; case DEVICE_DOWNIFUP : func = thread_spindownifup; break; } if (!func) return 0; return device_thread(low, func); } void os_default_conf(char* conf, size_t conf_size, const char* argv0) { (void)argv0; #ifdef SYSCONFDIR /* if it exists, give precedence at sysconfdir, usually /usr/local/etc */ if (eaccess(SYSCONFDIR "/" PACKAGE ".conf", F_OK) == 0) { pathcpy(conf, conf_size, SYSCONFDIR "/" PACKAGE ".conf"); } else /* otherwise fallback to plain /etc */ #endif { pathcpy(conf, conf_size, "/etc/" PACKAGE ".conf"); } } void os_init(int opt) { #if HAVE_BLKID int ret; ret = blkid_get_cache(&cache, NULL); if (ret != 0) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "WARNING Failed to get blkid cache\n"); /* LCOV_EXCL_STOP */ } #endif /* set LC_ALL=C to make smartctl ignoring the locale when printing info */ setenv("LC_ALL", "C", 1); (void)opt; } void os_done(void) { #if HAVE_BLKID if (cache != 0) blkid_put_cache(cache); #endif } /* LCOV_EXCL_START */ void os_abort(void) { #if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS void* stack[32]; char** messages; size_t size; unsigned i; #endif printf("Stacktrace of " PACKAGE " v" VERSION); #ifdef _linux printf(", linux"); #endif #ifdef __GNUC__ printf(", gcc " __VERSION__); #endif printf(", %d-bit", (int)sizeof(void*) * 8); printf(", PATH_MAX=%d", PATH_MAX); printf("\n"); #if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS size = backtrace(stack, 32); messages = backtrace_symbols(stack, size); for (i = 1; i < size; ++i) { const char* msg; if (messages) msg = messages[i]; else msg = ""; printf("[bt] %02u: %s\n", i, msg); if (messages) { int ret; char addr2line[1024]; size_t j = 0; while (msg[j] != '(' && msg[j] != ' ' && msg[j] != 0) ++j; snprintf(addr2line, sizeof(addr2line), "addr2line %p -e %.*s", stack[i], (unsigned)j, msg); ret = system(addr2line); if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0) printf("exit:%d\n", WEXITSTATUS(ret)); if (WIFSIGNALED(ret)) printf("signal:%d\n", WTERMSIG(ret)); } } #endif printf("Please report this error to the SnapRAID Forum:\n"); printf("https://sourceforge.net/p/snapraid/discussion/1677233/\n"); abort(); } /* LCOV_EXCL_STOP */ void os_clear(void) { /* ANSI codes */ printf("\033[H"); /* cursor at topleft */ printf("\033[2J"); /* clear screen */ } size_t direct_size(void) { long size; size = sysconf(_SC_PAGESIZE); if (size == -1) { /* LCOV_EXCL_START */ log_fatal(EEXTERNAL, "No page size\n"); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } return size; } #if HAVE_LINUX_DEVICE /* List of possible ambient temperature labels */ const char* AMBIENT_LABEL[] = { "systin", "auxtin", "mb", "m/b", "board", "motherboard", "system", "chassis", "case", "room", "ambient", 0 }; int ambient_temperature(void) { DIR* dir; struct dirent* entry; int lowest_temp = 0; dir = opendir("/sys/class/hwmon"); if (!dir) return 0; /* iterate through hwmon devices */ while ((entry = readdir(dir)) != NULL) { char path[PATH_MAX]; DIR* hwmon_dir; struct dirent* hwmon_entry; if (strncmp(entry->d_name, "hwmon", 5) != 0) continue; pathprint(path, sizeof(path), "/sys/class/hwmon/%s", entry->d_name); /* iterate through temp*_input files */ hwmon_dir = opendir(path); if (!hwmon_dir) continue; while ((hwmon_entry = readdir(hwmon_dir)) != NULL) { char hwmon_name[PATH_MAX]; char value[128]; char name[128]; char label[128]; char* dash; char* e; long temp; if (strncmp(hwmon_entry->d_name, "temp", 4) != 0) continue; /* make a copy to allow modifications in place */ pathcpy(hwmon_name, sizeof(hwmon_name), hwmon_entry->d_name); dash = strrchr(hwmon_name, '_'); if (dash == 0) continue; if (strcmp(dash, "_input") != 0) continue; /* read the temperature */ pathprint(path, sizeof(path), "/sys/class/hwmon/%s/%s", entry->d_name, hwmon_name); if (sysattr(path, value, sizeof(value)) != 0) continue; temp = strtol(value, &e, 10) / 1000; if (*e != 0 && !isspace((unsigned char)*e)) continue; /* cut the file name at "_input" */ *dash = 0; /* read the corresponding name */ pathprint(path, sizeof(path), "/sys/class/hwmon/%s/name", entry->d_name); if (sysattr(path, name, sizeof(name)) != 0) { /* fallback to using the hwmon name */ pathcpy(name, sizeof(name), entry->d_name); } /* read the corresponding label file */ pathprint(path, sizeof(path), "/sys/class/hwmon/%s/%s_label", entry->d_name, hwmon_name); if (sysattr(path, label, sizeof(label)) != 0) { /* fallback to using the temp* name (e.g., temp1, temp2) */ pathcpy(label, sizeof(label), hwmon_name); } log_tag("thermal:ambient:device:%s:%s:%s:%s:%ld\n", entry->d_name, name, hwmon_name, label, temp); /* check if temperature is in reasonable range */ if (temp < 15 || temp > 40) continue; /* lower case */ strlwr(label); /* check if label matches possible ambient labels */ for (int i = 0; AMBIENT_LABEL[i]; ++i) { if (worddigitstr(label, AMBIENT_LABEL[i]) != 0) { log_tag("thermal:ambient:candidate:%ld\n", temp); if (lowest_temp == 0 || lowest_temp > temp) lowest_temp = temp; break; } } /* accept also generic "temp1" */ if (strcmp(label, "temp1") == 0) { log_tag("thermal:ambient:candidate:%ld\n", temp); if (lowest_temp == 0 || lowest_temp > temp) lowest_temp = temp; } } closedir(hwmon_dir); } closedir(dir); return lowest_temp; } #else int ambient_temperature(void) { return 0; } #endif int devmap(void) { #if HAVE_LINUX_DEVICE char path[PATH_MAX]; DIR* d = opendir("/sys/block"); if (!d) return -1; struct dirent* dd; while (1) { char buf[256]; dd = readdir(d); if (dd == 0) break; if (dd->d_name[0] == '.') continue; /* verify it has a hardware device link (excludes virtual disks) */ pathprint(path, sizeof(path), "/sys/block/%s/device", dd->d_name); if (eaccess(path, F_OK) != 0) continue; /* device/serial */ pathprint(path, sizeof(path), "/sys/block/%s/device/serial", dd->d_name); if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) { log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf)); } /* device/vpd_pg80 */ pathprint(path, sizeof(path), "/sys/block/%s/device/vpd_pg80", dd->d_name); if (sysattr_vpd_pg80(path, buf, sizeof(buf)) == 0 && buf[0] != 0) { log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf)); } /* device/vpd_pg83 */ pathprint(path, sizeof(path), "/sys/block/%s/device/vpd_pg83", dd->d_name); if (sysattr_vpd_pg83(path, buf, sizeof(buf)) == 0 && buf[0] != 0) { log_tag("map:/dev/%s:naa.%s\n", dd->d_name, esc_tag(buf)); } /* device/WWN */ pathprint(path, sizeof(path), "/sys/block/%s/device/wwn", dd->d_name); if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) { log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf)); } /* device/WWID */ pathprint(path, sizeof(path), "/sys/block/%s/device/wwid", dd->d_name); if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) { log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf)); } /* WWN */ pathprint(path, sizeof(path), "/sys/block/%s/wwn", dd->d_name); if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) { log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf)); } /* WWID */ pathprint(path, sizeof(path), "/sys/block/%s/wwid", dd->d_name); if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) { log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf)); } /* uuid */ pathprint(path, sizeof(path), "/sys/block/%s/uuid", dd->d_name); if (sysattr(path, buf, sizeof(buf)) == 0 && buf[0] != 0) { log_tag("map:/dev/%s:%s\n", dd->d_name, esc_tag(buf)); } } closedir(d); #endif return 0; } #endif