Adds a new smartignore option to ignore specific SMART attributes
Some checks are pending
Build / build-mac (push) Waiting to run
Build / build-linux (push) Waiting to run
Build / build-win (push) Waiting to run
CodeQL / Analyze (push) Waiting to run

This commit is contained in:
Andrea Mazzoleni 2025-09-15 16:56:09 +02:00
parent 4c504ab325
commit 8cef686d26
15 changed files with 211 additions and 39 deletions

View file

@ -4,6 +4,8 @@ SnapRAID HISTORY
12.5 2025/XX
============
* Add a bandwidth limit option --bw-limit [Allan Clark]
* Add a new smartignore configuration option to ignore specific SMART
attributes.
12.4 2025/01
============

6
TODO
View file

@ -5,15 +5,13 @@ This is the list of TODO items for SnapRAID.
- Next
* Add an option to ignore a specific Smart attribute. This solves PullRequest #17 an #21
* Support BTRFS subvolumes. Keep track of the subvolumeid, along with the inode, and use it when searching/comparing inodes.
The value 0 can be used for the root volume, like a normal filesystem.
See: https://github.com/kdave/btrfs-progs/tree/master/libbtrfsutil
* A "dependents" output option to [--test-fmt file|disk|path|dependents] for LIST.
* A "dependents" output option to [--test-fmt file|disk|path|dependents] for LIST.
Example, snapraid LIST --test-fmt dependents >FileList_with_dependents.txt
The goal is a filelist that, for every file, includes full paths to the dependent files
The goal is a filelist that, for every file, includes full paths to the dependent files
on other data disks needed to repair that file (in addition to the parity data).
I created a thread about it here: https://sourceforge.net/p/snapraid/discussion/1677233/thread/6e872fdae0/

View file

@ -605,7 +605,7 @@ static double raid_prob_of_one_or_more_failures(double array_failure_rate, doubl
return poisson_prob_n_or_more_failures(raid_failure_rate, 1);
}
static void state_smart(unsigned n, tommy_list* low)
static void state_smart(struct snapraid_state* state, unsigned n, tommy_list* low)
{
tommy_node* i;
unsigned j;
@ -665,6 +665,12 @@ static void state_smart(unsigned n, tommy_list* low)
double afr;
uint64_t flag;
/* clear attributes to ignore */
for (j = 0; j < SMART_IGNORE_MAX; ++j) {
devinfo->smart[state->smartignore[j]] = SMART_UNASSIGNED;
devinfo->smart[devinfo->smartignore[j]] = SMART_UNASSIGNED;
}
if (devinfo->smart[SMART_TEMPERATURE_CELSIUS] != SMART_UNASSIGNED)
printf("%7" PRIu64, devinfo->smart[SMART_TEMPERATURE_CELSIUS] & mask16);
else if (devinfo->smart[SMART_AIRFLOW_TEMPERATURE_CELSIUS] != SMART_UNASSIGNED)
@ -932,6 +938,7 @@ void state_device(struct snapraid_state* state, int operation, tommy_list* filte
pathcpy(entry->name, sizeof(entry->name), disk->name);
pathcpy(entry->mount, sizeof(entry->mount), disk->dir);
pathcpy(entry->smartctl, sizeof(entry->smartctl), disk->smartctl);
memcpy(entry->smartignore, disk->smartignore, sizeof(entry->smartignore));
tommy_list_insert_tail(&high, &entry->node, entry);
}
@ -951,6 +958,7 @@ void state_device(struct snapraid_state* state, int operation, tommy_list* filte
pathcpy(entry->name, sizeof(entry->name), lev_config_name(j));
pathcpy(entry->mount, sizeof(entry->mount), state->parity[j].split_map[s].path);
pathcpy(entry->smartctl, sizeof(entry->smartctl), state->parity[j].smartctl);
memcpy(entry->smartignore, state->parity[j].smartignore, sizeof(entry->smartignore));
pathcut(entry->mount); /* remove the parity file */
tommy_list_insert_tail(&high, &entry->node, entry);
@ -992,7 +1000,7 @@ void state_device(struct snapraid_state* state, int operation, tommy_list* filte
}
if (operation == DEVICE_SMART)
state_smart(state->level + tommy_list_count(&state->disklist), &low);
state_smart(state, state->level + tommy_list_count(&state->disklist), &low);
}
tommy_list_foreach(&high, free);

View file

@ -739,6 +739,7 @@ int dir_name_compare(const void* void_arg, const void* void_data)
struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev, const char* uuid, int skip_access)
{
struct snapraid_disk* disk;
unsigned i;
disk = malloc_nofail(sizeof(struct snapraid_disk));
pathcpy(disk->name, sizeof(disk->name), name);
@ -754,6 +755,8 @@ struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev
#endif
disk->smartctl[0] = 0;
for (i = 0; i < SMART_IGNORE_MAX; ++i)
disk->smartignore[i] = 0;
disk->device = dev;
disk->tick = 0;
disk->cached_blocks = 0;

View file

@ -330,6 +330,7 @@ struct snapraid_disk {
char name[PATH_MAX]; /**< Name of the disk. */
char dir[PATH_MAX]; /**< Mount point of the disk. It always terminates with /. */
char smartctl[PATH_MAX]; /**< Custom command for smartctl. Empty means auto. */
int smartignore[SMART_IGNORE_MAX]; /**< Smart attributes to ignore for this device. */
char uuid[UUID_MAX]; /**< UUID of the disk. */
uint64_t device; /**< Device identifier. */
@ -471,6 +472,7 @@ struct snapraid_parity {
struct snapraid_split split_map[SPLIT_MAX]; /**< Parity splits. */
unsigned split_mac; /**< Number of parity splits. */
char smartctl[PATH_MAX]; /**< Custom command for smartctl. Empty means auto. */
int smartignore[SMART_IGNORE_MAX]; /**< Smart attributes to ignore for this device. */
block_off_t total_blocks; /**< Number of total blocks. */
block_off_t free_blocks; /**< Number of free blocks at the last sync. */
int is_excluded_by_filter; /**< If the parity is excluded by filters. */

View file

@ -2154,7 +2154,7 @@ static int devresolve(const char* mount, char* file, size_t file_size, char* wfi
/**
* Read a device tree filling the specified list of disk_t entries.
*/
static int devtree(const char* name, const char* custom, const char* wfile, devinfo_t* parent, tommy_list* list)
static int devtree(const char* name, const char* smartctl, const int* smartignore, const char* wfile, devinfo_t* parent, tommy_list* list)
{
wchar_t conv_buf[CONV_MAX];
HANDLE h;
@ -2203,7 +2203,8 @@ static int devtree(const char* name, const char* custom, const char* wfile, devi
devinfo = calloc_nofail(1, sizeof(devinfo_t));
pathcpy(devinfo->name, sizeof(devinfo->name), name);
pathcpy(devinfo->smartctl, sizeof(devinfo->smartctl), custom);
pathcpy(devinfo->smartctl, sizeof(devinfo->smartctl), smartctl);
memcpy(devinfo->smartignore, smartignore, sizeof(devinfo->smartignore));
devinfo->device = vde->Extents[i].DiskNumber;
pathprint(devinfo->file, sizeof(devinfo->file), "/dev/pd%" PRIu64, devinfo->device);
pathprint(devinfo->wfile, sizeof(devinfo->wfile), "\\\\.\\PhysicalDrive%" PRIu64, devinfo->device);
@ -2339,7 +2340,7 @@ static int devscan(tommy_list* list)
/**
* Get SMART attributes.
*/
static int devsmart(uint64_t device, const char* name, const char* custom, uint64_t* smart, char* serial, char* vendor, char* model)
static int devsmart(uint64_t device, const char* name, const char* smartctl, uint64_t* smart, char* serial, char* vendor, char* model)
{
char conv_buf[CONV_MAX];
WCHAR cmd[MAX_PATH + 128];
@ -2350,10 +2351,10 @@ static int devsmart(uint64_t device, const char* name, const char* custom, uint6
snprintf(file, sizeof(file), "/dev/pd%" PRIu64, device);
/* if there is a custom command */
if (custom[0]) {
/* if there is a custom smartctl command */
if (smartctl[0]) {
char option[128];
snprintf(option, sizeof(option), custom, file);
snprintf(option, sizeof(option), smartctl, file);
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -a %s", exedir, option);
} else {
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -a %s", exedir, file);
@ -2390,8 +2391,8 @@ retry:
/* LCOV_EXCL_STOP */
}
/* if first try without custom command */
if (count == 0 && custom[0] == 0) {
/* if first try without custom smartctl command */
if (count == 0 && smartctl[0] == 0) {
/*
* Handle some common cases in Windows.
*
@ -2425,7 +2426,7 @@ retry:
/**
* Spin down a specific device.
*/
static int devdown(uint64_t device, const char* name, const char* custom)
static int devdown(uint64_t device, const char* name, const char* smartctl)
{
char conv_buf[CONV_MAX];
WCHAR cmd[MAX_PATH + 128];
@ -2436,10 +2437,10 @@ static int devdown(uint64_t device, const char* name, const char* custom)
snprintf(file, sizeof(file), "/dev/pd%" PRIu64, device);
/* if there is a custom command */
if (custom[0]) {
/* if there is a custom smartctl command */
if (smartctl[0]) {
char option[128];
snprintf(option, sizeof(option), custom, file);
snprintf(option, sizeof(option), smartctl, file);
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -s standby,now %s", exedir, option);
} else {
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -s standby,now %s", exedir, file);
@ -2476,8 +2477,8 @@ retry:
/* LCOV_EXCL_STOP */
}
/* if first try without custom command */
if (count == 0 && custom[0] == 0) {
/* if first try without custom smartctl command */
if (count == 0 && smartctl[0] == 0) {
/*
* Handle some common cases in Windows.
*
@ -2660,7 +2661,7 @@ int devquery(tommy_list* high, tommy_list* low, int operation, int others)
}
/* expand the tree of devices */
if (devtree(devinfo->name, devinfo->smartctl, devinfo->wfile, devinfo, low) != 0) {
if (devtree(devinfo->name, devinfo->smartctl, devinfo->smartignore, devinfo->wfile, devinfo, low) != 0) {
/* LCOV_EXCL_START */
log_fatal("Failed to expand device '%s'.\n", devinfo->file);
return -1;

View file

@ -461,6 +461,8 @@ int randomize(void* ptr, size_t size);
*/
#define SMART_COUNT 260
#define SMART_IGNORE_MAX 4 /**< Max number of ignored smart attributes */
/**
* Flags returned by smartctl.
*/
@ -491,6 +493,7 @@ struct devinfo_struct {
char name[PATH_MAX]; /**< Name of the disk. */
char mount[PATH_MAX]; /**< Mount point or other contained directory. */
char smartctl[PATH_MAX]; /**< Options for smartctl. */
int smartignore[SMART_IGNORE_MAX]; /**< Attribues to ignore */
char file[PATH_MAX]; /**< File device. */
#ifdef _WIN32
char wfile[PATH_MAX]; /**< File device in Windows format. Like \\.\PhysicalDriveX, or \\?\Volume{X}. */

View file

@ -134,7 +134,7 @@ const char* lev_raid_name(unsigned mode, unsigned n)
void state_init(struct snapraid_state* state)
{
unsigned l, s;
unsigned l, s, i;
memset(&state->opt, 0, sizeof(state->opt));
state->filter_hidden = 0;
@ -153,6 +153,8 @@ void state_init(struct snapraid_state* state)
state->parity[l].split_map[s].device = 0;
}
state->parity[l].smartctl[0] = 0;
for (i = 0; i < SMART_IGNORE_MAX; ++i)
state->parity[l].smartignore[i] = 0;
state->parity[l].total_blocks = 0;
state->parity[l].free_blocks = 0;
state->parity[l].skip_access = 0;
@ -173,6 +175,8 @@ void state_init(struct snapraid_state* state)
state->level = 1; /* default is the lowest protection */
state->clear_past_hash = 0;
state->no_conf = 0;
for (i = 0; i < SMART_IGNORE_MAX; ++i)
state->smartignore[i] = 0;
tommy_list_init(&state->disklist);
tommy_list_init(&state->maplist);
@ -1063,6 +1067,93 @@ void state_config(struct snapraid_state* state, const char* path, const char* co
pathcpy(disk->smartctl, sizeof(disk->smartctl), custom);
}
} else if (strcmp(tag, "smartignore") == 0) {
int* smart;
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
log_fatal("Invalid 'smartignore' name in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*buffer) {
/* LCOV_EXCL_START */
log_fatal("Empty 'smartignore' name in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
sgetspace(f);
if (strcmp(buffer, "*") == 0) {
smart = state->smartignore;
} else if (lev_config_scan(buffer, &level, 0) == 0) { /* search for parity */
smart = state->parity[level].smartignore;
} else {
struct snapraid_disk* disk;
/* search the disk */
disk = 0;
for (i = state->disklist; i != 0; i = i->next) {
disk = i->data;
if (strcmp(disk->name, buffer) == 0)
break;
}
if (!disk) {
/* LCOV_EXCL_START */
log_fatal("Missing disk 'smartctl' '%s' at line %u\n", buffer, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
smart = disk->smartignore;
}
int si = 0;
while (1) {
char* e;
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
log_fatal("Invalid 'smartignore' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*buffer)
break;
if (si == SMART_IGNORE_MAX) {
/* LCOV_EXCL_START */
log_fatal("Too many 'smartignore' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
smart[si] = strtoul(buffer, &e, 0);
if (!e || *e) {
/* LCOV_EXCL_START */
log_fatal("Invalid 'smartignore' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
++si;
sgetspace(f);
}
if (si == 0) {
/* LCOV_EXCL_START */
log_fatal("Invalid 'smartignore' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
} else if (strcmp(tag, "nohidden") == 0) {
state->filter_hidden = 1;
} else if (strcmp(tag, "exclude") == 0) {

View file

@ -144,6 +144,7 @@ struct snapraid_state {
unsigned prevhash; /**< Previous hash kind used. In case of rehash. */
unsigned besthash; /**< Best hash suggested. */
const char* command; /**< Command running. */
int smartignore[SMART_IGNORE_MAX]; /**< Smart attributes to ignore for all devices. */
tommy_list contentlist; /**< List of content files. */
tommy_list disklist; /**< List of all the disks. */
tommy_list maplist; /**< List of all the disk mappings. */

View file

@ -513,7 +513,7 @@ static int devuuid_blkid(uint64_t device, char* uuid, size_t uuid_size)
#endif
#ifdef __APPLE__
static int devuuid_darwin(const char* path, char* uuid, size_t uuid_size)
static int devuuid_darwin(const char* path, char* uuid, size_t uuid_size)
{
CFStringRef path_apple = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
@ -531,7 +531,7 @@ static int devuuid_darwin(const char* path, char* uuid, size_t uuid_size)
path_appler = parent_path_appler;
}
} while (true); // This is guaranteed to succeed eventually because it'll hit `/`.
CFDictionaryRef description = DADiskCopyDescription(disk);
CFUUIDRef uuid_apple = CFDictionaryGetValue(description, kDADiskDescriptionVolumeUUIDKey);
CFStringRef uuid_string = CFUUIDCreateString(kCFAllocatorDefault, uuid_apple);
@ -948,7 +948,7 @@ static dev_t devread(const char* path)
* Read a device tree filling the specified list of disk_t entries.
*/
#if HAVE_LINUX_DEVICE
static int devtree(const char* name, const char* custom, dev_t device, devinfo_t* parent, tommy_list* list)
static int devtree(const char* name, const char* smartctl, const int* smartignore, dev_t device, devinfo_t* parent, tommy_list* list)
{
char path[PATH_MAX];
DIR* d;
@ -976,7 +976,7 @@ static int devtree(const char* name, const char* custom, dev_t device, devinfo_t
/* LCOV_EXCL_STOP */
}
if (devtree(name, custom, subdev, parent, list) != 0) {
if (devtree(name, smartctl, smartignore, subdev, parent, list) != 0) {
/* LCOV_EXCL_START */
closedir(d);
return -1;
@ -1021,7 +1021,8 @@ static int devtree(const char* name, const char* custom, dev_t device, devinfo_t
devinfo->device = device;
pathcpy(devinfo->name, sizeof(devinfo->name), name);
pathcpy(devinfo->smartctl, sizeof(devinfo->smartctl), custom);
pathcpy(devinfo->smartctl, sizeof(devinfo->smartctl), smartctl);
memcpy(devinfo->smartignore, smartignore, sizeof(devinfo->smartignore));
pathcpy(devinfo->file, sizeof(devinfo->file), path);
devinfo->parent = parent;
@ -1118,7 +1119,7 @@ static int devscan(tommy_list* list)
* Get SMART attributes.
*/
#if HAVE_LINUX_DEVICE
static int devsmart(dev_t device, const char* name, const char* custom, uint64_t* smart, char* serial, char* vendor, char* model)
static int devsmart(dev_t device, const char* name, const char* smartctl, uint64_t* smart, char* serial, char* vendor, char* model)
{
char cmd[PATH_MAX + 64];
char file[PATH_MAX];
@ -1132,10 +1133,10 @@ static int devsmart(dev_t device, const char* name, const char* custom, uint64_t
/* LCOV_EXCL_STOP */
}
/* if there is a custom command */
if (custom[0]) {
/* if there is a custom smartctl command */
if (smartctl[0]) {
char option[PATH_MAX];
snprintf(option, sizeof(option), custom, file);
snprintf(option, sizeof(option), smartctl, file);
snprintf(cmd, sizeof(cmd), "smartctl -a %s", option);
} else {
snprintf(cmd, sizeof(cmd), "smartctl -a %s", file);
@ -1186,7 +1187,7 @@ static int devsmart(dev_t device, const char* name, const char* custom, uint64_t
* Spin down a specific device.
*/
#if HAVE_LINUX_DEVICE
static int devdown(dev_t device, const char* name, const char* custom)
static int devdown(dev_t device, const char* name, const char* smartctl)
{
char cmd[PATH_MAX + 64];
char file[PATH_MAX];
@ -1200,10 +1201,10 @@ static int devdown(dev_t device, const char* name, const char* custom)
/* LCOV_EXCL_STOP */
}
/* if there is a custom command */
if (custom[0]) {
/* if there is a custom smartctl command */
if (smartctl[0]) {
char option[PATH_MAX];
snprintf(option, sizeof(option), custom, file);
snprintf(option, sizeof(option), smartctl, file);
snprintf(cmd, sizeof(cmd), "smartctl -s standby,now %s", option);
} else {
snprintf(cmd, sizeof(cmd), "smartctl -s standby,now %s", file);
@ -1447,7 +1448,7 @@ int devquery(tommy_list* high, tommy_list* low, int operation, int others)
}
/* expand the tree of devices */
if (devtree(devinfo->name, devinfo->smartctl, device, devinfo, low) != 0) {
if (devtree(devinfo->name, devinfo->smartctl, devinfo->smartignore, device, devinfo, low) != 0) {
/* LCOV_EXCL_START */
log_fatal("Failed to expand device '%u:%u'.\n", major(device), minor(device));
return -1;

View file

@ -1504,6 +1504,32 @@ https://www.smartmontools.org/wiki/Supported_USB\-Devices
.PP
.PD
.RE
.SS smartignore DISK/PARITY ATTR [ATTR...]
Ignores the specified SMART attribute when computing the probability
of disk failure. This option is useful if a disk reports unusual or
misleading values for a particular attribute.
.PP
DISK is the same disk name specified in the \[dq]disk\[dq] option.
PARITY is one of the parity name as \[dq]parity,(1,2,3,4,5,6,z)\-parity\[dq].
The special value * can be used to ignore the attribute on all disks.
.PP
For example, to ignore the \[dq]Load_Cycle_Count\[dq] attribute on all disks:
.PP
.RS 4
smartignore * 193
.PD 0
.PP
.PD
.RE
.PP
To ignore it only on the first parity disk:
.PP
.RS 4
smartignore parity 193
.PD 0
.PP
.PD
.RE
.SS Examples
An example of a typical configuration for Unix is:
.PP

View file

@ -1181,6 +1181,23 @@ Configuration
:https://www.smartmontools.org/wiki/Supported_RAID-Controllers
:https://www.smartmontools.org/wiki/Supported_USB-Devices
smartignore DISK/PARITY ATTR [ATTR...]
Ignores the specified SMART attribute when computing the probability
of disk failure. This option is useful if a disk reports unusual or
misleading values for a particular attribute.
DISK is the same disk name specified in the "disk" option.
PARITY is one of the parity name as "parity,(1,2,3,4,5,6,z)-parity".
The special value * can be used to ignore the attribute on all disks.
For example, to ignore the "Load_Cycle_Count" attribute on all disks:
:smartignore * 193
To ignore it only on the first parity disk:
:smartignore parity 193
Examples
An example of a typical configuration for Unix is:

View file

@ -1267,7 +1267,26 @@ Refers at the smartmontools documentation about the possible options:
https://www.smartmontools.org/wiki/Supported_RAID-Controllers
https://www.smartmontools.org/wiki/Supported_USB-Devices
7.14 Examples
7.14 smartignore DISK/PARITY ATTR [ATTR...]
-------------------------------------------
Ignores the specified SMART attribute when computing the probability
of disk failure. This option is useful if a disk reports unusual or
misleading values for a particular attribute.
DISK is the same disk name specified in the "disk" option.
PARITY is one of the parity name as "parity,(1,2,3,4,5,6,z)-parity".
The special value * can be used to ignore the attribute on all disks.
For example, to ignore the "Load_Cycle_Count" attribute on all disks:
smartignore * 193
To ignore it only on the first parity disk:
smartignore parity 193
7.15 Examples
-------------
An example of a typical configuration for Unix is:

View file

@ -14,4 +14,3 @@ exclude *.unrecoverable
pool bench/pool
share \\server\jbod
autosave 1

View file

@ -22,4 +22,5 @@ include *.hidden
exclude *.unrecoverable
smartctl disk1 %s
smartctl parity /dev/sda
smartignore * 193
smartignore parity 193