Add a new probe command

Probe the power state of the disks in the system
This commit is contained in:
Andrea Mazzoleni 2025-09-23 22:38:13 +02:00
parent e57f07cc24
commit fedeb99bdc
8 changed files with 330 additions and 17 deletions

View file

@ -288,7 +288,7 @@ endif
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) up
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) down -d parity -d disk1
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) devices
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS_VERBOSE) -c $(CONF) smart --no-warnings
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS_VERBOSE) -c $(CONF) probe --test-fake-device
$(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS_VERBOSE) -c $(CONF) smart --test-fake-device --test-expect-failure
if HAVE_POSIX
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) pool

View file

@ -837,6 +837,65 @@ bail:
}
}
static void state_probe(struct snapraid_state* state, tommy_list* low)
{
tommy_node* i;
size_t device_pad;
(void)state;
/* compute lengths for padding */
device_pad = 0;
for (i = tommy_list_head(low); i != 0; i = i->next) {
size_t len;
devinfo_t* devinfo = i->data;
len = strlen(devinfo->file);
if (len > device_pad)
device_pad = len;
}
printf("SnapRAID PROBE report:\n");
printf("\n");
printf(" State ");
printf(" ");
printl("Device", device_pad);
printf(" Disk");
printf("\n");
/* |<##################################################################72>|####80>| */
printf(" -----------------------------------------------------------------------\n");
for (i = tommy_list_head(low); i != 0; i = i->next) {
devinfo_t* devinfo = i->data;
printf(" ");
switch (devinfo->power) {
case POWER_STANDBY : printf("StandBy"); break;
case POWER_ACTIVE : printf(" Active"); break;
default: printf("Unknown"); break;
}
printf(" ");
if (*devinfo->file)
printl(devinfo->file, device_pad);
else
printl("-", device_pad);
printf(" ");
if (*devinfo->name)
printf("%s", devinfo->name);
else
printf("-");
printf("\n");
log_tag("probe:%s:%s:%d\n", devinfo->file, devinfo->name, devinfo->power);
}
printf("\n");
}
/**
* Fill with fake data the device list.
*/
@ -844,7 +903,7 @@ static int devtest(tommy_list* low, int operation)
{
unsigned c;
if (operation != DEVICE_SMART)
if (operation != DEVICE_SMART && operation != DEVICE_PROBE)
return -1;
/* add some fake data */
@ -903,6 +962,8 @@ static int devtest(tommy_list* low, int operation)
case 10 : entry->smart[SMART_FLAGS] = SMARTCTL_FLAG_ERROR; break;
case 11 : entry->smart[SMART_FLAGS] = SMARTCTL_FLAG_ERROR_LOGGED; break;
}
entry->power = POWER_ACTIVE;
}
return 0;
@ -968,7 +1029,7 @@ void state_device(struct snapraid_state* state, int operation, tommy_list* filte
if (state->opt.fake_device) {
ret = devtest(&low, operation);
} else {
int others = operation == DEVICE_SMART;
int others = operation == DEVICE_SMART || operation == DEVICE_PROBE;
ret = devquery(&high, &low, operation, others);
}
@ -984,6 +1045,7 @@ void state_device(struct snapraid_state* state, int operation, tommy_list* filte
case DEVICE_DOWN : ope = "Spindown"; break;
case DEVICE_LIST : ope = "Device listing"; break;
case DEVICE_SMART : ope = "Smart"; break;
case DEVICE_PROBE : ope = "Probe"; break;
}
log_fatal("%s is unsupported in this platform.\n", ope);
} else {
@ -1001,6 +1063,9 @@ void state_device(struct snapraid_state* state, int operation, tommy_list* filte
if (operation == DEVICE_SMART)
state_smart(state, state->level + tommy_list_count(&state->disklist), &low);
if (operation == DEVICE_PROBE)
state_probe(state, &low);
}
tommy_list_foreach(&high, free);

View file

@ -2423,6 +2423,92 @@ retry:
return 0;
}
/**
* Get POWER state
*/
static int devprobe(uint64_t device, const char* name, const char* smartctl, int* power)
{
char conv_buf[CONV_MAX];
WCHAR cmd[MAX_PATH + 128];
char file[128];
FILE* f;
int ret;
int count;
snprintf(file, sizeof(file), "/dev/pd%" PRIu64, device);
/* if there is a custom smartctl command */
if (smartctl[0]) {
char option[128];
snprintf(option, sizeof(option), smartctl, file);
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -n standby,3 -i %s", exedir, option);
} else {
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -n standby,3 -i %s", exedir, file);
}
count = 0;
retry:
log_tag("smartctl:%s:%s:run: %s\n", file, name, u16tou8(conv_buf, cmd));
f = _wpopen(cmd, L"rt");
if (!f) {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' (from popen).\n", u16tou8(conv_buf, cmd));
return -1;
/* LCOV_EXCL_STOP */
}
if (smartctl_flush(f, file, name) != 0) {
/* LCOV_EXCL_START */
pclose(f);
return -1;
/* LCOV_EXCL_STOP */
}
ret = pclose(f);
log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
if (ret == -1) {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' (from pclose).\n", u16tou8(conv_buf, cmd));
return -1;
/* LCOV_EXCL_STOP */
}
/* if first try without custom smartctl command */
if (count == 0 && smartctl[0] == 0) {
/*
* Handle some common cases in Windows.
*
* Sometimes the "type" autodetection is wrong, and the command fails at identification
* stage, returning with error 2, or even with error 0, and with no info at all.
* We detect this condition checking the PowerOnHours, Size and RotationRate attributes.
*
* In such conditions we retry using the "sat" type, that often allows to proceed.
*
* Note that getting error 4 is instead very common, even with full info gathering.
*/
if (ret == 2) {
/* retry using the "sat" type */
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -a -d sat %s", exedir, file);
++count;
goto retry;
}
}
if (ret == 0)
*power = POWER_ACTIVE;
else if (ret == 3)
*power = POWER_STANDBY;
else
*power = POWER_UNKNOWN;
return 0;
}
/**
* Spin down a specific device.
*/
@ -2611,6 +2697,22 @@ static void* thread_smart(void* arg)
return 0;
}
/**
* Thread for getting power info.
*/
static void* thread_probe(void* arg)
{
devinfo_t* devinfo = arg;
if (devprobe(devinfo->device, devinfo->name, devinfo->smartctl, &devinfo->power) != 0) {
/* LCOV_EXCL_START */
return (void*)-1;
/* LCOV_EXCL_STOP */
}
return 0;
}
static int device_thread(tommy_list* list, void* (*func)(void* arg))
{
int fail = 0;
@ -2701,6 +2803,7 @@ int devquery(tommy_list* high, tommy_list* low, int operation, int others)
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;
}
if (!func)

View file

@ -485,6 +485,14 @@ int randomize(void* ptr, size_t size);
*/
#define SMART_UNASSIGNED 0xFFFFFFFFFFFFFFFFULL
/**
* Power mode
*/
#define POWER_STANDBY 0
#define POWER_ACTIVE 1
#define POWER_UNKNOWN -1
/**
* Device info entry.
*/
@ -503,6 +511,7 @@ struct devinfo_struct {
char smart_serial[SMART_MAX]; /**< SMART serial number. */
char smart_vendor[SMART_MAX]; /**< SMART vendor. */
char smart_model[SMART_MAX]; /**< SMART model. */
int power; /**< POWER mode. */
#if HAVE_THREAD
thread_id_t thread;
#endif
@ -514,6 +523,7 @@ typedef struct devinfo_struct devinfo_t;
#define DEVICE_DOWN 1
#define DEVICE_UP 2
#define DEVICE_SMART 3
#define DEVICE_PROBE 4
/**
* Query all the "high" level devices with the specified operation,

View file

@ -538,6 +538,7 @@ void signal_init(void)
#define OPERATION_SPINDOWN 15
#define OPERATION_DEVICES 16
#define OPERATION_SMART 17
#define OPERATION_PROBE 18
int main(int argc, char* argv[])
{
@ -1038,6 +1039,8 @@ int main(int argc, char* argv[])
operation = OPERATION_DEVICES;
} else if (strcmp(argv[optind], "smart") == 0) {
operation = OPERATION_SMART;
} else if (strcmp(argv[optind], "probe") == 0) {
operation = OPERATION_PROBE;
} else {
/* LCOV_EXCL_START */
log_fatal("Unknown command '%s'\n", argv[optind]);
@ -1062,6 +1065,7 @@ int main(int argc, char* argv[])
case OPERATION_FIX :
case OPERATION_CHECK :
case OPERATION_SMART :
case OPERATION_PROBE :
case OPERATION_DEVICES :
case OPERATION_SPINUP :
case OPERATION_SPINDOWN :
@ -1245,6 +1249,7 @@ int main(int argc, char* argv[])
case OPERATION_SPINDOWN :
case OPERATION_DEVICES :
case OPERATION_SMART :
case OPERATION_PROBE :
opt.skip_self = 1;
break;
}
@ -1266,6 +1271,7 @@ int main(int argc, char* argv[])
switch (operation) {
case OPERATION_DEVICES :
case OPERATION_SMART :
case OPERATION_PROBE :
/* we may need to use these commands during operations */
opt.skip_lock = 1;
break;
@ -1273,6 +1279,7 @@ int main(int argc, char* argv[])
switch (operation) {
case OPERATION_SMART :
case OPERATION_PROBE :
/* allow to run without configuration file */
opt.auto_conf = 1;
break;
@ -1494,6 +1501,8 @@ int main(int argc, char* argv[])
state_device(&state, DEVICE_LIST, 0);
} else if (operation == OPERATION_SMART) {
state_device(&state, DEVICE_SMART, 0);
} else if (operation == OPERATION_PROBE) {
state_device(&state, DEVICE_PROBE, 0);
} else if (operation == OPERATION_STATUS) {
state_read(&state);

View file

@ -1183,6 +1183,81 @@ static int devsmart(dev_t device, const char* name, const char* smartctl, uint64
}
#endif
/**
* Get POWER state.
*/
#if HAVE_LINUX_DEVICE
static int devprobe(dev_t device, const char* name, const char* smartctl, int* power)
{
char cmd[PATH_MAX + 64];
char file[PATH_MAX];
FILE* f;
int ret;
if (devresolve(device, file, sizeof(file)) != 0) {
/* LCOV_EXCL_START */
log_fatal("Failed to resolve device '%u:%u'.\n", major(device), minor(device));
return -1;
/* LCOV_EXCL_STOP */
}
/* if there is a custom smartctl command */
if (smartctl[0]) {
char option[PATH_MAX];
snprintf(option, sizeof(option), smartctl, file);
snprintf(cmd, sizeof(cmd), "smartctl -n standby,3 -i %s", option);
} else {
snprintf(cmd, sizeof(cmd), "smartctl -n standby,3 -i %s", file);
}
log_tag("smartctl:%s:%s:run: %s\n", file, name, cmd);
f = popen(cmd, "r");
if (!f) {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' (from popen).\n", cmd);
return -1;
/* LCOV_EXCL_STOP */
}
if (smartctl_flush(f, file, name) != 0) {
/* LCOV_EXCL_START */
pclose(f);
return -1;
/* LCOV_EXCL_STOP */
}
ret = pclose(f);
log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
if (!WIFEXITED(ret)) {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' (not exited).\n", cmd);
return -1;
/* LCOV_EXCL_STOP */
}
if (WEXITSTATUS(ret) == 127) {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' (from sh).\n", cmd);
return -1;
/* LCOV_EXCL_STOP */
}
if (WEXITSTATUS(ret) == 0) {
*power = POWER_ACTIVE;
} else if (WEXITSTATUS(ret) == 3) {
*power = POWER_STANDBY;
} else {
/* LCOV_EXCL_START */
log_fatal("Failed to run '%s' with return code %xh.\n", cmd, WEXITSTATUS(ret));
return -1;
/* LCOV_EXCL_STOP */
}
return 0;
}
#endif
/**
* Spin down a specific device.
*/
@ -1387,6 +1462,27 @@ static void* thread_smart(void* arg)
#endif
}
/**
* Thread for getting power info.
*/
static void* thread_probe(void* arg)
{
#if HAVE_LINUX_DEVICE
devinfo_t* devinfo = arg;
if (devprobe(devinfo->device, devinfo->name, devinfo->smartctl, &devinfo->power) != 0) {
/* LCOV_EXCL_START */
return (void*)-1;
/* LCOV_EXCL_STOP */
}
return 0;
#else
(void)arg;
return (void*)-1;
#endif
}
static int device_thread(tommy_list* list, void* (*func)(void* arg))
{
int fail = 0;
@ -1494,6 +1590,7 @@ int devquery(tommy_list* high, tommy_list* low, int operation, int others)
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;
}
if (!func)

View file

@ -58,11 +58,11 @@ snapraid [\-c, \-\-conf CONFIG]
.PD 0
.PP
.PD
status|smart|up|down|diff|sync|scrub|fix|check|list|dup
status|smart|probe|up|down|diff|sync|scrub|fix|check
.PD 0
.PP
.PD
|pool|devices|touch|rehash
|list|dup|pool|devices|touch|rehash
.PD 0
.PD
.PP
@ -635,7 +635,7 @@ SnapRAID provides a few simple commands that allow to:
.IP \(bu
Prints the status of the array \-> \[dq]status\[dq]
.IP \(bu
Controls the disks \-> \[dq]smart\[dq], \[dq]up\[dq], \[dq]down\[dq]
Controls the disks \-> \[dq]smart\[dq], \[dq]probe\[dq], \[dq]up\[dq], \[dq]down\[dq]
.IP \(bu
Makes a backup/snapshot \-> \[dq]sync\[dq]
.IP \(bu
@ -664,7 +664,7 @@ blocks synced but not yet scrubbed with \'o\'.
.PP
Nothing is modified.
.SS smart
Prints a SMART report of all the disks of the array.
Prints a SMART report of all the disks in the system.
.PP
It includes an estimation of the probability of failure in the next
year allowing to plan maintenance replacements of the disks that show
@ -703,10 +703,24 @@ If the \-v, \-\-verbose option is specified a deeper statistical analysis
is provided. This analysis can help you to decide if you need more
or less parity.
.PP
This command uses the \[dq]smartctl\[dq] tool, and it\'s equivalent to run
\[dq]smartctl \-a\[dq] on all the devices.
This command uses the \[dq]smartctl\[dq] tool and is equivalent to running
\[dq]smartctl \-a\[dq] on all devices.
.PP
If your devices are not auto\-detected correctly, you can configure
If your devices are not auto\-detected correctly, you can specify
a custom command using the \[dq]smartctl\[dq] option in the configuration
file.
.PP
Nothing is modified.
.SS probe
Prints the POWER state of all disks in the system.
.PP
\[dq]Standby\[dq] means the disk is not spinning. \[dq]Active\[dq] means
the disk is spinning.
.PP
This command uses the \[dq]smartctl\[dq] tool and is equivalent to running
\[dq]smartctl \-n standby \-i\[dq] on all devices.
.PP
If your devices are not auto\-detected correctly, you can specify
a custom command using the \[dq]smartctl\[dq] option in the configuration
file.
.PP

View file

@ -16,8 +16,8 @@ Synopsis
: [-S, --start BLKSTART] [-B, --count BLKCOUNT]
: [-L, --error-limit NUMBER]
: [-v, --verbose] [-q, --quiet]
: status|smart|up|down|diff|sync|scrub|fix|check|list|dup
: |pool|devices|touch|rehash
: status|smart|probe|up|down|diff|sync|scrub|fix|check
: |list|dup|pool|devices|touch|rehash
:snapraid [-V, --version] [-H, --help] [-C, --gen-conf CONTENT]
@ -379,7 +379,7 @@ Commands
SnapRAID provides a few simple commands that allow to:
* Prints the status of the array -> "status"
* Controls the disks -> "smart", "up", "down"
* Controls the disks -> "smart", "probe", "up", "down"
* Makes a backup/snapshot -> "sync"
* Periodically checks data -> "scrub"
* Restore the last backup/snapshot -> "fix".
@ -406,7 +406,7 @@ Commands
Nothing is modified.
smart
Prints a SMART report of all the disks of the array.
Prints a SMART report of all the disks in the system.
It includes an estimation of the probability of failure in the next
year allowing to plan maintenance replacements of the disks that show
@ -431,10 +431,25 @@ Commands
is provided. This analysis can help you to decide if you need more
or less parity.
This command uses the "smartctl" tool, and it's equivalent to run
"smartctl -a" on all the devices.
This command uses the "smartctl" tool and is equivalent to running
"smartctl -a" on all devices.
If your devices are not auto-detected correctly, you can configure
If your devices are not auto-detected correctly, you can specify
a custom command using the "smartctl" option in the configuration
file.
Nothing is modified.
probe
Prints the POWER state of all disks in the system.
"Standby" means the disk is not spinning. "Active" means
the disk is spinning.
This command uses the "smartctl" tool and is equivalent to running
"smartctl -n standby -i" on all devices.
If your devices are not auto-detected correctly, you can specify
a custom command using the "smartctl" option in the configuration
file.