@@ -410,3 +410,258 @@ void
410
410
after_zpool_upgrade (zpool_handle_t * zhp )
411
411
{
412
412
}
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
+ }
0 commit comments