Skip to content

Commit 0bc841f

Browse files
tonyhutterbehlendorf
authored andcommitted
zpool: Add slot power control, print power status
Add `zpool` flags to control the slot power to drives. This assumes your SAS or NVMe enclosure supports slot power control via sysfs. The new `--power` flag is added to `zpool offline|online|clear`: zpool offline --power <pool> <device> Turn off device slot power zpool online --power <pool> <device> Turn on device slot power zpool clear --power <pool> [device] Turn on device slot power If the ZPOOL_AUTO_POWER_ON_SLOT env var is set, then the '--power' option is automatically implied for `zpool online` and `zpool clear` and does not need to be passed. zpool status also gets a --power option to print the slot power status. Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Mart Frauenlob <[email protected]> Signed-off-by: Tony Hutter <[email protected]> Closes #15662
1 parent 6b33ae0 commit 0bc841f

File tree

15 files changed

+794
-48
lines changed

15 files changed

+794
-48
lines changed

cmd/zpool/os/freebsd/zpool_vdev_os.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,17 @@ after_zpool_upgrade(zpool_handle_t *zhp)
116116
"details.\n"), zpool_get_name(zhp));
117117
}
118118
}
119+
120+
int
121+
zpool_power_current_state(zpool_handle_t *zhp, char *vdev)
122+
{
123+
/* Enclosure slot power not supported on FreeBSD yet */
124+
return (-1);
125+
}
126+
127+
int
128+
zpool_power(zpool_handle_t *zhp, char *vdev, boolean_t turn_on)
129+
{
130+
/* Enclosure slot power not supported on FreeBSD yet */
131+
return (ENOTSUP);
132+
}

cmd/zpool/os/linux/zpool_vdev_os.c

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,3 +410,258 @@ void
410410
after_zpool_upgrade(zpool_handle_t *zhp)
411411
{
412412
}
413+
414+
/*
415+
* Read from a sysfs file and return an allocated string. Removes
416+
* the newline from the end of the string if there is one.
417+
*
418+
* Returns a string on success (which must be freed), or NULL on error.
419+
*/
420+
static char *zpool_sysfs_gets(char *path)
421+
{
422+
int fd;
423+
struct stat statbuf;
424+
char *buf = NULL;
425+
ssize_t count = 0;
426+
fd = open(path, O_RDONLY);
427+
if (fd < 0)
428+
return (NULL);
429+
430+
if (fstat(fd, &statbuf) != 0) {
431+
close(fd);
432+
return (NULL);
433+
}
434+
435+
buf = calloc(sizeof (*buf), statbuf.st_size + 1);
436+
if (buf == NULL) {
437+
close(fd);
438+
return (NULL);
439+
}
440+
441+
/*
442+
* Note, we can read less bytes than st_size, and that's ok. Sysfs
443+
* files will report their size is 4k even if they only return a small
444+
* string.
445+
*/
446+
count = read(fd, buf, statbuf.st_size);
447+
if (count < 0) {
448+
/* Error doing read() or we overran the buffer */
449+
close(fd);
450+
free(buf);
451+
return (NULL);
452+
}
453+
454+
/* Remove trailing newline */
455+
if (buf[count - 1] == '\n')
456+
buf[count - 1] = 0;
457+
458+
close(fd);
459+
460+
return (buf);
461+
}
462+
463+
/*
464+
* Write a string to a sysfs file.
465+
*
466+
* Returns 0 on success, non-zero otherwise.
467+
*/
468+
static int zpool_sysfs_puts(char *path, char *str)
469+
{
470+
FILE *file;
471+
472+
file = fopen(path, "w");
473+
if (!file) {
474+
return (-1);
475+
}
476+
477+
if (fputs(str, file) < 0) {
478+
fclose(file);
479+
return (-2);
480+
}
481+
fclose(file);
482+
return (0);
483+
}
484+
485+
/* Given a vdev nvlist_t, rescan its enclosure sysfs path */
486+
static void
487+
rescan_vdev_config_dev_sysfs_path(nvlist_t *vdev_nv)
488+
{
489+
update_vdev_config_dev_sysfs_path(vdev_nv,
490+
fnvlist_lookup_string(vdev_nv, ZPOOL_CONFIG_PATH),
491+
ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH);
492+
}
493+
494+
/*
495+
* Given a power string: "on", "off", "1", or "0", return 0 if it's an
496+
* off value, 1 if it's an on value, and -1 if the value is unrecognized.
497+
*/
498+
static int zpool_power_parse_value(char *str)
499+
{
500+
if ((strcmp(str, "off") == 0) || (strcmp(str, "0") == 0))
501+
return (0);
502+
503+
if ((strcmp(str, "on") == 0) || (strcmp(str, "1") == 0))
504+
return (1);
505+
506+
return (-1);
507+
}
508+
509+
/*
510+
* Given a vdev string return an allocated string containing the sysfs path to
511+
* its power control file. Also do a check if the power control file really
512+
* exists and has correct permissions.
513+
*
514+
* Example returned strings:
515+
*
516+
* /sys/class/enclosure/0:0:122:0/10/power_status
517+
* /sys/bus/pci/slots/10/power
518+
*
519+
* Returns allocated string on success (which must be freed), NULL on failure.
520+
*/
521+
static char *
522+
zpool_power_sysfs_path(zpool_handle_t *zhp, char *vdev)
523+
{
524+
char *enc_sysfs_dir = NULL;
525+
char *path = NULL;
526+
nvlist_t *vdev_nv = zpool_find_vdev(zhp, vdev, NULL, NULL, NULL);
527+
528+
if (vdev_nv == NULL) {
529+
return (NULL);
530+
}
531+
532+
/* Make sure we're getting the updated enclosure sysfs path */
533+
rescan_vdev_config_dev_sysfs_path(vdev_nv);
534+
535+
if (nvlist_lookup_string(vdev_nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH,
536+
&enc_sysfs_dir) != 0) {
537+
return (NULL);
538+
}
539+
540+
if (asprintf(&path, "%s/power_status", enc_sysfs_dir) == -1)
541+
return (NULL);
542+
543+
if (access(path, W_OK) != 0) {
544+
free(path);
545+
path = NULL;
546+
/* No HDD 'power_control' file, maybe it's NVMe? */
547+
if (asprintf(&path, "%s/power", enc_sysfs_dir) == -1) {
548+
return (NULL);
549+
}
550+
551+
if (access(path, R_OK | W_OK) != 0) {
552+
/* Not NVMe either */
553+
free(path);
554+
return (NULL);
555+
}
556+
}
557+
558+
return (path);
559+
}
560+
561+
/*
562+
* Given a path to a sysfs power control file, return B_TRUE if you should use
563+
* "on/off" words to control it, or B_FALSE otherwise ("0/1" to control).
564+
*/
565+
static boolean_t
566+
zpool_power_use_word(char *sysfs_path)
567+
{
568+
if (strcmp(&sysfs_path[strlen(sysfs_path) - strlen("power_status")],
569+
"power_status") == 0) {
570+
return (B_TRUE);
571+
}
572+
return (B_FALSE);
573+
}
574+
575+
/*
576+
* Check the sysfs power control value for a vdev.
577+
*
578+
* Returns:
579+
* 0 - Power is off
580+
* 1 - Power is on
581+
* -1 - Error or unsupported
582+
*/
583+
int
584+
zpool_power_current_state(zpool_handle_t *zhp, char *vdev)
585+
{
586+
char *val;
587+
int rc;
588+
589+
char *path = zpool_power_sysfs_path(zhp, vdev);
590+
if (path == NULL)
591+
return (-1);
592+
593+
val = zpool_sysfs_gets(path);
594+
if (val == NULL) {
595+
free(path);
596+
return (-1);
597+
}
598+
599+
rc = zpool_power_parse_value(val);
600+
free(val);
601+
free(path);
602+
return (rc);
603+
}
604+
605+
/*
606+
* Turn on or off the slot to a device
607+
*
608+
* Device path is the full path to the device (like /dev/sda or /dev/sda1).
609+
*
610+
* Return code:
611+
* 0: Success
612+
* ENOTSUP: Power control not supported for OS
613+
* EBADSLT: Couldn't read current power state
614+
* ENOENT: No sysfs path to power control
615+
* EIO: Couldn't write sysfs power value
616+
* EBADE: Sysfs power value didn't change
617+
*/
618+
int
619+
zpool_power(zpool_handle_t *zhp, char *vdev, boolean_t turn_on)
620+
{
621+
char *sysfs_path;
622+
const char *val;
623+
int rc;
624+
int timeout_ms;
625+
626+
rc = zpool_power_current_state(zhp, vdev);
627+
if (rc == -1) {
628+
return (EBADSLT);
629+
}
630+
631+
/* Already correct value? */
632+
if (rc == (int)turn_on)
633+
return (0);
634+
635+
sysfs_path = zpool_power_sysfs_path(zhp, vdev);
636+
if (sysfs_path == NULL)
637+
return (ENOENT);
638+
639+
if (zpool_power_use_word(sysfs_path)) {
640+
val = turn_on ? "on" : "off";
641+
} else {
642+
val = turn_on ? "1" : "0";
643+
}
644+
645+
rc = zpool_sysfs_puts(sysfs_path, (char *)val);
646+
647+
free(sysfs_path);
648+
if (rc != 0) {
649+
return (EIO);
650+
}
651+
652+
/*
653+
* Wait up to 30 seconds for sysfs power value to change after
654+
* writing it.
655+
*/
656+
timeout_ms = zpool_getenv_int("ZPOOL_POWER_ON_SLOT_TIMEOUT_MS", 30000);
657+
for (int i = 0; i < MAX(1, timeout_ms / 200); i++) {
658+
rc = zpool_power_current_state(zhp, vdev);
659+
if (rc == (int)turn_on)
660+
return (0); /* success */
661+
662+
fsleep(0.200); /* 200ms */
663+
}
664+
665+
/* sysfs value never changed */
666+
return (EBADE);
667+
}

cmd/zpool/zpool_iter.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,10 @@ for_each_vdev_run_cb(void *zhp_data, nvlist_t *nv, void *cb_vcdl)
551551
if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) != 0)
552552
return (1);
553553

554+
/* Make sure we're getting the updated enclosure sysfs path */
555+
update_vdev_config_dev_sysfs_path(nv, path,
556+
ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH);
557+
554558
nvlist_lookup_string(nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH,
555559
&vdev_enc_sysfs_path);
556560

0 commit comments

Comments
 (0)