Skip to content

Commit f91d3eb

Browse files
committed
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 `-0|-1` flags are added to `zpool offline|online|clear`: zpool offline -0 <pool> <device> Turn off device slot power zpool online -1 <pool> <device> Turn on device slot power zpool clear -1 <pool> [device] Turn on device slot power If the ZPOOL_AUTO_POWER_ON_SLOT env var is set, then the '-1' is automatically implied for `zpool online` and `zpool clear` and does not need to be passed. `zpool status` also gets a `-1` option to print the slot power status. Signed-off-by: Tony Hutter <[email protected]>
1 parent 687e4d7 commit f91d3eb

File tree

15 files changed

+716
-43
lines changed

15 files changed

+716
-43
lines changed

cmd/zpool/os/freebsd/zpool_vdev_os.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,17 @@ check_file(const char *file, boolean_t force, boolean_t isspare)
124124
{
125125
return (check_file_generic(file, force, isspare));
126126
}
127+
128+
int
129+
zpool_power_current_state(zpool_handle_t *zhp, char *vdev)
130+
{
131+
/* Enclosure slot power not supported on FreeBSD yet */
132+
return (-1);
133+
}
134+
135+
int
136+
zpool_power(zpool_handle_t *zhp, char *vdev, boolean_t turn_on)
137+
{
138+
/* Enclosure slot power not supported on FreeBSD yet */
139+
return (ENOTSUP);
140+
}

cmd/zpool/os/linux/zpool_vdev_os.c

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

cmd/zpool/zpool_iter.c

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

557+
/* Make sure we're getting the updated enclosure sysfs path */
558+
update_vdev_config_dev_sysfs_path(nv, path,
559+
ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH);
560+
557561
nvlist_lookup_string(nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH,
558562
&vdev_enc_sysfs_path);
559563

0 commit comments

Comments
 (0)