diff --git a/Makefile.am b/Makefile.am index 63304148..b073a855 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/cmdline/device.c b/cmdline/device.c index 238047f8..1794f739 100644 --- a/cmdline/device.c +++ b/cmdline/device.c @@ -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); diff --git a/cmdline/mingw.c b/cmdline/mingw.c index 65246b83..3c655322 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -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) diff --git a/cmdline/portable.h b/cmdline/portable.h index 17aa73fb..d3d91a16 100644 --- a/cmdline/portable.h +++ b/cmdline/portable.h @@ -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, diff --git a/cmdline/snapraid.c b/cmdline/snapraid.c index 6f53e7b1..54790b8c 100644 --- a/cmdline/snapraid.c +++ b/cmdline/snapraid.c @@ -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); diff --git a/cmdline/unix.c b/cmdline/unix.c index 9050bb03..d5a4657e 100644 --- a/cmdline/unix.c +++ b/cmdline/unix.c @@ -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) diff --git a/snapraid.1 b/snapraid.1 index 3f1699bc..c5595227 100644 --- a/snapraid.1 +++ b/snapraid.1 @@ -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 diff --git a/snapraid.d b/snapraid.d index 0c6051a7..9dfaef39 100644 --- a/snapraid.d +++ b/snapraid.d @@ -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.