mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 10:51:54 -04:00
4643 lines
111 KiB
C
4643 lines
111 KiB
C
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
// Copyright (C) 2013 Andrea Mazzoleni
|
||
|
||
#include "os/portable.h"
|
||
|
||
#ifndef __MINGW32__ /* Only for Unix */
|
||
|
||
#include "app.h"
|
||
#include "support.h"
|
||
#include "raid/cpu.h"
|
||
|
||
/****************************************************************************/
|
||
/* global */
|
||
|
||
/**
|
||
* Exit codes.
|
||
*/
|
||
int exit_success = 0;
|
||
int exit_failure = 1;
|
||
int exit_sync_needed = 2;
|
||
|
||
/****************************************************************************/
|
||
/* signal */
|
||
|
||
volatile sig_atomic_t global_interrupt = 0;
|
||
|
||
int os_signal_interrupt(void)
|
||
{
|
||
return (int)global_interrupt;
|
||
}
|
||
|
||
/* LCOV_EXCL_START */
|
||
void app_signal_handler(int signum)
|
||
{
|
||
/* report the request of interruption with the signal received */
|
||
global_interrupt = signum;
|
||
}
|
||
/* LCOV_EXCL_STOP */
|
||
|
||
/****************************************************************************/
|
||
/* find */
|
||
|
||
#define BTRFS_SUPER_MAGIC 0x9123683E
|
||
#define BCACHEFS_SUPER_MAGIC 0xCA451A4E
|
||
#define ZFS_SUPER_MAGIC 0x2FC12FC1
|
||
|
||
#if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE
|
||
/**
|
||
* Return the filesystem type as an unsigned value.
|
||
*
|
||
* In glibc, 'struct statfs.f_type' has type '__fsword_t' (signed 'int' on 32-bit
|
||
* architectures and 'long' on 64-bit). Directly comparing signed 'f_type' against
|
||
* 32-bit unsigned magic constants (e.g. BTRFS_SUPER_MAGIC > 0x7FFFFFFF) triggers
|
||
* signedness comparison warnings on 32-bit systems. Converting to unsigned here
|
||
* avoids signed/unsigned comparison mismatches across all architectures.
|
||
*/
|
||
static inline unsigned statfs_type(const struct statfs* sfs)
|
||
{
|
||
return sfs->f_type;
|
||
}
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static const char* const bcachefs_paths[] = {
|
||
#ifdef BCACHEFS_PATH
|
||
/* Path configured at build time (e.g. on NixOS). */
|
||
BCACHEFS_PATH,
|
||
#else
|
||
"/usr/sbin/bcachefs",
|
||
"/sbin/bcachefs",
|
||
"/usr/local/sbin/bcachefs",
|
||
"/usr/bin/bcachefs",
|
||
"/bin/bcachefs",
|
||
"/usr/local/bin/bcachefs",
|
||
#ifdef __APPLE__
|
||
/* macOS (Intel & Apple Silicon) */
|
||
"/opt/homebrew/sbin/bcachefs",
|
||
#endif
|
||
#endif
|
||
0
|
||
};
|
||
|
||
static const char* find_bcachefs(void)
|
||
{
|
||
for (int i = 0; bcachefs_paths[i]; ++i) {
|
||
if (eaccess(bcachefs_paths[i], X_OK) == 0) {
|
||
return bcachefs_paths[i];
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static const char* const zfs_paths[] = {
|
||
#ifdef ZFS_PATH
|
||
/* Path configured at build time (e.g. on NixOS). */
|
||
ZFS_PATH,
|
||
#else
|
||
"/usr/sbin/zfs",
|
||
"/sbin/zfs",
|
||
"/usr/local/sbin/zfs",
|
||
"/usr/bin/zfs",
|
||
"/bin/zfs",
|
||
"/usr/local/bin/zfs",
|
||
#ifdef __APPLE__
|
||
/* macOS (Intel & Apple Silicon) */
|
||
"/opt/homebrew/sbin/zfs",
|
||
#endif
|
||
#endif
|
||
0
|
||
};
|
||
|
||
static const char* find_zfs(void)
|
||
{
|
||
for (int i = 0; zfs_paths[i]; ++i) {
|
||
if (eaccess(zfs_paths[i], X_OK) == 0) {
|
||
return zfs_paths[i];
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static const char* const zpool_paths[] = {
|
||
#ifdef ZPOOL_PATH
|
||
/* Path configured at build time (e.g. on NixOS). */
|
||
ZPOOL_PATH,
|
||
#else
|
||
"/usr/sbin/zpool",
|
||
"/sbin/zpool",
|
||
"/usr/local/sbin/zpool",
|
||
"/usr/bin/zpool",
|
||
"/bin/zpool",
|
||
"/usr/local/bin/zpool",
|
||
#ifdef __APPLE__
|
||
/* macOS (Intel & Apple Silicon) */
|
||
"/opt/homebrew/sbin/zpool",
|
||
#endif
|
||
#endif
|
||
0
|
||
};
|
||
|
||
static const char* find_zpool(void)
|
||
{
|
||
for (int i = 0; zpool_paths[i]; ++i) {
|
||
if (eaccess(zpool_paths[i], X_OK) == 0) {
|
||
return zpool_paths[i];
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
|
||
static const char* smartctl_paths[] = {
|
||
#ifdef SMARTCTL_PATH
|
||
/* Path configured at build time (e.g. on NixOS). */
|
||
SMARTCTL_PATH,
|
||
#else
|
||
/* Linux & BSD */
|
||
"/usr/sbin/smartctl",
|
||
"/sbin/smartctl",
|
||
"/usr/local/sbin/smartctl",
|
||
"/usr/bin/smartctl",
|
||
"/usr/local/bin/smartctl",
|
||
#ifdef __APPLE__
|
||
/* macOS (Intel & Apple Silicon) */
|
||
"/opt/homebrew/sbin/smartctl",
|
||
#endif
|
||
#endif
|
||
0
|
||
};
|
||
|
||
static const char* find_smartctl(void)
|
||
{
|
||
for (int i = 0; smartctl_paths[i]; ++i) {
|
||
if (eaccess(smartctl_paths[i], X_OK) == 0) {
|
||
return smartctl_paths[i];
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
/****************************************************************************/
|
||
/* sys */
|
||
|
||
/**
|
||
* Read a file from sys
|
||
*
|
||
* Return -1 on error, otherwise the size of data read
|
||
*/
|
||
#if HAVE_LINUX_DEVICE
|
||
static ssize_t sysread(const char* path, char* buf, size_t buf_size)
|
||
{
|
||
int f;
|
||
int ret;
|
||
ssize_t 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
|
||
/*
|
||
* sysattr_vpd_pg80 — parse raw binary VPD page 0x80 (Unit Serial Number)
|
||
* from a sysfs vpd_pg80 file.
|
||
*/
|
||
static int sysattr_vpd_pg80(const char* path, char* dst, size_t dst_size)
|
||
{
|
||
unsigned char buf[512];
|
||
ssize_t ret = sysread(path, (char*)buf, sizeof(buf));
|
||
|
||
/* need at least the header */
|
||
if (ret < 4) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* validate page code */
|
||
if (buf[1] != 0x80) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
size_t page_len = ((size_t)buf[2] << 8) | buf[3];
|
||
size_t available = (size_t)ret - 4;
|
||
|
||
/* if incomplete */
|
||
if (page_len > available) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* if empty */
|
||
if (page_len == 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* if too big to store the 0 termination */
|
||
if (4 + page_len + 1 > sizeof(buf)) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
char* attr = (char*)buf + 4;
|
||
attr[page_len] = 0;
|
||
strtrim(attr);
|
||
|
||
/* if empty */
|
||
if (!*attr) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
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];
|
||
|
||
ssize_t ret = sysread(path, (char*)buf, sizeof(buf));
|
||
if (ret < 4) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* validate page code */
|
||
if (buf[1] != 0x83) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
size_t page_len = ((size_t)buf[2] << 8) | buf[3];
|
||
|
||
/*
|
||
* Clamp to what was actually read. This is safe because descriptors are
|
||
* parsed individually below with strict bounds checking and exact length
|
||
* validation, so incomplete or truncated data is never returned.
|
||
*/
|
||
size_t available = (size_t)ret - 4;
|
||
if (page_len > available)
|
||
page_len = available;
|
||
|
||
/* if empty */
|
||
if (page_len == 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/*
|
||
* 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 log 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)
|
||
{
|
||
ssize_t 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
|
||
|
||
/******************************************************************************/
|
||
/* cpu */
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static long long syslong(const char* path)
|
||
{
|
||
char buf[32];
|
||
if (sysattr(path, buf, sizeof(buf)) != 0)
|
||
return -1;
|
||
char* end;
|
||
long long v = strtoll(buf, &end, 10);
|
||
if (end == buf)
|
||
return -1;
|
||
|
||
return v;
|
||
}
|
||
|
||
/*
|
||
* Parse Linux cpulist format (e.g. "0-3,5,8-11") into an array of CPU IDs.
|
||
*
|
||
* Returns the total number of CPUs in the list. Stores up to max_cpus entries.
|
||
*/
|
||
static unsigned parse_cpulist(const char* str, unsigned* cpus, unsigned max_cpus)
|
||
{
|
||
const char* p = str;
|
||
unsigned count = 0;
|
||
|
||
while (*p != 0) {
|
||
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
|
||
++p;
|
||
if (*p == 0)
|
||
break;
|
||
|
||
char* end;
|
||
unsigned long start = strtoul(p, &end, 10);
|
||
if (end == p)
|
||
break;
|
||
|
||
unsigned long stop = start;
|
||
p = end;
|
||
if (*p == '-') {
|
||
++p;
|
||
stop = strtoul(p, &end, 10);
|
||
if (end == p)
|
||
break;
|
||
p = end;
|
||
}
|
||
|
||
if (stop < start || stop >= CPU_SETSIZE)
|
||
break;
|
||
|
||
for (unsigned long i = start;; ++i) {
|
||
if (cpus != 0 && count < max_cpus)
|
||
cpus[count] = (unsigned)i;
|
||
++count;
|
||
|
||
if (i == stop)
|
||
break;
|
||
}
|
||
|
||
if (*p == ',')
|
||
++p;
|
||
}
|
||
|
||
return count;
|
||
}
|
||
|
||
/*
|
||
* Read online CPU IDs from /sys/devices/system/cpu/online.
|
||
*
|
||
* Falls back to consecutive 0..N-1 IDs if sysfs is unavailable.
|
||
* Returns the total number of online CPUs. Stores up to max_cpus entries in cpus.
|
||
*/
|
||
static unsigned read_online_cpus(unsigned* cpus, unsigned max_cpus)
|
||
{
|
||
char buf[1024];
|
||
if (sysattr("/sys/devices/system/cpu/online", buf, sizeof(buf)) == 0) {
|
||
unsigned count = parse_cpulist(buf, cpus, max_cpus);
|
||
if (count > 0)
|
||
return count;
|
||
}
|
||
|
||
/* fallback: consecutive IDs from 0 */
|
||
long n = sysconf(_SC_NPROCESSORS_ONLN);
|
||
if (n <= 0)
|
||
n = sysconf(_SC_NPROCESSORS_CONF);
|
||
if (n <= 0)
|
||
n = 1;
|
||
|
||
unsigned count = (unsigned)n;
|
||
unsigned store = count < max_cpus ? count : max_cpus;
|
||
for (unsigned i = 0; i < store; ++i) {
|
||
if (cpus != 0)
|
||
cpus[i] = i;
|
||
}
|
||
return count;
|
||
}
|
||
|
||
/*
|
||
* Returns 1 if the specified cpu is the lowest-numbered logical CPU in its
|
||
* physical core's SMT sibling set.
|
||
*/
|
||
static int cpu_is_primary_sibling(unsigned i)
|
||
{
|
||
char path[256];
|
||
char buf[256];
|
||
|
||
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/topology/thread_siblings_list", i);
|
||
if (sysattr(path, buf, sizeof(buf)) != 0)
|
||
return 1; /* assume primary */
|
||
|
||
unsigned siblings[16];
|
||
unsigned count = parse_cpulist(buf, siblings, sizeof(siblings) / sizeof(siblings[0]));
|
||
if (count == 0)
|
||
return 1;
|
||
|
||
return i == siblings[0];
|
||
}
|
||
|
||
/**
|
||
* os_get_optimal_cpu() – returns the logical CPU index of the "fastest"
|
||
* core using a three-tier strategy:
|
||
*
|
||
* Tier 1 – ACPI CPPC preferred-core ranking (hardware-binned quality)
|
||
* Tier 2 – cpufreq max-frequency (P-core vs E-core discrimination)
|
||
* Tier 3 – SMT topology isolation (primary sibling only)
|
||
*/
|
||
int os_get_optimal_cpu(void)
|
||
{
|
||
char path[256];
|
||
unsigned cpus[CPU_SETSIZE];
|
||
unsigned total_cpus = read_online_cpus(cpus, CPU_SETSIZE);
|
||
unsigned num_cpus = total_cpus < CPU_SETSIZE ? total_cpus : CPU_SETSIZE;
|
||
if (num_cpus == 0)
|
||
return -1;
|
||
|
||
/* ACPI CPPC: highest_perf with nominal_perf tiebreaker */
|
||
long long max_highest = -1;
|
||
long long max_nominal = -1;
|
||
int best_cppc = -1;
|
||
for (unsigned k = 0; k < num_cpus; ++k) {
|
||
unsigned i = cpus[k];
|
||
|
||
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/acpi_cppc/highest_perf", i);
|
||
long long hp = syslong(path);
|
||
if (hp < 0)
|
||
continue;
|
||
|
||
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/acpi_cppc/nominal_perf", i);
|
||
long long np = syslong(path);
|
||
/* np may be missing */
|
||
|
||
/* tiebreaker priority: highest_perf > nominal_perf > non-CPU0 */
|
||
int better = (hp > max_highest)
|
||
|| (hp == max_highest && np > max_nominal)
|
||
|| (hp == max_highest && np == max_nominal && best_cppc == 0);
|
||
if (better) {
|
||
max_highest = hp;
|
||
max_nominal = np;
|
||
best_cppc = i;
|
||
}
|
||
}
|
||
|
||
if (best_cppc >= 0)
|
||
return best_cppc;
|
||
|
||
/* max frequency, then SMT isolation */
|
||
|
||
/* find the global maximum frequency among online CPUs */
|
||
long long global_max_freq = -1;
|
||
for (unsigned k = 0; k < num_cpus; ++k) {
|
||
unsigned i = cpus[k];
|
||
|
||
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/cpufreq/cpuinfo_max_freq", i);
|
||
long long freq = syslong(path);
|
||
if (freq < 0)
|
||
continue;
|
||
|
||
if (freq > global_max_freq)
|
||
global_max_freq = freq;
|
||
}
|
||
|
||
/* first online CPU at max freq that is a primary sibling, preferring non-CPU0 */
|
||
int best_isolated = -1; /* primary sibling, not CPU 0 */
|
||
int best_any = -1; /* any matching CPU, not CPU 0 */
|
||
int best_c0 = -1; /* CPU 0 fallback (always a primary sibling) */
|
||
|
||
for (unsigned k = 0; k < num_cpus; ++k) {
|
||
unsigned i = cpus[k];
|
||
|
||
if (global_max_freq != -1) {
|
||
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/cpufreq/cpuinfo_max_freq", i);
|
||
if (syslong(path) != global_max_freq)
|
||
continue;
|
||
}
|
||
|
||
if (i == 0) {
|
||
best_c0 = 0;
|
||
} else {
|
||
if (best_isolated == -1 && cpu_is_primary_sibling(i))
|
||
best_isolated = i;
|
||
if (best_any == -1)
|
||
best_any = i;
|
||
}
|
||
}
|
||
|
||
/* prefer non-CPU0, fall back to CPU0 only if no alternative exists */
|
||
if (best_isolated != -1)
|
||
return best_isolated;
|
||
if (best_any != -1)
|
||
return best_any;
|
||
if (best_c0 != -1)
|
||
return best_c0;
|
||
|
||
return -1;
|
||
}
|
||
#endif
|
||
|
||
/******************************************************************************/
|
||
/* resolve */
|
||
|
||
struct dev_struct {
|
||
uint64_t device; /**< Device ID. */
|
||
tommy_node node;
|
||
};
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int devdereference_btrfs(uint64_t device, const char* dir, int fd, tommy_list* devlist)
|
||
{
|
||
struct btrfs_ioctl_fs_info_args fs_info;
|
||
memset(&fs_info, 0, sizeof(fs_info));
|
||
if (ioctl(fd, BTRFS_IOC_FS_INFO, &fs_info) < 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (fs_info.max_id == 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
for (__u64 i = 1; i <= fs_info.max_id; ++i) {
|
||
struct btrfs_ioctl_dev_info_args dev_info;
|
||
memset(&dev_info, 0, sizeof(dev_info));
|
||
dev_info.devid = i;
|
||
|
||
if (ioctl(fd, BTRFS_IOC_DEV_INFO, &dev_info) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
/* btrfs device IDs (devid) are not guaranteed to be contiguous */
|
||
if (errno == ENODEV || errno == ENOENT)
|
||
continue; /* this is the expected error on missing id */
|
||
log_error(errno, "Failed ioctl BTRFS_IOC_DEV_INFO. %s.", strerror(errno));
|
||
goto bail;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* get major:minor, use stat on the path returned */
|
||
struct stat st;
|
||
if (stat((char*)dev_info.path, &st) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_error(errno, "Failed stat %s. %s.", dev_info.path, strerror(errno));
|
||
goto bail;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (!S_ISBLK(st.st_mode)) {
|
||
/* LCOV_EXCL_START */
|
||
log_error(errno, "Device %s is not a block device.", dev_info.path);
|
||
goto bail;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
|
||
dev->device = st.st_rdev;
|
||
tommy_list_insert_tail(devlist, &dev->node, dev);
|
||
|
||
log_tag("dereference:btrfs:%s:%u:%u:%u:%u\n", dir, major(device), minor(device), major(dev->device), minor(dev->device));
|
||
}
|
||
|
||
/* something has to be found */
|
||
if (tommy_list_empty(devlist))
|
||
goto bail;
|
||
|
||
return 0;
|
||
|
||
bail:
|
||
tommy_list_foreach(devlist, free);
|
||
tommy_list_init(devlist);
|
||
return -1;
|
||
}
|
||
|
||
/**
|
||
* Decodes octal escape sequences (\ooo) in place.
|
||
*
|
||
* In /proc/self/mountinfo, special characters like spaces, tabs, newlines,
|
||
* and backslashes are encoded as 3-digit octal escape sequences (e.g. \040 for space).
|
||
* Decoding is performed in place because the decoded character is always
|
||
* shorter than the 4-byte escape sequence.
|
||
*/
|
||
static void unescape_mount(char* s)
|
||
{
|
||
char* r = s;
|
||
char* w = s;
|
||
|
||
while (*r != 0) {
|
||
if (r[0] == '\\'
|
||
&& r[1] >= '0' && r[1] <= '7'
|
||
&& r[2] >= '0' && r[2] <= '7'
|
||
&& r[3] >= '0' && r[3] <= '7') {
|
||
*w = (char)(((r[1] - '0') << 6) | ((r[2] - '0') << 3) | (r[3] - '0'));
|
||
++w;
|
||
r += 4;
|
||
} else {
|
||
*w = *r;
|
||
++w;
|
||
++r;
|
||
}
|
||
}
|
||
*w = 0;
|
||
}
|
||
|
||
static int devdereference_bcachefs(uint64_t device, const char* dir, tommy_list* devlist)
|
||
{
|
||
char resolved[PATH_MAX];
|
||
char device_list[PATH_MAX * 8];
|
||
|
||
if (realpath(dir, resolved) == 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
FILE* fd = fopen("/proc/self/mountinfo", "r");
|
||
if (!fd) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/*
|
||
* 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
|
||
*/
|
||
size_t best_len = 0;
|
||
while (1) {
|
||
char buf[PATH_MAX * 2 + 64];
|
||
char* id_map[8];
|
||
unsigned id_mac;
|
||
char* fs_map[8];
|
||
unsigned fs_mac;
|
||
|
||
char* s = fgets(buf, sizeof(buf), fd);
|
||
if (s == 0)
|
||
break;
|
||
|
||
/* find the separator position */
|
||
char* separator = strstr(s, " - ");
|
||
if (!separator)
|
||
continue;
|
||
|
||
/* skip the separator */
|
||
*separator = 0;
|
||
separator += 3;
|
||
|
||
/* split the line */
|
||
id_mac = strsplit(id_map, 8, s, " \t\n");
|
||
fs_mac = strsplit(fs_map, 8, separator, " \t\n");
|
||
|
||
/* if too short, it's the wrong line */
|
||
if (id_mac < 5)
|
||
continue;
|
||
if (fs_mac < 2)
|
||
continue;
|
||
|
||
/* mount point must contain the directory */
|
||
unescape_mount(id_map[4]);
|
||
const char* mp = id_map[4];
|
||
size_t mp_len = strlen(mp);
|
||
if (strncmp(resolved, mp, mp_len) != 0)
|
||
continue;
|
||
if (mp_len > 1 && resolved[mp_len] != '/' && resolved[mp_len] != 0)
|
||
continue;
|
||
|
||
/* it should be bcachefs */
|
||
const char* fs = fs_map[0];
|
||
if (strcmp(fs, "bcachefs") != 0)
|
||
continue;
|
||
|
||
/* keep the longest (innermost) match */
|
||
if (mp_len > best_len) {
|
||
best_len = mp_len;
|
||
unescape_mount(fs_map[1]);
|
||
pathcpy(device_list, sizeof(device_list), fs_map[1]);
|
||
}
|
||
}
|
||
|
||
fclose(fd);
|
||
|
||
if (best_len == 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* split the device list */
|
||
char* dev_map[64];
|
||
unsigned dev_mac = strsplit(dev_map, 64, device_list, ":");
|
||
for (unsigned i = 0; i < dev_mac; ++i) {
|
||
/* get major:minor, use stat on the path returned */
|
||
struct stat st;
|
||
if (stat(dev_map[i], &st) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_error(errno, "Failed stat %s. %s.", dev_map[i], strerror(errno));
|
||
goto bail;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (!S_ISBLK(st.st_mode)) {
|
||
/* LCOV_EXCL_START */
|
||
log_error(errno, "Device %s is not a block device.", dev_map[i]);
|
||
goto bail;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
|
||
dev->device = st.st_rdev;
|
||
tommy_list_insert_tail(devlist, &dev->node, dev);
|
||
|
||
log_tag("dereference:bcachefs:%s:%u:%u:%u:%u\n", dir, major(device), minor(device), major(dev->device), minor(dev->device));
|
||
}
|
||
|
||
/* something has to be found */
|
||
if (tommy_list_empty(devlist))
|
||
goto bail;
|
||
|
||
return 0;
|
||
|
||
bail:
|
||
tommy_list_foreach(devlist, free);
|
||
tommy_list_init(devlist);
|
||
return -1;
|
||
}
|
||
|
||
static int extract_zfs(const char* dir, char* dataset, size_t dataset_size, char* uuid, size_t uuid_size, char* mount_point, size_t mount_point_size)
|
||
{
|
||
char resolved[PATH_MAX];
|
||
|
||
const char* zfs = find_zfs();
|
||
if (!zfs) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (realpath(dir, resolved) == 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* list all ZFS filesystems in one shot */
|
||
const char* argv[] = {
|
||
zfs,
|
||
"list",
|
||
"-H",
|
||
"-o",
|
||
"name,guid,mountpoint",
|
||
"-t",
|
||
"filesystem",
|
||
0
|
||
};
|
||
|
||
OS_FILE* fp = os_popen(argv);
|
||
if (!fp) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
size_t best_len = 0;
|
||
while (1) {
|
||
char buf[PATH_MAX * 2 + 64];
|
||
char* map[3];
|
||
unsigned mac;
|
||
|
||
char* s = os_fgets(buf, sizeof(buf), fp);
|
||
if (s == 0)
|
||
break;
|
||
|
||
/* split the line */
|
||
mac = strsplit(map, 3, s, "\t\n");
|
||
|
||
if (mac < 3)
|
||
continue;
|
||
|
||
const char* mp = map[2];
|
||
size_t mp_len = strlen(mp);
|
||
if (strncmp(resolved, mp, mp_len) != 0)
|
||
continue;
|
||
if (mp_len > 1 && resolved[mp_len] != '/' && resolved[mp_len] != 0)
|
||
continue;
|
||
|
||
/* keep the longest (innermost) match */
|
||
if (mp_len > best_len) {
|
||
best_len = mp_len;
|
||
if (dataset)
|
||
pathcpy(dataset, dataset_size, map[0]);
|
||
if (uuid)
|
||
pathcpy(uuid, uuid_size, map[1]);
|
||
if (mount_point)
|
||
pathcpy(mount_point, mount_point_size, map[2]);
|
||
}
|
||
}
|
||
|
||
os_pclose(fp);
|
||
|
||
if (best_len == 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int devdereference_zfs(uint64_t device, const char* dir, tommy_list* devlist)
|
||
{
|
||
char pool[PATH_MAX];
|
||
|
||
const char* zpool = find_zpool();
|
||
if (!zpool) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (extract_zfs(dir, pool, sizeof(pool), 0, 0, 0, 0) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* extract pool name */
|
||
char* slash = strchr(pool, '/');
|
||
if (slash)
|
||
*slash = 0;
|
||
|
||
const char* argv[] = {
|
||
zpool,
|
||
"status",
|
||
"-P",
|
||
pool,
|
||
0
|
||
};
|
||
|
||
OS_FILE* fp = os_popen(argv);
|
||
if (!fp) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
while (1) {
|
||
char buf[PATH_MAX * 2 + 64];
|
||
|
||
char* s = os_fgets(buf, sizeof(buf), fp);
|
||
if (s == 0)
|
||
break;
|
||
|
||
char* file = strstr(s, "/dev/");
|
||
if (!file)
|
||
continue;
|
||
|
||
size_t i = 0;
|
||
while (file[i] != 0 && file[i] != ' ' && file[i] != '\t' && file[i] != '\n')
|
||
++i;
|
||
file[i] = 0;
|
||
|
||
struct stat st;
|
||
if (stat(file, &st) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_error(errno, "Failed stat %s. %s.", file, strerror(errno));
|
||
os_pclose(fp);
|
||
goto bail;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (!S_ISBLK(st.st_mode)) {
|
||
/* LCOV_EXCL_START */
|
||
log_error(errno, "Device %s is not a block device.", file);
|
||
os_pclose(fp);
|
||
goto bail;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
|
||
dev->device = st.st_rdev;
|
||
tommy_list_insert_tail(devlist, &dev->node, dev);
|
||
|
||
log_tag("dereference:zfs:%s:%u:%u:%u:%u\n", dir, major(device), minor(device), major(dev->device), minor(dev->device));
|
||
}
|
||
|
||
os_pclose(fp);
|
||
|
||
if (tommy_list_empty(devlist))
|
||
goto bail;
|
||
|
||
return 0;
|
||
|
||
bail:
|
||
tommy_list_foreach(devlist, free);
|
||
tommy_list_init(devlist);
|
||
return -1;
|
||
}
|
||
|
||
/**
|
||
* Get the devices of a virtual device.
|
||
*
|
||
* This is intended to resolve the case of Btrfs/ZFS 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
|
||
*/
|
||
static int devdereference(uint64_t device, const char* dir, tommy_list* devlist)
|
||
{
|
||
struct statfs sfs;
|
||
int fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||
if (fd < 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (fstatfs(fd, &sfs) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
close(fd);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
int ret;
|
||
if (statfs_type(&sfs) == BTRFS_SUPER_MAGIC) {
|
||
ret = devdereference_btrfs(device, dir, fd, devlist);
|
||
} else if (statfs_type(&sfs) == BCACHEFS_SUPER_MAGIC) {
|
||
ret = devdereference_bcachefs(device, dir, devlist);
|
||
} else if (statfs_type(&sfs) == ZFS_SUPER_MAGIC) {
|
||
ret = devdereference_zfs(device, dir, devlist);
|
||
} else {
|
||
/* insert the device itself in the list */
|
||
struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
|
||
dev->device = device;
|
||
tommy_list_insert_tail(devlist, &dev->node, dev);
|
||
ret = 0;
|
||
}
|
||
|
||
close(fd);
|
||
return ret;
|
||
}
|
||
#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)
|
||
{
|
||
ssize_t 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
|
||
|
||
|
||
/****************************************************************************/
|
||
/* uuid */
|
||
|
||
/**
|
||
* Get the filesystem UUID using the /dev/disk/by-uuid/ links.
|
||
* It doesn't require root permission, and the UUIDs are always updated.
|
||
*
|
||
* NOTE: This method reads the filesystem-level UUID (unlike /dev/disk/by-partuuid/
|
||
* which reads the GPT partition table slot).
|
||
*
|
||
* This method WILL reliably work for standard single-device filesystems:
|
||
* - ext2 / ext3 / ext4
|
||
* - XFS
|
||
* - F2FS (Flash-Friendly File System)
|
||
* - NTFS
|
||
* - FAT32 / exFAT (via their volume serial number formatted as a UUID string)
|
||
*
|
||
* This method will NOT find an entry for:
|
||
* - Network filesystems (NFS, SMB/CIFS, CephFS)
|
||
* - Virtual/In-memory filesystems (tmpfs, proc, sysfs)
|
||
* - ZFS (bypasses the Linux block layer and uses internal GUIDs)
|
||
* - Raw flash filesystems (JFFS2, UBIFS)
|
||
*
|
||
* For multi-device filesystems like Btrfs and Bcachefs, all member devices
|
||
* share the same filesystem UUID. Because /dev/disk/by-uuid/ cannot have duplicate
|
||
* filename entries, only ONE arbitrary device from the pool will be present here.
|
||
*/
|
||
#if HAVE_LINUX_DEVICE
|
||
static int devuuid_dev(uint64_t device, char* uuid, size_t uuid_size)
|
||
{
|
||
ssize_t 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) {
|
||
/* LCOV_EXCL_START */
|
||
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;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
int dir_fd = dirfd(d);
|
||
if (dir_fd == -1) {
|
||
/* LCOV_EXCL_START */
|
||
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;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
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) {
|
||
/* LCOV_EXCL_START */
|
||
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;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* 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) {
|
||
/* LCOV_EXCL_START */
|
||
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;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
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;
|
||
}
|
||
|
||
static int hexval(char c)
|
||
{
|
||
if (c >= '0' && c <= '9')
|
||
return c - '0';
|
||
if (c >= 'a' && c <= 'f')
|
||
return c - 'a' + 10;
|
||
if (c >= 'A' && c <= 'F')
|
||
return c - 'A' + 10;
|
||
return -1;
|
||
}
|
||
|
||
/**
|
||
* Decode udev \xNN hex escapes into raw string.
|
||
* Return 0 on success, -1 if truncated.
|
||
*/
|
||
static int udev_decode(char* dst, size_t dst_size, const char* src)
|
||
{
|
||
size_t i = 0;
|
||
while (*src != 0) {
|
||
if (i + 1 >= dst_size)
|
||
return -1;
|
||
if (src[0] == '\\' && src[1] == 'x' && src[2] != 0 && src[3] != 0) {
|
||
int h1 = hexval(src[2]);
|
||
int h2 = hexval(src[3]);
|
||
if (h1 >= 0 && h2 >= 0) {
|
||
dst[i++] = (char)((h1 << 4) | h2);
|
||
src += 4;
|
||
continue;
|
||
}
|
||
}
|
||
dst[i++] = *src++;
|
||
}
|
||
dst[i] = 0;
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Get the filesystem LABEL using the /dev/disk/by-label/ links.
|
||
*/
|
||
static int devlabel_dev(uint64_t device, char* label, size_t label_size)
|
||
{
|
||
ssize_t ret;
|
||
DIR* d;
|
||
struct dirent* dd;
|
||
struct stat st;
|
||
|
||
/* scan the LABEL directory searching for the device */
|
||
d = opendir("/dev/disk/by-label");
|
||
if (!d) {
|
||
/* LCOV_EXCL_START */
|
||
log_tag("label:by-label:%u:%u: opendir(/dev/disk/by-label) failed, %s\n", major(device), minor(device), strerror(errno));
|
||
/* directory missing?, likely we are not in Linux */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
int dir_fd = dirfd(d);
|
||
if (dir_fd == -1) {
|
||
/* LCOV_EXCL_START */
|
||
log_tag("label:by-label:%u:%u: dirfd(/dev/disk/by-label) failed, %s\n", major(device), minor(device), strerror(errno));
|
||
closedir(d);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
while ((dd = readdir(d)) != 0) {
|
||
/* skip "." and ".." files */
|
||
if (strcmp(dd->d_name, ".") == 0 || strcmp(dd->d_name, "..") == 0)
|
||
continue;
|
||
|
||
ret = fstatat(dir_fd, dd->d_name, &st, 0);
|
||
if (ret != 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_tag("label:by-label:%u:%u: fstatat(%s) failed, %s\n", major(device), minor(device), dd->d_name, strerror(errno));
|
||
/* generic error, ignore and continue the search */
|
||
continue;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* if it matches, we have the label */
|
||
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-label/%s", dd->d_name);
|
||
ret = readlink(path, buf, sizeof(buf));
|
||
if (ret < 0 || ret >= PATH_MAX) {
|
||
/* LCOV_EXCL_START */
|
||
log_tag("label:by-label:%u:%u: readlink(/dev/disk/by-label/%s) failed, %s\n", major(device), minor(device), dd->d_name, strerror(errno));
|
||
/* generic error, ignore and continue the search */
|
||
continue;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
buf[ret] = 0;
|
||
|
||
/* found */
|
||
if (udev_decode(label, label_size, dd->d_name) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
closedir(d);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
log_tag("label:by-label:%u:%u:%s: found %s\n", major(device), minor(device), label, buf);
|
||
|
||
closedir(d);
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
log_tag("label:by-label:%u:%u: /dev/disk/by-label doesn't contain a matching block device\n", major(device), minor(device));
|
||
|
||
/* not found */
|
||
closedir(d);
|
||
return -1;
|
||
}
|
||
#endif
|
||
|
||
/**
|
||
* Get the LABEL using the generic Linux FS_IOC_GETFSLABEL ioctl.
|
||
*/
|
||
#if HAVE_LINUX_DEVICE
|
||
#ifndef FSLABEL_MAX
|
||
#define FSLABEL_MAX 256
|
||
#endif
|
||
|
||
#ifndef FS_IOC_GETFSLABEL
|
||
#define FS_IOC_GETFSLABEL _IOR(0x94, 49, char[FSLABEL_MAX])
|
||
#endif
|
||
|
||
static int devlabel_ioctl(const char* path, char* label, size_t label_size)
|
||
{
|
||
char label_buf[FSLABEL_MAX];
|
||
|
||
int fd = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||
if (fd < 0 && (errno == ENOENT || errno == ENOTDIR)) {
|
||
char dir[PATH_MAX];
|
||
char* slash;
|
||
pathcpy(dir, sizeof(dir), path);
|
||
slash = strrchr(dir, '/');
|
||
if (slash) {
|
||
if (slash == dir)
|
||
slash[1] = 0;
|
||
else
|
||
*slash = 0;
|
||
fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||
}
|
||
}
|
||
if (fd < 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_tag("label:ioctl: open(%s) failed, %s\n", path, strerror(errno));
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
memset(label_buf, 0, sizeof(label_buf));
|
||
if (ioctl(fd, FS_IOC_GETFSLABEL, label_buf) < 0) {
|
||
log_tag("label:ioctl: FS_IOC_GETFSLABEL(%s) failed, %s\n", path, strerror(errno));
|
||
close(fd);
|
||
return -1;
|
||
}
|
||
|
||
close(fd);
|
||
|
||
if (label_buf[0] == 0) {
|
||
log_tag("label:ioctl:%s: empty label\n", path);
|
||
return -1;
|
||
}
|
||
|
||
if (strlen(label_buf) + 1 > label_size) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
strcpy(label, label_buf);
|
||
|
||
log_tag("label:ioctl:%s: found %s\n", path, label);
|
||
|
||
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
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int devuuid_btrfs(uint64_t device, const char* dir, char* uuid, size_t uuid_size)
|
||
{
|
||
struct statfs sfs;
|
||
|
||
int fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||
if (fd < 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP*/
|
||
}
|
||
|
||
if (fstatfs(fd, &sfs) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
close(fd);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (statfs_type(&sfs) != BTRFS_SUPER_MAGIC) {
|
||
close(fd);
|
||
return -1;
|
||
}
|
||
|
||
struct btrfs_ioctl_fs_info_args info;
|
||
memset(&info, 0, sizeof(info));
|
||
if (ioctl(fd, BTRFS_IOC_FS_INFO, &info) < 0) {
|
||
/* LCOV_EXCL_START */
|
||
close(fd);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
snprintf(uuid, uuid_size,
|
||
"%02x%02x%02x%02x-"
|
||
"%02x%02x-"
|
||
"%02x%02x-"
|
||
"%02x%02x-"
|
||
"%02x%02x%02x%02x%02x%02x",
|
||
info.fsid[0], info.fsid[1], info.fsid[2], info.fsid[3],
|
||
info.fsid[4], info.fsid[5],
|
||
info.fsid[6], info.fsid[7],
|
||
info.fsid[8], info.fsid[9],
|
||
info.fsid[10], info.fsid[11], info.fsid[12], info.fsid[13], info.fsid[14], info.fsid[15]
|
||
);
|
||
|
||
log_tag("uuid:by-btrfs:%u:%u:%s: found %s\n", major(device), minor(device), uuid, dir);
|
||
|
||
close(fd);
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
struct bch_ioctl_query_uuid {
|
||
__u8 uuid[16];
|
||
};
|
||
|
||
#define BCH_IOCTL_QUERY_UUID _IOR(0xbc, 1, struct bch_ioctl_query_uuid)
|
||
|
||
static int devuuid_bcachefs(uint64_t device, const char* dir, char* uuid, size_t uuid_size)
|
||
{
|
||
struct statfs sfs;
|
||
|
||
int fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||
if (fd < 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP*/
|
||
}
|
||
|
||
if (fstatfs(fd, &sfs) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
close(fd);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (statfs_type(&sfs) != BCACHEFS_SUPER_MAGIC) {
|
||
close(fd);
|
||
return -1;
|
||
}
|
||
|
||
struct bch_ioctl_query_uuid info;
|
||
memset(&info, 0, sizeof(info));
|
||
if (ioctl(fd, BCH_IOCTL_QUERY_UUID, &info) < 0) {
|
||
/* LCOV_EXCL_START */
|
||
close(fd);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
close(fd);
|
||
|
||
snprintf(uuid, uuid_size,
|
||
"%02x%02x%02x%02x-"
|
||
"%02x%02x-"
|
||
"%02x%02x-"
|
||
"%02x%02x-"
|
||
"%02x%02x%02x%02x%02x%02x",
|
||
info.uuid[0], info.uuid[1], info.uuid[2], info.uuid[3],
|
||
info.uuid[4], info.uuid[5],
|
||
info.uuid[6], info.uuid[7],
|
||
info.uuid[8], info.uuid[9],
|
||
info.uuid[10], info.uuid[11], info.uuid[12], info.uuid[13], info.uuid[14], info.uuid[15]
|
||
);
|
||
|
||
log_tag("uuid:by-bcachefs:%u:%u:%s: found %s\n", major(device), minor(device), uuid, dir);
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
int devuuid_zfs(uint64_t device, const char* dir, char* uuid, size_t uuid_size)
|
||
{
|
||
if (extract_zfs(dir, 0, 0, uuid, uuid_size, 0, 0) != 0)
|
||
return -1;
|
||
|
||
log_tag("uuid:by-zfs:%u:%u:%s: found %s\n", major(device), minor(device), uuid, dir);
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
int devuuid(uint64_t device_id, const char* device_path, char* uuid, size_t uuid_size)
|
||
{
|
||
(void)device_path;
|
||
(void)device_id;
|
||
|
||
#ifdef __APPLE__
|
||
if (devuuid_darwin(device_path, uuid, uuid_size) == 0)
|
||
return 0;
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
if (devuuid_btrfs(device_id, device_path, uuid, uuid_size) == 0)
|
||
return 0;
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
if (devuuid_bcachefs(device_id, device_path, uuid, uuid_size) == 0)
|
||
return 0;
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
if (devuuid_zfs(device_id, device_path, uuid, uuid_size) == 0)
|
||
return 0;
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
if (devuuid_dev(device_id, uuid, uuid_size) == 0)
|
||
return 0;
|
||
#else
|
||
log_tag("uuid:by-uuid:%u:%u: by-uuid not supported\n", major(device_id), minor(device_id));
|
||
#endif
|
||
|
||
|
||
log_tag("uuid:notfound:%u:%u:\n", major(device_id), minor(device_id));
|
||
|
||
/* not supported */
|
||
(void)uuid;
|
||
(void)uuid_size;
|
||
return -1;
|
||
}
|
||
|
||
/****************************************************************************/
|
||
/* fs */
|
||
|
||
/* 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 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 BTRFS_SUPER_MAGIC 0x9123683E
|
||
#define BCACHEFS_SUPER_MAGIC 0xCA451A4E
|
||
#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 },
|
||
{ BCACHEFS_SUPER_MAGIC, "bcachefs", 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) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/*
|
||
* If it doesn't exist, we assume a file
|
||
* and we check for the containing dir
|
||
*/
|
||
if (strlen(path) + 1 > sizeof(dir)) {
|
||
/* LCOV_EXCL_START */
|
||
errno = ENAMETOOLONG;
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
strcpy(dir, path);
|
||
|
||
slash = strrchr(dir, '/');
|
||
if (!slash) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
*slash = 0;
|
||
if (statfs(dir, &st) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
}
|
||
#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 (statfs_type(&st)) {
|
||
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 (statfs_type(&st)) {
|
||
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 (statfs_type(&st) == 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_LINUX_DEVICE
|
||
if (devlabel_ioctl(path, fslabel, fslabel_size) != 0) {
|
||
struct stat fst;
|
||
if (stat(path, &fst) == 0)
|
||
devlabel_dev(fst.st_dev, fslabel, fslabel_size);
|
||
}
|
||
#else
|
||
(void)fslabel_size;
|
||
#endif
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/****************************************************************************/
|
||
/* identity */
|
||
|
||
#if HAVE_STATX
|
||
|
||
#ifndef STATX_MNT_ID
|
||
#define STATX_MNT_ID 0x00001000U
|
||
#endif
|
||
#ifndef STATX_SUBVOL
|
||
#define STATX_SUBVOL 0x00008000U
|
||
#endif
|
||
|
||
struct snapraid_statx_timestamp_abi {
|
||
int64_t tv_sec;
|
||
uint32_t tv_nsec;
|
||
int32_t reserved;
|
||
};
|
||
|
||
struct snapraid_statx_abi {
|
||
/* 0x00 */
|
||
uint32_t stx_mask;
|
||
uint32_t stx_blksize;
|
||
uint64_t stx_attributes;
|
||
|
||
/* 0x10 */
|
||
uint32_t stx_nlink;
|
||
uint32_t stx_uid;
|
||
uint32_t stx_gid;
|
||
uint16_t stx_mode;
|
||
uint16_t spare0;
|
||
|
||
/* 0x20 */
|
||
uint64_t stx_ino;
|
||
uint64_t stx_size;
|
||
uint64_t stx_blocks;
|
||
uint64_t stx_attributes_mask;
|
||
|
||
/* 0x40 */
|
||
struct snapraid_statx_timestamp_abi stx_atime;
|
||
struct snapraid_statx_timestamp_abi stx_btime;
|
||
struct snapraid_statx_timestamp_abi stx_ctime;
|
||
struct snapraid_statx_timestamp_abi stx_mtime;
|
||
|
||
/* 0x80 */
|
||
uint32_t stx_rdev_major;
|
||
uint32_t stx_rdev_minor;
|
||
uint32_t stx_dev_major;
|
||
uint32_t stx_dev_minor;
|
||
|
||
/* 0x90 */
|
||
uint64_t stx_mnt_id;
|
||
uint32_t stx_dio_mem_align;
|
||
uint32_t stx_dio_offset_align;
|
||
|
||
/* 0xa0 */
|
||
uint64_t stx_subvol;
|
||
|
||
/* ABI continues to 0x100. */
|
||
unsigned char tail[0x100 - 0xa8];
|
||
};
|
||
|
||
STATIC_ASSERT(statx_abi_size, sizeof(struct snapraid_statx_abi) == 0x100);
|
||
STATIC_ASSERT(statx_abi_mnt_id, offsetof(struct snapraid_statx_abi, stx_mnt_id) == 0x90);
|
||
STATIC_ASSERT(statx_abi_subvol, offsetof(struct snapraid_statx_abi, stx_subvol) == 0xa0);
|
||
#endif
|
||
|
||
int fsidentity(const char* path, const struct stat* st, struct fsidentity* identity)
|
||
{
|
||
(void)path;
|
||
|
||
identity->device = st->st_dev;
|
||
identity->mnt_id = 0;
|
||
identity->subvol = 0;
|
||
|
||
#if HAVE_STATX
|
||
struct statx stx;
|
||
|
||
/* query mount ID and subvolume ID without following symlinks */
|
||
if (statx(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, STATX_MNT_ID | STATX_SUBVOL, &stx) != 0) {
|
||
/*
|
||
* Fall back if statx is unsupported by the kernel (ENOSYS) or blocked
|
||
* by a seccomp/security sandbox filter (EPERM/EACCES in older Docker/OCI).
|
||
*/
|
||
if (errno == ENOSYS || errno == EPERM || errno == EACCES)
|
||
return 0;
|
||
return -1;
|
||
}
|
||
|
||
/* copy to fixed ABI representation to support libc headers lacking named fields */
|
||
struct snapraid_statx_abi abi;
|
||
memcpy(&abi, &stx, sizeof(abi));
|
||
|
||
if ((abi.stx_mask & STATX_MNT_ID) != 0)
|
||
identity->mnt_id = abi.stx_mnt_id;
|
||
if ((abi.stx_mask & STATX_SUBVOL) != 0)
|
||
identity->subvol = abi.stx_subvol;
|
||
#endif
|
||
|
||
return 0;
|
||
}
|
||
|
||
/****************************************************************************/
|
||
/* snapshot */
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int fssnapshot_inode(const char* path, uint32_t magic, uint64_t root_inode, struct fssnapshot_struct* fss)
|
||
{
|
||
char resolved[PATH_MAX];
|
||
char current_path[PATH_MAX];
|
||
|
||
if (realpath(path, resolved) == 0)
|
||
return -1;
|
||
|
||
pathslash(resolved, sizeof(resolved));
|
||
pathcpy(current_path, sizeof(current_path), resolved);
|
||
|
||
/* walk up the directory tree to find the subvolume root (Inode 256) */
|
||
while (1) {
|
||
struct stat st;
|
||
if (stat(current_path, &st) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_error(errno, "Error stating '%s'. %s.\n", current_path, strerror(errno));
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (st.st_ino == root_inode) {
|
||
/* copy the subvol root */
|
||
pathcpy(fss->root_dir, sizeof(fss->root_dir), current_path);
|
||
pathcpy(fss->snapshot_dir, sizeof(fss->snapshot_dir), current_path);
|
||
pathcat(fss->snapshot_dir, sizeof(fss->snapshot_dir), SNAPSHOT_CONTAINER "/");
|
||
|
||
/* create the snapshot container directory if not existing */
|
||
int ret = mkdir(fss->snapshot_dir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
|
||
if (ret != 0 && errno != EEXIST) {
|
||
log_error(errno, "Error creating '%s'. %s.\n", fss->snapshot_dir, strerror(errno));
|
||
return -1;
|
||
}
|
||
|
||
size_t root_len = strlen(fss->root_dir);
|
||
|
||
if (pathncmp(fss->root_dir, resolved, root_len) != 0) {
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
|
||
pathcpy(fss->sub_dir, sizeof(fss->sub_dir), resolved + root_len);
|
||
|
||
fss->magic = magic;
|
||
return 0;
|
||
}
|
||
|
||
/* move to parent directory */
|
||
pathup(current_path);
|
||
|
||
if (current_path[0] == 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_error(ENOENT, "No subvolume root found for '%s'. %s.\n", path, strerror(errno));
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int fssnapshot_zfs(const char* dir, uint32_t magic, struct fssnapshot_struct* fss)
|
||
{
|
||
char resolved[PATH_MAX];
|
||
char root[PATH_MAX];
|
||
|
||
if (realpath(dir, resolved) == 0)
|
||
return -1;
|
||
|
||
if (extract_zfs(dir, fss->dataset, sizeof(fss->dataset), 0, 0, fss->root_dir, sizeof(fss->root_dir)) != 0)
|
||
return -1;
|
||
|
||
if (realpath(fss->root_dir, root) == 0)
|
||
return -1;
|
||
|
||
pathslash(resolved, sizeof(resolved));
|
||
pathslash(root, sizeof(root));
|
||
|
||
size_t root_len = strlen(root);
|
||
if (pathncmp(root, resolved, root_len) != 0) {
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
|
||
pathcpy(fss->sub_dir, sizeof(fss->sub_dir), resolved + root_len);
|
||
pathcpy(fss->root_dir, sizeof(fss->root_dir), root);
|
||
|
||
pathcpy(fss->snapshot_dir, sizeof(fss->snapshot_dir), fss->root_dir);
|
||
pathcat(fss->snapshot_dir, sizeof(fss->snapshot_dir), ".zfs/snapshot/");
|
||
|
||
/* ensure ZFS snapshot directory is visible */
|
||
if (eaccess(fss->snapshot_dir, R_OK | X_OK) != 0) {
|
||
log_error(errno, "ZFS snapshot directory not accessible: '%s'. %s.\n", fss->snapshot_dir, strerror(errno));
|
||
return -1;
|
||
}
|
||
|
||
fss->magic = magic;
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
int fssnapshot_mount(const char* path, struct fssnapshot_struct* fss)
|
||
{
|
||
#if HAVE_LINUX_DEVICE
|
||
struct statfs sfs;
|
||
|
||
if (statfs(path, &sfs) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_error(errno, "Error stating filesystem '%s'. %s.\n", path, strerror(errno));
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
assert(fss != 0); /* silence the clang static analyzer (it's never called with a NULL fss) */
|
||
|
||
if (statfs_type(&sfs) == BTRFS_SUPER_MAGIC) {
|
||
/* btrfs reserved inode 256 for subvolume roots */
|
||
return fssnapshot_inode(path, BTRFS_SUPER_MAGIC, 256, fss);
|
||
} else if (statfs_type(&sfs) == BCACHEFS_SUPER_MAGIC) {
|
||
/* Bcachefs reserves inode 4096 for each subvolume's root directory */
|
||
return fssnapshot_inode(path, BCACHEFS_SUPER_MAGIC, 4096, fss);
|
||
} else if (statfs_type(&sfs) == ZFS_SUPER_MAGIC) {
|
||
return fssnapshot_zfs(path, ZFS_SUPER_MAGIC, fss);
|
||
} else {
|
||
return 1;
|
||
}
|
||
#else
|
||
(void)path;
|
||
(void)fss;
|
||
return 1;
|
||
#endif
|
||
}
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int fssnapshot_stat_fs(const struct fssnapshot_struct* fss, const char* name, struct stat* st)
|
||
{
|
||
char target_path[PATH_MAX];
|
||
|
||
pathcpy(target_path, sizeof(target_path), fss->snapshot_dir);
|
||
pathcat(target_path, sizeof(target_path), name);
|
||
|
||
if (stat(target_path, st) != 0)
|
||
return -1;
|
||
|
||
/* it must be a directory */
|
||
if (!S_ISDIR(st->st_mode))
|
||
return -1;
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
/*
|
||
* Native ZFS snapshots use a SnapRAID-specific prefix to avoid
|
||
* deleting or renaming unrelated user snapshots with generic
|
||
* names such as @scan, @pending, or @stable.
|
||
*/
|
||
#define ZFS_SNAPSHOT_PREFIX "snapraid-"
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int fssnapshot_stat_zfs(const struct fssnapshot_struct* fss, const char* name, struct stat* st)
|
||
{
|
||
char native_name[PATH_MAX];
|
||
char target_path[PATH_MAX];
|
||
|
||
pathcpy(native_name, sizeof(native_name), ZFS_SNAPSHOT_PREFIX);
|
||
pathcat(native_name, sizeof(native_name), name);
|
||
|
||
pathcpy(target_path, sizeof(target_path), fss->snapshot_dir);
|
||
pathcat(target_path, sizeof(target_path), native_name);
|
||
|
||
/*
|
||
* Ensure to get into into the snapshot
|
||
*
|
||
* This is required by ZFS, otherwise we get a different st_dev
|
||
*/
|
||
pathcat(target_path, sizeof(target_path), "/.");
|
||
|
||
if (stat(target_path, st) != 0)
|
||
return -1;
|
||
|
||
/* it must be a directory */
|
||
if (!S_ISDIR(st->st_mode))
|
||
return -1;
|
||
|
||
/**
|
||
* When verifying if a ZFS snapshot has been successfully deleted, a direct
|
||
* path lookup (stat, access, open) is unreliable. A directory listing
|
||
* (readdir) of the .zfs/snapshot directory must be used instead.
|
||
*
|
||
* On Linux, the Virtual File System (VFS) maintains a 'dentry cache'
|
||
* (dcache). If a snapshot path was accessed recently, the kernel caches
|
||
* the directory entry in RAM. When ZFS destroys the snapshot, the kernel
|
||
* dcache is not always immediately synchronized. Consequently, stat()
|
||
* may hit the stale cache and report the snapshot still exists.
|
||
*
|
||
* Unlike stat(), a directory listing (ls/readdir) forces the kernel to
|
||
* query the ZFS module for the current set of valid entries, bypassing
|
||
* the stale dcache for a "ground truth" check.
|
||
*
|
||
* This behavior can be confirmed by forcing the kernel to purge its
|
||
* dcache manually using the following command:
|
||
* sync; echo 2 > /proc/sys/vm/drop_caches
|
||
*
|
||
* After dropping caches, stat() will correctly return ENOENT. To avoid
|
||
* requiring root privileges or impacting system performance with
|
||
* drop_caches, always use readdir() for existence checks in this context.
|
||
*/
|
||
DIR* d = opendir(fss->snapshot_dir);
|
||
if (!d) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
while (1) {
|
||
struct dirent* entry = readdir(d);
|
||
if (!entry)
|
||
break;
|
||
|
||
if (strcmp(entry->d_name, native_name) == 0) {
|
||
closedir(d);
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
closedir(d);
|
||
return -1;
|
||
}
|
||
#endif
|
||
|
||
int fssnapshot_stat(struct fssnapshot_struct* fss, const char* name, struct stat* st)
|
||
{
|
||
#if HAVE_LINUX_DEVICE
|
||
if (fss->magic == ZFS_SUPER_MAGIC) {
|
||
return fssnapshot_stat_zfs(fss, name, st);
|
||
} else {
|
||
return fssnapshot_stat_fs(fss, name, st);
|
||
}
|
||
#else
|
||
(void)fss;
|
||
(void)name;
|
||
(void)st;
|
||
return 0;
|
||
#endif
|
||
}
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
int fssnapshot_btrfs_create(const struct fssnapshot_struct* fss, const char* name)
|
||
{
|
||
struct btrfs_ioctl_vol_args_v2 args;
|
||
int fd_source;
|
||
int fd_dest_parent;
|
||
int ret;
|
||
|
||
/* open the source subvolume to get a file descriptor */
|
||
fd_source = open(fss->root_dir, O_RDONLY | O_DIRECTORY);
|
||
if (fd_source < 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
fd_dest_parent = open(fss->snapshot_dir, O_RDONLY | O_DIRECTORY);
|
||
if (fd_dest_parent < 0) {
|
||
/* LCOV_EXCL_START */
|
||
close(fd_source);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
memset(&args, 0, sizeof(args));
|
||
args.fd = fd_source;
|
||
args.flags = BTRFS_SUBVOL_RDONLY;
|
||
pathcpy(args.name, BTRFS_SUBVOL_NAME_MAX, name);
|
||
|
||
/* issue the snapshot command to the PARENT directory of the destination */
|
||
ret = ioctl(fd_dest_parent, BTRFS_IOC_SNAP_CREATE_V2, &args);
|
||
if (ret < 0) {
|
||
/* LCOV_EXCL_START */
|
||
close(fd_source);
|
||
close(fd_dest_parent);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
close(fd_source);
|
||
close(fd_dest_parent);
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int fssnapshot_bcachefs_create(const struct fssnapshot_struct* fss, const char* name)
|
||
{
|
||
char target_path[PATH_MAX];
|
||
int ret;
|
||
|
||
const char* bcachefs = find_bcachefs();
|
||
if (!bcachefs) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
pathcpy(target_path, sizeof(target_path), fss->snapshot_dir);
|
||
pathcat(target_path, sizeof(target_path), name);
|
||
|
||
if (os_validate_exec_input(fss->root_dir) != 0 || os_validate_exec_input(target_path) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
const char* argv[] = {
|
||
bcachefs,
|
||
"subvolume",
|
||
"snapshot",
|
||
"-r",
|
||
fss->root_dir,
|
||
target_path,
|
||
0
|
||
};
|
||
|
||
ret = os_spawn_and_wait(argv);
|
||
if (ret != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int fssnapshot_zfs_create(const struct fssnapshot_struct* fss, const char* name)
|
||
{
|
||
char snapshot[PATH_MAX + 64];
|
||
int ret;
|
||
|
||
const char* zfs = find_zfs();
|
||
if (!zfs) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
snprintf(snapshot, sizeof(snapshot), "%s@" ZFS_SNAPSHOT_PREFIX "%s", fss->dataset, name);
|
||
|
||
if (os_validate_exec_input(snapshot) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
const char* argv[] = {
|
||
zfs,
|
||
"snapshot",
|
||
snapshot,
|
||
0
|
||
};
|
||
|
||
ret = os_spawn_and_wait(argv);
|
||
if (ret != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
int fssnapshot_create(const struct fssnapshot_struct* fss, const char* name)
|
||
{
|
||
#if HAVE_LINUX_DEVICE
|
||
if (fss->magic == BTRFS_SUPER_MAGIC) {
|
||
return fssnapshot_btrfs_create(fss, name);
|
||
} else if (fss->magic == BCACHEFS_SUPER_MAGIC) {
|
||
return fssnapshot_bcachefs_create(fss, name);
|
||
} else if (fss->magic == ZFS_SUPER_MAGIC) {
|
||
return fssnapshot_zfs_create(fss, name);
|
||
} else {
|
||
return -1;
|
||
}
|
||
#else
|
||
(void)fss;
|
||
(void)name;
|
||
return -1;
|
||
#endif
|
||
}
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
int fssnapshot_btrfs_delete(const struct fssnapshot_struct* fss, const char* name)
|
||
{
|
||
struct btrfs_ioctl_vol_args args;
|
||
int fd_parent;
|
||
int ret;
|
||
|
||
fd_parent = open(fss->snapshot_dir, O_RDONLY | O_DIRECTORY);
|
||
if (fd_parent < 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
memset(&args, 0, sizeof(args));
|
||
pathcpy(args.name, BTRFS_PATH_NAME_MAX, name);
|
||
|
||
ret = ioctl(fd_parent, BTRFS_IOC_SNAP_DESTROY, &args);
|
||
if (ret < 0) {
|
||
/* LCOV_EXCL_START */
|
||
close(fd_parent);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
close(fd_parent);
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int fssnapshot_bcachefs_delete(const struct fssnapshot_struct* fss, const char* name)
|
||
{
|
||
char target_path[PATH_MAX];
|
||
int ret;
|
||
|
||
const char* bcachefs = find_bcachefs();
|
||
if (!bcachefs) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
pathcpy(target_path, sizeof(target_path), fss->snapshot_dir);
|
||
pathcat(target_path, sizeof(target_path), name);
|
||
|
||
if (os_validate_exec_input(target_path) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
const char* argv[] = {
|
||
bcachefs,
|
||
"subvolume",
|
||
"delete",
|
||
target_path,
|
||
0,
|
||
};
|
||
|
||
ret = os_spawn_and_wait(argv);
|
||
if (ret != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int fssnapshot_zfs_delete(const struct fssnapshot_struct* fss, const char* name)
|
||
{
|
||
char snapshot[PATH_MAX + 64];
|
||
int ret;
|
||
|
||
const char* zfs = find_zfs();
|
||
if (!zfs) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
snprintf(snapshot, sizeof(snapshot), "%s@" ZFS_SNAPSHOT_PREFIX "%s", fss->dataset, name);
|
||
|
||
if (os_validate_exec_input(snapshot) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
const char* argv[] = {
|
||
zfs,
|
||
"destroy",
|
||
snapshot,
|
||
0
|
||
};
|
||
|
||
ret = os_spawn_and_wait(argv);
|
||
if (ret != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
int fssnapshot_delete(const struct fssnapshot_struct* fss, const char* name)
|
||
{
|
||
#if HAVE_LINUX_DEVICE
|
||
if (fss->magic == BTRFS_SUPER_MAGIC) {
|
||
return fssnapshot_btrfs_delete(fss, name);
|
||
} else if (fss->magic == BCACHEFS_SUPER_MAGIC) {
|
||
return fssnapshot_bcachefs_delete(fss, name);
|
||
} else if (fss->magic == ZFS_SUPER_MAGIC) {
|
||
return fssnapshot_zfs_delete(fss, name);
|
||
} else {
|
||
return -1;
|
||
}
|
||
#else
|
||
(void)fss;
|
||
(void)name;
|
||
return -1;
|
||
#endif
|
||
}
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int fssnapshot_fs_rename(const struct fssnapshot_struct* fss, const char* old_name, const char* new_name)
|
||
{
|
||
char old_path[PATH_MAX];
|
||
char new_path[PATH_MAX];
|
||
|
||
pathcpy(old_path, sizeof(old_path), fss->snapshot_dir);
|
||
pathcat(old_path, sizeof(old_path), old_name);
|
||
pathcpy(new_path, sizeof(new_path), fss->snapshot_dir);
|
||
pathcat(new_path, sizeof(new_path), new_name);
|
||
|
||
if (rename(old_path, new_path) < 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
#if HAVE_LINUX_DEVICE
|
||
static int fssnapshot_zfs_rename(const struct fssnapshot_struct* fss, const char* old_name, const char* new_name)
|
||
{
|
||
char old_snapshot[PATH_MAX + 64];
|
||
char new_snapshot[PATH_MAX + 64];
|
||
int ret;
|
||
|
||
const char* zfs = find_zfs();
|
||
if (!zfs) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
snprintf(old_snapshot, sizeof(old_snapshot), "%s@" ZFS_SNAPSHOT_PREFIX "%s", fss->dataset, old_name);
|
||
snprintf(new_snapshot, sizeof(new_snapshot), "%s@" ZFS_SNAPSHOT_PREFIX "%s", fss->dataset, new_name);
|
||
|
||
if (os_validate_exec_input(old_snapshot) != 0 || os_validate_exec_input(new_snapshot) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
const char* argv[] = {
|
||
zfs,
|
||
"rename",
|
||
old_snapshot,
|
||
new_snapshot,
|
||
0
|
||
};
|
||
|
||
ret = os_spawn_and_wait(argv);
|
||
if (ret != 0) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
int fssnapshot_rename(const struct fssnapshot_struct* fss, const char* old_name, const char* new_name)
|
||
{
|
||
#if HAVE_LINUX_DEVICE
|
||
if (fss->magic == BTRFS_SUPER_MAGIC || fss->magic == BCACHEFS_SUPER_MAGIC) {
|
||
return fssnapshot_fs_rename(fss, old_name, new_name);
|
||
} else if (fss->magic == ZFS_SUPER_MAGIC) {
|
||
return fssnapshot_zfs_rename(fss, old_name, new_name);
|
||
} else {
|
||
return -1;
|
||
}
|
||
#else
|
||
(void)fss;
|
||
(void)old_name;
|
||
(void)new_name;
|
||
return -1;
|
||
#endif
|
||
}
|
||
|
||
int fssnapshot_path(const struct fssnapshot_struct* fss, const char* name, char* path, size_t path_size)
|
||
{
|
||
pathcpy(path, path_size, fss->snapshot_dir);
|
||
if (fss->magic == ZFS_SUPER_MAGIC)
|
||
pathcat(path, path_size, ZFS_SNAPSHOT_PREFIX);
|
||
pathcat(path, path_size, name);
|
||
pathcatc(path, path_size, '/');
|
||
pathcat(path, path_size, fss->sub_dir);
|
||
|
||
return 0;
|
||
}
|
||
|
||
void fssnapshot_unmount(const struct fssnapshot_struct* fss)
|
||
{
|
||
(void)fss;
|
||
}
|
||
|
||
|
||
/****************************************************************************/
|
||
/* dev */
|
||
|
||
/**
|
||
* 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;
|
||
ssize_t 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 = strtou(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 = strtou(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);
|
||
pathcpy(devinfo->smartctl_info, sizeof(devinfo->smartctl_info), parent->smartctl_info);
|
||
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;
|
||
ssize_t 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) {
|
||
/* LCOV_EXCL_START */
|
||
break;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* sum reads and writes completed */
|
||
if (token == 1 || token == 5) {
|
||
*count += v;
|
||
if (token == 5)
|
||
break; /* stop here */
|
||
}
|
||
|
||
++token;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
/**
|
||
* Max number of arguments supported.
|
||
*/
|
||
#define ARGS_MAX 64
|
||
|
||
/**
|
||
* Get SMART attributes.
|
||
*/
|
||
#if HAVE_LINUX_DEVICE
|
||
static int devsmart(dev_t device, const char* name, const char* smartctl, const char* smartctl_info, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* interface)
|
||
{
|
||
char file[PATH_MAX];
|
||
OS_FILE* f;
|
||
int ret;
|
||
const char* x;
|
||
const char* info_opts = smartctl_info[0] ? smartctl_info : "-a";
|
||
|
||
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 */
|
||
}
|
||
|
||
const char* argv[ARGS_MAX];
|
||
unsigned argc = 0;
|
||
argv[argc++] = x;
|
||
|
||
char info_buf[SMART_MAX];
|
||
pathcpy(info_buf, sizeof(info_buf), info_opts);
|
||
argc += argsplit(argv + argc, ARGS_MAX - argc, info_buf);
|
||
if (argc >= ARGS_MAX) {
|
||
/* LCOV_EXCL_START */
|
||
log_fatal(EEXTERNAL, "Too many smartctl arguments.\n");
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
char extra_args[PATH_MAX];
|
||
if (smartctl[0]) {
|
||
pathprint(extra_args, sizeof(extra_args), smartctl, file);
|
||
argc += argsplit(argv + argc, ARGS_MAX - argc, extra_args);
|
||
if (argc >= ARGS_MAX) {
|
||
/* LCOV_EXCL_START */
|
||
log_fatal(EEXTERNAL, "Too many smartctl arguments.\n");
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
log_tag("smartctl:%s:%s:run: %s [info: %s] %s\n", file, name, x, info_opts, extra_args);
|
||
} else {
|
||
if (argc >= ARGS_MAX - 1) {
|
||
/* LCOV_EXCL_START */
|
||
log_fatal(EEXTERNAL, "Too many smartctl arguments.\n");
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
argv[argc++] = file;
|
||
log_tag("smartctl:%s:%s:run: %s [info: %s] %s\n", file, name, x, info_opts, file);
|
||
}
|
||
argv[argc++] = 0;
|
||
|
||
f = os_popen(argv);
|
||
if (!f) {
|
||
/* LCOV_EXCL_START */
|
||
log_tag("device:%s:%s:spawn\n", file, name);
|
||
if (smartctl[0])
|
||
log_fatal(EEXTERNAL, "Failed to run '%s %s %s'.\n", x, info_opts, extra_args);
|
||
else
|
||
log_fatal(EEXTERNAL, "Failed to run '%s %s %s'.\n", x, info_opts, file);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (smartctl_attribute(f, file, name, smart, info, serial, family, model, interface) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
os_pclose(f);
|
||
log_tag("device:%s:%s:spawn\n", file, name);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
ret = os_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);
|
||
if (smartctl[0])
|
||
log_fatal(EEXTERNAL, "Failed to run '%s %s %s' (not exited).\n", x, info_opts, extra_args);
|
||
else
|
||
log_fatal(EEXTERNAL, "Failed to run '%s %s %s' (not exited).\n", x, info_opts, file);
|
||
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];
|
||
ssize_t 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));
|
||
long long v = syslong(path);
|
||
if (v >= 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));
|
||
long long v = syslong(path);
|
||
if (v >= 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, const char* smartctl_info, int* power, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* interface)
|
||
{
|
||
char file[PATH_MAX];
|
||
OS_FILE* f;
|
||
int ret;
|
||
const char* x;
|
||
const char* info_opts = smartctl_info[0] ? smartctl_info : "-a";
|
||
|
||
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 */
|
||
}
|
||
|
||
const char* argv[ARGS_MAX];
|
||
unsigned argc = 0;
|
||
argv[argc++] = x;
|
||
argv[argc++] = "-n";
|
||
argv[argc++] = "standby,3";
|
||
|
||
char info_buf[SMART_MAX];
|
||
pathcpy(info_buf, sizeof(info_buf), info_opts);
|
||
argc += argsplit(argv + argc, ARGS_MAX - argc, info_buf);
|
||
if (argc >= ARGS_MAX) {
|
||
/* LCOV_EXCL_START */
|
||
log_fatal(EEXTERNAL, "Too many smartctl arguments.\n");
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
char extra_args[PATH_MAX];
|
||
if (smartctl[0]) {
|
||
pathprint(extra_args, sizeof(extra_args), smartctl, file);
|
||
argc += argsplit(argv + argc, ARGS_MAX - argc, extra_args);
|
||
if (argc >= ARGS_MAX) {
|
||
/* LCOV_EXCL_START */
|
||
log_fatal(EEXTERNAL, "Too many smartctl arguments.\n");
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
log_tag("smartctl:%s:%s:run: %s -n standby,3 [info: %s] %s\n", file, name, x, info_opts, extra_args);
|
||
} else {
|
||
if (argc >= ARGS_MAX - 1) {
|
||
/* LCOV_EXCL_START */
|
||
log_fatal(EEXTERNAL, "Too many smartctl arguments.\n");
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
argv[argc++] = file;
|
||
log_tag("smartctl:%s:%s:run: %s -n standby,3 [info: %s] %s\n", file, name, x, info_opts, file);
|
||
}
|
||
argv[argc++] = 0;
|
||
|
||
f = os_popen(argv);
|
||
if (!f) {
|
||
/* LCOV_EXCL_START */
|
||
log_tag("device:%s:%s:spawn\n", file, name);
|
||
if (smartctl[0])
|
||
log_fatal(EEXTERNAL, "Failed to run '%s -n standby,3 %s %s'.\n", x, info_opts, extra_args);
|
||
else
|
||
log_fatal(EEXTERNAL, "Failed to run '%s -n standby,3 %s %s'.\n", x, info_opts, file);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (smartctl_attribute(f, file, name, smart, info, serial, family, model, interface) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
os_pclose(f);
|
||
log_tag("device:%s:%s:spawn\n", file, name);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
ret = os_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);
|
||
if (smartctl[0])
|
||
log_fatal(EEXTERNAL, "Failed to run '%s -n standby,3 %s %s' (not exited).\n", x, info_opts, extra_args);
|
||
else
|
||
log_fatal(EEXTERNAL, "Failed to run '%s -n standby,3 %s %s' (not exited).\n", x, info_opts, file);
|
||
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 extra_args[PATH_MAX];
|
||
char file[PATH_MAX];
|
||
OS_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 */
|
||
}
|
||
|
||
const char* argv[ARGS_MAX];
|
||
unsigned argc = 0;
|
||
argv[argc++] = x;
|
||
argv[argc++] = "-s";
|
||
argv[argc++] = "standby,now";
|
||
|
||
if (smartctl[0]) {
|
||
pathprint(extra_args, sizeof(extra_args), smartctl, file);
|
||
argc += argsplit(argv + argc, ARGS_MAX - argc, extra_args);
|
||
if (argc >= ARGS_MAX) {
|
||
/* LCOV_EXCL_START */
|
||
log_fatal(EEXTERNAL, "Too many smartctl arguments.\n");
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
log_tag("smartctl:%s:%s:run: %s -s standby,now %s\n", file, name, x, extra_args);
|
||
} else {
|
||
if (argc >= ARGS_MAX - 1) {
|
||
/* LCOV_EXCL_START */
|
||
log_fatal(EEXTERNAL, "Too many smartctl arguments.\n");
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
argv[argc++] = file;
|
||
log_tag("smartctl:%s:%s:run: %s -s standby,now %s\n", file, name, x, file);
|
||
}
|
||
argv[argc++] = 0;
|
||
|
||
f = os_popen(argv);
|
||
if (!f) {
|
||
/* LCOV_EXCL_START */
|
||
log_tag("device:%s:%s:spawn\n", file, name);
|
||
if (smartctl[0])
|
||
log_fatal(EEXTERNAL, "Failed to run '%s -s standby,now %s'.\n", x, extra_args);
|
||
else
|
||
log_fatal(EEXTERNAL, "Failed to run '%s -s standby,now %s'.\n", x, file);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (smartctl_flush(f, file, name) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
os_pclose(f);
|
||
log_tag("device:%s:%s:spawn\n", file, name);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
ret = os_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);
|
||
if (smartctl[0])
|
||
log_fatal(EEXTERNAL, "Failed to run '%s -s standby,now %s' (not exited).\n", x, extra_args);
|
||
else
|
||
log_fatal(EEXTERNAL, "Failed to run '%s -s standby,now %s' (not exited).\n", x, file);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
if (WEXITSTATUS(ret) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_tag("device:%s:%s:exit:%d\n", file, name, WEXITSTATUS(ret));
|
||
if (smartctl[0])
|
||
log_fatal(EEXTERNAL, "Failed to run '%s -s standby,now %s' with return code %xh.\n", x, extra_args, WEXITSTATUS(ret));
|
||
else
|
||
log_fatal(EEXTERNAL, "Failed to run '%s -s standby,now %s' with return code %xh.\n", x, file, 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, const char* smartctl_info, int* power)
|
||
{
|
||
*power = POWER_UNKNOWN;
|
||
|
||
if (devprobe(device, name, smartctl, smartctl_info, power, 0, 0, 0, 0, 0, 0) != 0)
|
||
return -1;
|
||
|
||
if (*power == POWER_ACTIVE)
|
||
return devdown(device, name, smartctl);
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
/**
|
||
* Check if the device needs power management (it's a rotational one)
|
||
*/
|
||
#if HAVE_LINUX_DEVICE
|
||
static int devpower(dev_t device)
|
||
{
|
||
char path[PATH_MAX];
|
||
long long rotational;
|
||
|
||
pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/queue/rotational", major(device), minor(device));
|
||
rotational = syslong(path);
|
||
|
||
if (rotational < 0)
|
||
return 1; /* assume rotational if unknown */
|
||
|
||
return rotational != 0;
|
||
}
|
||
#endif
|
||
|
||
/**
|
||
* Spin up a specific device.
|
||
*/
|
||
#if HAVE_LINUX_DEVICE
|
||
static int devup(dev_t device, const char* name)
|
||
{
|
||
char file[PATH_MAX];
|
||
ssize_t 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;
|
||
|
||
/* skip not rotational devices */
|
||
if (devpower(devinfo->device) == 0)
|
||
return 0;
|
||
|
||
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->smartctl_info, 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;
|
||
|
||
/* skip not rotational devices */
|
||
if (devpower(devinfo->device) == 0)
|
||
return 0;
|
||
|
||
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;
|
||
|
||
/* skip not rotational devices */
|
||
if (devpower(devinfo->device) == 0)
|
||
return 0;
|
||
|
||
start = os_tick_ms();
|
||
|
||
if (devdownifup(devinfo->device, devinfo->name, devinfo->smartctl, devinfo->smartctl_info, &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->smartctl_info, 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->smartctl_info, &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;
|
||
}
|
||
|
||
void devsync(tommy_list* high)
|
||
{
|
||
#if HAVE_SYNCFS
|
||
tommy_node* i;
|
||
|
||
for (i = tommy_list_head(high); i != 0; i = i->next) {
|
||
devinfo_t* devinfo = i->data;
|
||
int f;
|
||
|
||
f = open(devinfo->mount, O_RDONLY);
|
||
if (f >= 0) {
|
||
syncfs(f);
|
||
close(f);
|
||
}
|
||
}
|
||
#else
|
||
(void)high;
|
||
#endif
|
||
}
|
||
|
||
int devquery(tommy_list* high, tommy_list* low)
|
||
{
|
||
#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;
|
||
|
||
tommy_list devlist;
|
||
tommy_list_init(&devlist);
|
||
|
||
/* obtain the real devices */
|
||
if (devdereference(device, devinfo->mount, &devlist) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_fatal(EEXTERNAL, "Failed to dereference device '%u:%u' at '%s'.\n", major(device), minor(device), devinfo->mount);
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
devinfo->file[0] = 0;
|
||
for (tommy_node* j = tommy_list_head(&devlist); j != 0; j = j->next) {
|
||
struct dev_struct* dev = j->data;
|
||
uint64_t access_stat;
|
||
|
||
/* retrieve access stat for the high level device */
|
||
if (devstat(dev->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 */
|
||
char file[PATH_MAX];
|
||
if (devresolve(dev->device, file, sizeof(file)) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_fatal(EEXTERNAL, "Failed to resolve device '%u:%u'.\n", major(dev->device), minor(dev->device));
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* add to the list of device files */
|
||
if (devinfo->file[0] != 0)
|
||
pathcat(devinfo->file, sizeof(devinfo->file), ",");
|
||
pathcat(devinfo->file, sizeof(devinfo->file), file);
|
||
|
||
/* expand the tree of devices */
|
||
if (devtree(devinfo, dev->device, low) != 0) {
|
||
/* LCOV_EXCL_START */
|
||
log_fatal(EEXTERNAL, "Failed to expand device '%u:%u'.\n", major(dev->device), minor(dev->device));
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
}
|
||
|
||
tommy_list_foreach(&devlist, free);
|
||
}
|
||
#else
|
||
(void)high;
|
||
(void)low;
|
||
#endif
|
||
|
||
return 0;
|
||
}
|
||
|
||
int devrun(tommy_list* low, int operation)
|
||
{
|
||
void* (*func)(void* arg) = 0;
|
||
|
||
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);
|
||
}
|
||
|
||
int devmap(void)
|
||
{
|
||
#if HAVE_LINUX_DEVICE
|
||
char path[PATH_MAX];
|
||
DIR* d = opendir("/sys/block");
|
||
if (!d) {
|
||
/* LCOV_EXCL_START */
|
||
return -1;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
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
|
||
log_tag("map_end\n");
|
||
return 0;
|
||
}
|
||
|
||
/****************************************************************************/
|
||
/* temperature */
|
||
|
||
#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) {
|
||
/* LCOV_EXCL_START */
|
||
return 0;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* 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) {
|
||
/* LCOV_EXCL_START */
|
||
continue;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
while ((hwmon_entry = readdir(hwmon_dir)) != NULL) {
|
||
char hwmon_name[PATH_MAX];
|
||
char value[SMART_MAX];
|
||
char name[SMART_MAX];
|
||
char label[SMART_MAX];
|
||
char* dash;
|
||
char* e;
|
||
int 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) {
|
||
/* LCOV_EXCL_START */
|
||
continue;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
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) {
|
||
/* LCOV_EXCL_START */
|
||
continue;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
temp = strtoi(value, &e, 10) / 1000;
|
||
if (*e != 0 && !isspace((unsigned char)*e)) {
|
||
/* LCOV_EXCL_START */
|
||
continue;
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* 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) {
|
||
/* LCOV_EXCL_START */
|
||
/* fallback to using the hwmon name */
|
||
pathcpy(name, sizeof(name), entry->d_name);
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
/* 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) {
|
||
/* LCOV_EXCL_START */
|
||
/* fallback to using the temp* name (e.g., temp1, temp2) */
|
||
pathcpy(label, sizeof(label), hwmon_name);
|
||
/* LCOV_EXCL_STOP */
|
||
}
|
||
|
||
log_tag("thermal:ambient:device:%s:%s:%s:%s:%d\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:%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
|
||
|
||
/****************************************************************************/
|
||
/* app */
|
||
|
||
void app_init(void)
|
||
{
|
||
/* set LC_ALL=C to make smartctl ignoring the locale when printing info */
|
||
setenv("LC_ALL", "C", 1);
|
||
}
|
||
|
||
void app_done(void)
|
||
{
|
||
}
|
||
|
||
void app_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");
|
||
}
|
||
}
|
||
|
||
#ifdef __linux__
|
||
static void read_os_release(char* distro, size_t distro_size)
|
||
{
|
||
distro[0] = 0;
|
||
|
||
char buf[1024];
|
||
ssize_t len = sysread("/etc/os-release", buf, sizeof(buf) - 1);
|
||
if (len <= 0)
|
||
len = sysread("/usr/lib/os-release", buf, sizeof(buf) - 1);
|
||
if (len <= 0)
|
||
return;
|
||
buf[len] = 0;
|
||
|
||
char* p = buf;
|
||
while ((p = strstr(p, "PRETTY_NAME=")) != 0) {
|
||
if (p == buf || *(p - 1) == '\n')
|
||
break;
|
||
++p;
|
||
}
|
||
if (!p)
|
||
return;
|
||
|
||
p += 12;
|
||
char q = 0;
|
||
if (*p == '"' || *p == '\'') {
|
||
q = *p++;
|
||
}
|
||
|
||
size_t d = 0;
|
||
while (*p != 0 && *p != '\n' && (q != 0 ? *p != q : 1)) {
|
||
if (*p == '\\' && *(p + 1) != 0 && *(p + 1) != '\n') {
|
||
++p;
|
||
}
|
||
if (d + 1 < distro_size) {
|
||
distro[d++] = *p;
|
||
}
|
||
++p;
|
||
}
|
||
distro[d] = 0;
|
||
strtrim(distro);
|
||
}
|
||
#endif
|
||
|
||
/****************************************************************************/
|
||
/* machine */
|
||
|
||
#if defined(__linux__) || defined(__APPLE__)
|
||
static void format_freq(char* buf, size_t size, unsigned mhz)
|
||
{
|
||
if (mhz >= 1000)
|
||
snprintf(buf, size, "%.2f GHz", mhz / 1000.0);
|
||
else
|
||
snprintf(buf, size, "%u MHz", mhz);
|
||
}
|
||
#endif
|
||
|
||
#ifdef __APPLE__
|
||
static int sysctl_string(const char* name, char* buf, size_t size)
|
||
{
|
||
size_t len = size;
|
||
|
||
if (sysctlbyname(name, buf, &len, 0, 0) != 0) {
|
||
buf[0] = 0;
|
||
return -1;
|
||
}
|
||
|
||
buf[size - 1] = 0;
|
||
strtrim(buf);
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
void machineinfo(struct machineinfo_struct* info)
|
||
{
|
||
memset(info, 0, sizeof(*info));
|
||
|
||
#if defined(__x86_64__)
|
||
snprintf(info->cpu_arch, sizeof(info->cpu_arch), "x86_64");
|
||
#elif defined(__i386__)
|
||
snprintf(info->cpu_arch, sizeof(info->cpu_arch), "x86_32");
|
||
#elif defined(__aarch64__)
|
||
snprintf(info->cpu_arch, sizeof(info->cpu_arch), "aarch64");
|
||
#elif defined(__arm__)
|
||
snprintf(info->cpu_arch, sizeof(info->cpu_arch), "arm");
|
||
#elif defined(__powerpc64__)
|
||
snprintf(info->cpu_arch, sizeof(info->cpu_arch), "ppc64");
|
||
#elif defined(__powerpc__)
|
||
snprintf(info->cpu_arch, sizeof(info->cpu_arch), "ppc");
|
||
#elif defined(__riscv)
|
||
snprintf(info->cpu_arch, sizeof(info->cpu_arch), "riscv");
|
||
#elif defined(__s390x__)
|
||
snprintf(info->cpu_arch, sizeof(info->cpu_arch), "s390x");
|
||
#endif
|
||
|
||
#ifdef CONFIG_X86
|
||
raid_cpu_info(info->cpu_vendor, &info->cpu_family, &info->cpu_model);
|
||
|
||
uint32_t reg[4];
|
||
raid_cpuid(1, 0, reg);
|
||
info->cpu_stepping = reg[0] & 0xF;
|
||
|
||
/* brand string from extended CPUID leaves 0x80000002..0x80000004 */
|
||
raid_cpuid(0x80000000, 0, reg);
|
||
if (reg[0] >= 0x80000004) {
|
||
raid_cpuid(0x80000002, 0, reg);
|
||
memcpy(info->cpu_brand + 0, reg, sizeof(reg));
|
||
raid_cpuid(0x80000003, 0, reg);
|
||
memcpy(info->cpu_brand + 16, reg, sizeof(reg));
|
||
raid_cpuid(0x80000004, 0, reg);
|
||
memcpy(info->cpu_brand + 32, reg, sizeof(reg));
|
||
info->cpu_brand[48] = 0;
|
||
strtrim(info->cpu_brand);
|
||
}
|
||
|
||
/* hypervisor detection */
|
||
raid_cpuid(1, 0, reg);
|
||
if (reg[2] & (1U << 31)) {
|
||
raid_cpuid(0x40000000, 0, reg);
|
||
memcpy(info->hypervisor + 0, ®[1], 4);
|
||
memcpy(info->hypervisor + 4, ®[2], 4);
|
||
memcpy(info->hypervisor + 8, ®[3], 4);
|
||
info->hypervisor[12] = 0;
|
||
strtrim(info->hypervisor);
|
||
}
|
||
#endif
|
||
|
||
struct utsname un;
|
||
if (uname(&un) == 0) {
|
||
snprintf(info->os_name, sizeof(info->os_name), "%s %s", un.sysname, un.release);
|
||
}
|
||
|
||
long ps = sysconf(_SC_PAGESIZE);
|
||
if (ps > 0)
|
||
info->memory_page_size = (uint32_t)ps;
|
||
|
||
#ifdef _SC_NPROCESSORS_ONLN
|
||
long num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
|
||
if (num_cpus <= 0)
|
||
num_cpus = sysconf(_SC_NPROCESSORS_CONF);
|
||
if (num_cpus > 0)
|
||
info->cpu_cores_logical = (unsigned)num_cpus;
|
||
#elif defined(_SC_NPROCESSORS_CONF)
|
||
long num_cpus = sysconf(_SC_NPROCESSORS_CONF);
|
||
if (num_cpus > 0)
|
||
info->cpu_cores_logical = (unsigned)num_cpus;
|
||
#endif
|
||
|
||
#ifdef _SC_LEVEL1_DCACHE_SIZE
|
||
long l1_d = sysconf(_SC_LEVEL1_DCACHE_SIZE);
|
||
if (l1_d > 0)
|
||
info->cache_l1_data = (uint64_t)l1_d;
|
||
#endif
|
||
#ifdef _SC_LEVEL1_ICACHE_SIZE
|
||
long l1_i = sysconf(_SC_LEVEL1_ICACHE_SIZE);
|
||
if (l1_i > 0)
|
||
info->cache_l1_instruction = (uint64_t)l1_i;
|
||
#endif
|
||
#ifdef _SC_LEVEL2_CACHE_SIZE
|
||
long l2 = sysconf(_SC_LEVEL2_CACHE_SIZE);
|
||
if (l2 > 0)
|
||
info->cache_l2 = (uint64_t)l2;
|
||
#endif
|
||
#ifdef _SC_LEVEL3_CACHE_SIZE
|
||
long l3 = sysconf(_SC_LEVEL3_CACHE_SIZE);
|
||
if (l3 > 0)
|
||
info->cache_l3 = (uint64_t)l3;
|
||
#endif
|
||
|
||
#ifdef __linux__
|
||
struct sysinfo si;
|
||
if (sysinfo(&si) == 0) {
|
||
info->memory_total_bytes = (uint64_t)si.totalram * si.mem_unit;
|
||
}
|
||
|
||
/*
|
||
* Group CPUs by distinct maximum frequency rather than relying on
|
||
* consecutive ID ranges. This handles non-consecutive ID layouts cleanly
|
||
* and avoids artificial cluster fragmentation.
|
||
*/
|
||
struct {
|
||
unsigned mhz;
|
||
unsigned count;
|
||
unsigned cpu;
|
||
} clusters[8];
|
||
unsigned cluster_count = 0;
|
||
|
||
unsigned cpus[CPU_SETSIZE];
|
||
unsigned online_total = read_online_cpus(cpus, sizeof(cpus) / sizeof(cpus[0]));
|
||
unsigned scan_cpus = online_total < sizeof(cpus) / sizeof(cpus[0]) ? online_total : sizeof(cpus) / sizeof(cpus[0]);
|
||
|
||
if (scan_cpus > 0) {
|
||
for (unsigned k = 0; k < scan_cpus; ++k) {
|
||
unsigned i = cpus[k];
|
||
char path[PATH_MAX];
|
||
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/cpufreq/cpuinfo_max_freq", i);
|
||
long long khz = syslong(path);
|
||
if (khz > 0) {
|
||
unsigned mhz = (unsigned)(khz / 1000);
|
||
int found = 0;
|
||
for (unsigned c = 0; c < cluster_count; ++c) {
|
||
if (clusters[c].mhz == mhz) {
|
||
++clusters[c].count;
|
||
found = 1;
|
||
break;
|
||
}
|
||
}
|
||
if (!found && cluster_count < sizeof(clusters) / sizeof(clusters[0])) {
|
||
clusters[cluster_count].mhz = mhz;
|
||
clusters[cluster_count].count = 1;
|
||
clusters[cluster_count].cpu = i;
|
||
++cluster_count;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* sort clusters by frequency descending (fastest cores first) */
|
||
for (unsigned i = 0; i < cluster_count; ++i) {
|
||
for (unsigned j = i + 1; j < cluster_count; ++j) {
|
||
if (clusters[j].mhz > clusters[i].mhz) {
|
||
unsigned tmp_mhz = clusters[i].mhz;
|
||
unsigned tmp_count = clusters[i].count;
|
||
unsigned tmp_cpu = clusters[i].cpu;
|
||
clusters[i].mhz = clusters[j].mhz;
|
||
clusters[i].count = clusters[j].count;
|
||
clusters[i].cpu = clusters[j].cpu;
|
||
clusters[j].mhz = tmp_mhz;
|
||
clusters[j].count = tmp_count;
|
||
clusters[j].cpu = tmp_cpu;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (cluster_count > 1) {
|
||
info->cpu_clock[0] = 0;
|
||
size_t pos = 0;
|
||
for (unsigned c = 0; c < cluster_count; ++c) {
|
||
char freq_str[32];
|
||
format_freq(freq_str, sizeof(freq_str), clusters[c].mhz);
|
||
char part[64];
|
||
if (clusters[c].count == 1)
|
||
snprintf(part, sizeof(part), "%s%s (1 cpu)", c > 0 ? ", " : "", freq_str);
|
||
else
|
||
snprintf(part, sizeof(part), "%s%s (%u cpus)", c > 0 ? ", " : "", freq_str, clusters[c].count);
|
||
if (pos < sizeof(info->cpu_clock)) {
|
||
int n = snprintf(info->cpu_clock + pos, sizeof(info->cpu_clock) - pos, "%s", part);
|
||
if (n > 0)
|
||
pos += (size_t)n;
|
||
}
|
||
}
|
||
} else if (cluster_count == 1) {
|
||
char path[PATH_MAX];
|
||
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/cpufreq/base_frequency", clusters[0].cpu);
|
||
long long base_khz = syslong(path);
|
||
unsigned base_mhz = base_khz > 0 ? (unsigned)(base_khz / 1000) : 0;
|
||
unsigned max_mhz = clusters[0].mhz;
|
||
char max_str[32];
|
||
char base_str[32];
|
||
format_freq(max_str, sizeof(max_str), max_mhz);
|
||
if (base_mhz > 0 && base_mhz != max_mhz) {
|
||
format_freq(base_str, sizeof(base_str), base_mhz);
|
||
snprintf(info->cpu_clock, sizeof(info->cpu_clock), "%s (max %s)", base_str, max_str);
|
||
} else {
|
||
snprintf(info->cpu_clock, sizeof(info->cpu_clock), "%s", max_str);
|
||
}
|
||
}
|
||
|
||
/* Fallback if sysfs cpufreq is unavailable (e.g. Cloud VM) */
|
||
if (info->cpu_clock[0] == 0) {
|
||
#ifdef CONFIG_X86
|
||
raid_cpuid(0, 0, reg);
|
||
if (reg[0] >= 0x16) {
|
||
raid_cpuid(0x16, 0, reg);
|
||
unsigned base_mhz = reg[0] & 0xFFFF;
|
||
unsigned max_mhz = reg[1] & 0xFFFF;
|
||
if (base_mhz > 0 && max_mhz > 0 && base_mhz != max_mhz) {
|
||
char base_str[32], max_str[32];
|
||
format_freq(base_str, sizeof(base_str), base_mhz);
|
||
format_freq(max_str, sizeof(max_str), max_mhz);
|
||
snprintf(info->cpu_clock, sizeof(info->cpu_clock), "%s (max %s)", base_str, max_str);
|
||
} else if (base_mhz > 0) {
|
||
format_freq(info->cpu_clock, sizeof(info->cpu_clock), base_mhz);
|
||
} else if (max_mhz > 0) {
|
||
format_freq(info->cpu_clock, sizeof(info->cpu_clock), max_mhz);
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
|
||
if (info->cpu_clock[0] == 0) {
|
||
char buf[1024];
|
||
ssize_t len = sysread("/proc/cpuinfo", buf, sizeof(buf) - 1);
|
||
if (len > 0) {
|
||
buf[len] = 0;
|
||
char* p = strstr(buf, "cpu MHz");
|
||
if (p) {
|
||
p = strchr(p, ':');
|
||
if (p) {
|
||
double mhz = strtod(p + 1, 0);
|
||
if (mhz > 0)
|
||
format_freq(info->cpu_clock, sizeof(info->cpu_clock), (unsigned)(mhz + 0.5));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
char model_buf[128];
|
||
if (sysattr("/sys/class/dmi/id/product_name", model_buf, sizeof(model_buf)) == 0 && model_buf[0]) {
|
||
char vendor_buf[128];
|
||
if (sysattr("/sys/class/dmi/id/sys_vendor", vendor_buf, sizeof(vendor_buf)) == 0 && vendor_buf[0]) {
|
||
snprintf(info->system_model, sizeof(info->system_model), "%s %s", vendor_buf, model_buf);
|
||
} else {
|
||
snprintf(info->system_model, sizeof(info->system_model), "%s", model_buf);
|
||
}
|
||
} else if (sysattr("/proc/device-tree/model", model_buf, sizeof(model_buf)) == 0 && model_buf[0]) {
|
||
snprintf(info->system_model, sizeof(info->system_model), "%s", model_buf);
|
||
}
|
||
|
||
read_os_release(info->os_distribution, sizeof(info->os_distribution));
|
||
#endif
|
||
|
||
#ifdef __APPLE__
|
||
size_t len;
|
||
|
||
if (info->cpu_brand[0] == 0)
|
||
sysctl_string("machdep.cpu.brand_string", info->cpu_brand, sizeof(info->cpu_brand));
|
||
|
||
sysctl_string("hw.model", info->system_model, sizeof(info->system_model));
|
||
|
||
unsigned val = 0;
|
||
len = sizeof(val);
|
||
if (sysctlbyname("hw.physicalcpu", &val, &len, 0, 0) == 0)
|
||
info->cpu_cores_physical = val;
|
||
|
||
len = sizeof(val);
|
||
if (sysctlbyname("hw.logicalcpu", &val, &len, 0, 0) == 0)
|
||
info->cpu_cores_logical = val;
|
||
|
||
uint64_t hz = 0;
|
||
len = sizeof(hz);
|
||
if (sysctlbyname("hw.cpufrequency", &hz, &len, 0, 0) == 0 && hz > 0)
|
||
format_freq(info->cpu_clock, sizeof(info->cpu_clock), (unsigned)(hz / 1000000));
|
||
|
||
unsigned long clen = 0;
|
||
len = sizeof(clen);
|
||
if (sysctlbyname("hw.l1dcachesize", &clen, &len, 0, 0) == 0)
|
||
info->cache_l1_data = clen;
|
||
|
||
len = sizeof(clen);
|
||
if (sysctlbyname("hw.l1icachesize", &clen, &len, 0, 0) == 0)
|
||
info->cache_l1_instruction = clen;
|
||
|
||
len = sizeof(clen);
|
||
if (sysctlbyname("hw.l2cachesize", &clen, &len, 0, 0) == 0)
|
||
info->cache_l2 = clen;
|
||
|
||
len = sizeof(clen);
|
||
if (sysctlbyname("hw.l3cachesize", &clen, &len, 0, 0) == 0)
|
||
info->cache_l3 = clen;
|
||
|
||
uint64_t mem = 0;
|
||
len = sizeof(mem);
|
||
if (sysctlbyname("hw.memsize", &mem, &len, 0, 0) == 0)
|
||
info->memory_total_bytes = mem;
|
||
|
||
char osver[32];
|
||
sysctl_string("kern.osproductversion", osver, sizeof(osver));
|
||
|
||
char osrel[32];
|
||
sysctl_string("kern.osrelease", osrel, sizeof(osrel));
|
||
|
||
if (osver[0] && osrel[0]) {
|
||
snprintf(info->os_name, sizeof(info->os_name), "macOS %s (Darwin %s)", osver, osrel);
|
||
} else if (osver[0]) {
|
||
snprintf(info->os_name, sizeof(info->os_name), "macOS %s", osver);
|
||
} else if (osrel[0]) {
|
||
snprintf(info->os_name, sizeof(info->os_name), "Darwin %s", osrel);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
#endif
|
||
|