snapraid/cmdline/unix.c
Andrea Mazzoleni d452b109bc
Some checks are pending
Build / build-mac (push) Waiting to run
Build / build-linux (push) Waiting to run
Build / build-win (push) Waiting to run
CodeQL / Analyze (push) Waiting to run
More regression tests
2025-10-28 12:15:50 +01:00

1923 lines
43 KiB
C

/*
* Copyright (C) 2013 Andrea Mazzoleni
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "portable.h"
#ifndef __MINGW32__ /* Only for Unix */
#include "support.h"
/**
* Exit codes.
*/
int exit_success = 0;
int exit_failure = 1;
int exit_sync_needed = 2;
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 "link";
if (S_ISLNK(st->st_mode))
return "symbolic-link";
if (S_ISSOCK(st->st_mode))
return "socket";
return "unknown";
}
/**
* Read a file from sys
*
* Return -1 on error, otherwise the 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 */
log_fatal("Failed to open '%s'.\n", path);
return -1;
/* LCOV_EXCL_STOP */
}
len = read(f, buf, buf_size);
if (len < 0) {
/* LCOV_EXCL_START */
close(f);
log_fatal("Failed to read '%s'.\n", path);
return -1;
/* LCOV_EXCL_STOP */
}
ret = close(f);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("Failed to close '%s'.\n", path);
return -1;
/* LCOV_EXCL_STOP */
}
if (len == (int)buf_size)
buf[buf_size - 1] = 0;
else
buf[len] = 0;
return len;
}
#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)
return -1;
if (ret == sizeof(buf)) {
/* LCOV_EXCL_START */
log_fatal("Too long read '%s'.\n", path);
return -1;
/* LCOV_EXCL_STOP */
}
tag_len = strlen(tag);
for (i = buf; *i; ++i) {
char* p = i;
/* start with a space */
if (p != buf) {
if (!isspace(*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("Missing tag '%s' for '%s'.\n", tag, path);
return -1;
/* LCOV_EXCL_STOP */
}
/* terminate at the first space */
e = i;
while (*e != 0 && !isspace(*e))
++e;
*e = 0;
if (!*i) {
/* LCOV_EXCL_START */
log_fatal("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;
}
while ((dd = readdir(d)) != 0) {
/* skip "." and ".." files, UUIDs never start with '.' */
if (dd->d_name[0] == '.')
continue;
ret = fstatat(dirfd(d), 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
#ifdef __APPLE__
static int devuuid_darwin(const char* path, char* uuid, size_t uuid_size)
{
CFStringRef path_apple = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
CFURLRef path_appler = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_apple, kCFURLPOSIXPathStyle, false);
DADiskRef disk;
do {
disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, path_appler);
if (disk) {
CFRelease(path_appler);
break;
} else {
CFURLRef parent_path_appler = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, path_appler);
CFRelease(path_appler);
path_appler = parent_path_appler;
}
} while (true); // This is guaranteed to succeed eventually because it'll hit `/`.
CFDictionaryRef description = DADiskCopyDescription(disk);
CFUUIDRef uuid_apple = CFDictionaryGetValue(description, kDADiskDescriptionVolumeUUIDKey);
CFStringRef uuid_string = CFUUIDCreateString(kCFAllocatorDefault, uuid_apple);
bool success = CFStringGetCString(uuid_string, uuid, uuid_size, kCFStringEncodingUTF8);
CFRelease(uuid_string);
CFRelease(description);
CFRelease(disk);
CFRelease(session);
CFRelease(path_apple);
if (success) {
return 0;
} else {
return 1;
}
}
#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 {
struct fiemap fiemap;
struct fiemap_extent extent;
} fm;
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 */
memset(&fm, 0, sizeof(fm));
fm.fiemap.fm_start = 0;
fm.fiemap.fm_length = ~0ULL;
fm.fiemap.fm_flags = FIEMAP_FLAG_SYNC; /* required to ensure that just created files report a valid address and not 0 */
fm.fiemap.fm_extent_count = 1; /* we are interested only at the first block */
if (ioctl(f, FS_IOC_FIEMAP, &fm) != -1) {
uint32_t flags = fm.fiemap.fm_extents[0].fe_flags;
uint64_t offset = fm.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;
}
if (close(f) == -1)
return -1;
return 0;
}
/* 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;
}
int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_hardlinks, uint64_t* total_space, uint64_t* free_space)
{
char type[64];
const char* ptype;
#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 0x65735546 : /* FUSE, "fuseblk" in the stat command */
case 0x4d44 : /* 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 0x5346544E : /* NTFS */
case 0x4d44 : /* 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
}
#if HAVE_STATFS && HAVE_STRUCT_STATFS_F_FSTYPENAME
/* get the filesystem type directly from the struct (Mac OS X) */
(void)type;
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 */
switch (st.f_type) {
case 0x65735546 : ptype = "fuseblk"; break;
case 0x4D44 : ptype = "vfat/msdos"; break;
case 0xEF53 : ptype = "ext2/3/4"; break;
case 0x6969 : ptype = "nfs"; break; /* remote */
case 0x6E667364 : ptype = "nfsd"; break; /* remote */
case 0x517B : ptype = "smb"; break; /* remote */
case 0x5346544E : ptype = "ntfs"; break;
case 0x52654973 : ptype = "reiserfs"; break;
case 0x3153464A : ptype = "jfs"; break;
case 0x58465342 : ptype = "xfs"; break;
case 0x9123683E : ptype = "btrfs"; break;
case 0x2FC12FC1 : ptype = "zfs"; break;
default :
snprintf(type, sizeof(type), "0x%X", (unsigned)st.f_type);
ptype = type;
}
#else
(void)type;
ptype = "unknown";
#endif
log_tag("statfs:%s: %s \n", ptype, path);
return 0;
}
uint64_t 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 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("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("Failed to read '%s'.\n", path);
return 0;
/* LCOV_EXCL_STOP */
}
if (len == sizeof(buf)) {
/* LCOV_EXCL_START */
close(f);
log_fatal("Too long read '%s'.\n", path);
return 0;
/* LCOV_EXCL_STOP */
}
ret = close(f);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("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("Invalid format in '%s' for '%s'.\n", path, buf);
return 0;
/* LCOV_EXCL_STOP */
}
mi = strtoul(e + 1, &e, 10);
if (*e != 0 && !isspace(*e)) {
/* LCOV_EXCL_START */
log_fatal("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(const char* name, const char* smartctl, const int* smartignore, dev_t device, devinfo_t* parent, 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(name, smartctl, smartignore, subdev, parent, 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 (access(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("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), name);
pathcpy(devinfo->smartctl, sizeof(devinfo->smartctl), smartctl);
memcpy(devinfo->smartignore, 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
/**
* Scan all the devices.
*
* If a device is already in, it's not added again.
*/
#if HAVE_LINUX_DEVICE
static int devscan(tommy_list* list)
{
char dir[PATH_MAX];
DIR* d;
struct dirent* dd;
pathprint(dir, sizeof(dir), "/sys/dev/block/");
/* check if there is a slaves list */
d = opendir(dir);
if (d == 0) {
/* LCOV_EXCL_START */
log_fatal("Failed to open dir '%s'.\n", dir);
return -1;
/* LCOV_EXCL_STOP */
}
while ((dd = readdir(d)) != 0) {
char path[PATH_MAX];
tommy_node* i;
dev_t device;
devinfo_t* devinfo;
if (dd->d_name[0] == '.')
continue;
pathprint(path, sizeof(path), "/sys/dev/block/%s/device", dd->d_name);
/* check if it's a real device */
if (access(path, F_OK) != 0)
continue;
pathprint(path, sizeof(path), "/sys/dev/block/%s/dev", dd->d_name);
device = devread(path);
if (!device) {
/* LCOV_EXCL_START */
log_tag("scan:skip: Skipping device %s because failed to read its device number.\n", dd->d_name);
continue;
/* LCOV_EXCL_STOP */
}
/* check if already present */
for (i = tommy_list_head(list); i != 0; i = i->next) {
devinfo = i->data;
if (devinfo->device == device)
break;
}
/* if already present */
if (i != 0)
continue;
/* get the device file */
if (devresolve(device, path, sizeof(path)) != 0) {
/* LCOV_EXCL_START */
log_tag("scan:skip: Skipping device %u:%u because failed to resolve.\n", major(device), minor(device));
continue;
/* LCOV_EXCL_STOP */
}
devinfo = calloc_nofail(1, sizeof(devinfo_t));
devinfo->device = device;
pathcpy(devinfo->file, sizeof(devinfo->file), path);
/* insert in the list */
tommy_list_insert_tail(list, &devinfo->node, devinfo);
}
closedir(d);
return 0;
}
#endif
/**
* Get SMART attributes.
*/
#if HAVE_LINUX_DEVICE
static int devsmart(dev_t device, const char* name, const char* smartctl, uint64_t* smart, char* serial, char* vendor, char* model)
{
char cmd[PATH_MAX + 64];
char file[PATH_MAX];
FILE* f;
int ret;
if (devresolve(device, file, sizeof(file)) != 0) {
/* LCOV_EXCL_START */
log_fatal("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), "smartctl -a %s", option);
} else {
snprintf(cmd, sizeof(cmd), "smartctl -a %s", file);
}
log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
f = popen(cmd, "r");
if (!f) {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' (from popen).\n", cmd);
return -1;
/* LCOV_EXCL_STOP */
}
if (smartctl_attribute(f, file, name, smart, serial, vendor, model) != 0) {
/* LCOV_EXCL_START */
pclose(f);
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_fatal("Failed to run '%s' (not exited).\n", cmd);
return -1;
/* LCOV_EXCL_STOP */
}
if (WEXITSTATUS(ret) == 127) {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' (from sh).\n", cmd);
return -1;
/* LCOV_EXCL_STOP */
}
/* store the return smartctl return value */
smart[SMART_FLAGS] = WEXITSTATUS(ret);
return 0;
}
#endif
/**
* Get POWER state.
*/
#if HAVE_LINUX_DEVICE
static int devprobe(dev_t device, const char* name, const char* smartctl, int* power)
{
char cmd[PATH_MAX + 64];
char file[PATH_MAX];
FILE* f;
int ret;
if (devresolve(device, file, sizeof(file)) != 0) {
/* LCOV_EXCL_START */
log_fatal("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), "smartctl -n standby,3 -i %s", option);
} else {
snprintf(cmd, sizeof(cmd), "smartctl -n standby,3 -i %s", file);
}
log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
f = popen(cmd, "r");
if (!f) {
/* LCOV_EXCL_START */
log_fatal("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);
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_fatal("Failed to run '%s' (not exited).\n", cmd);
return -1;
/* LCOV_EXCL_STOP */
}
if (WEXITSTATUS(ret) == 127) {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' (from sh).\n", cmd);
return -1;
/* LCOV_EXCL_STOP */
}
if (WEXITSTATUS(ret) == 0) {
*power = POWER_ACTIVE;
} else if (WEXITSTATUS(ret) == 3) {
*power = POWER_STANDBY;
} else {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' with return code %xh.\n", cmd, WEXITSTATUS(ret));
return -1;
/* LCOV_EXCL_STOP */
}
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;
if (devresolve(device, file, sizeof(file)) != 0) {
/* LCOV_EXCL_START */
log_fatal("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), "smartctl -s standby,now %s", option);
} else {
snprintf(cmd, sizeof(cmd), "smartctl -s standby,now %s", file);
}
log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
f = popen(cmd, "r");
if (!f) {
/* LCOV_EXCL_START */
log_fatal("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);
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_fatal("Failed to run '%s' (not exited).\n", cmd);
return -1;
/* LCOV_EXCL_STOP */
}
if (WEXITSTATUS(ret) == 127) {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' (from sh).\n", cmd);
return -1;
/* LCOV_EXCL_STOP */
}
if (WEXITSTATUS(ret) != 0) {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' with return code %xh.\n", cmd, WEXITSTATUS(ret));
return -1;
/* LCOV_EXCL_STOP */
}
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)
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)
{
char file[PATH_MAX];
int ret;
int f;
void* buf;
if (devresolve(device, file, sizeof(file)) != 0) {
/* LCOV_EXCL_START */
log_fatal("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, 512, 512) != 0) {
/* LCOV_EXCL_START */
log_fatal("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_fatal("Failed to open device '%u:%u'.\n", major(device), minor(device));
return -1;
/* LCOV_EXCL_STOP */
}
ret = read(f, buf, 512);
if (ret < 0) {
/* LCOV_EXCL_START */
close(f);
free(buf);
log_fatal("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_fatal("Failed to close device '%u:%u'.\n", major(device), minor(device));
return -1;
/* LCOV_EXCL_STOP */
}
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 = tick_ms();
if (devup(devinfo->device) != 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, tick_ms() - start);
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 = 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, 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 = 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, 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->smart_serial, devinfo->smart_vendor, devinfo->smart_model) != 0) {
/* LCOV_EXCL_START */
return (void*)-1;
/* LCOV_EXCL_STOP */
}
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) != 0) {
/* LCOV_EXCL_START */
return (void*)-1;
/* LCOV_EXCL_STOP */
}
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, int others)
{
void* (*func)(void* arg) = 0;
if (operation == DEVICE_DOWN) {
/* flush all the disk caches before spinning down */
sync();
}
#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("Missing interface /sys/dev/block.\n");
return -1;
/* LCOV_EXCL_STOP */
}
/* for each device */
for (i = tommy_list_head(high); i != 0; i = i->next) {
devinfo_t* devinfo = i->data;
uint64_t device = devinfo->device;
/* 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("Failed to dereference device '%u:%u'.\n", major(device), minor(device));
return -1;
/* LCOV_EXCL_STOP */
}
}
/* get the device file */
if (devresolve(device, devinfo->file, sizeof(devinfo->file)) != 0) {
/* LCOV_EXCL_START */
log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
return -1;
/* LCOV_EXCL_STOP */
}
/* expand the tree of devices */
if (devtree(devinfo->name, devinfo->smartctl, devinfo->smartignore, device, devinfo, low) != 0) {
/* LCOV_EXCL_START */
log_fatal("Failed to expand device '%u:%u'.\n", major(device), minor(device));
return -1;
/* LCOV_EXCL_STOP */
}
}
/* add other devices */
if (others) {
if (devscan(low) != 0) {
/* LCOV_EXCL_START */
log_fatal("Failed to list other devices.\n");
return -1;
/* LCOV_EXCL_STOP */
}
}
#else
(void)high;
(void)others;
#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_init(int opt)
{
#if HAVE_BLKID
int ret;
ret = blkid_get_cache(&cache, NULL);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("WARNING Failed to get blkid cache\n");
/* LCOV_EXCL_STOP */
}
#endif
(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 = "<unknown>";
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("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",
"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 value[128];
char name[128];
char label[128];
char* dash;
int ret;
char* e;
int temp;
if (strncmp(hwmon_entry->d_name, "temp", 4) != 0)
continue;
dash = strrchr(hwmon_entry->d_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_entry->d_name);
ret = sysread(path, value, sizeof(value));
if (ret < 0)
continue;
temp = strtol(value, &e, 10) / 1000;
if (*e != 0 && !isspace(*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);
ret = sysread(path, name, sizeof(name));
if (ret < 0) {
/* fallback to using the hwmon entry */
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_entry->d_name);
ret = sysread(path, label, sizeof(label));
if (ret < 0) {
/* fallback to using the temp* name (e.g., temp1, temp2) */
pathcpy(label, sizeof(label), hwmon_entry->d_name);
}
/* trim spaces */
strtrim(name);
strtrim(label);
log_tag("thermal:ambient:device:%s:%s:%s:%s:%d\n", entry->d_name, name, hwmon_entry->d_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:%d\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:%d\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
#endif