@@ -416,3 +416,257 @@ check_file(const char *file, boolean_t force, boolean_t isspare)
416
416
{
417
417
return (check_file_generic (file , force , isspare ));
418
418
}
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
+ }
0 commit comments