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