2525#include <getopt.h>
2626#include <dirent.h>
2727#include <stdbool.h>
28+ #include "kernel-lib/list_sort.h"
2829#include "kernel-shared/zoned.h"
2930#include "common/string-table.h"
3031#include "common/utils.h"
@@ -585,10 +586,13 @@ static const char * const cmd_device_stats_usage[] = {
585586 "btrfs device stats [options] <path>|<device>" ,
586587 "Show device IO error statistics" ,
587588 "Show device IO error statistics for all devices of the given filesystem" ,
588- "identified by PATH or DEVICE. The filesystem must be mounted." ,
589+ "identified by PATH or DEVICE. On a mounted filesystem the stats are read" ,
590+ "using ioctls, for direct read from file images or block devices use the" ,
591+ "option --offline." ,
589592 "" ,
590593 OPTLINE ("-c|--check" , "return non-zero if any stat counter is not zero" ),
591594 OPTLINE ("-z|--reset" , "show current stats and reset values to zero" ),
595+ OPTLINE ("--offline" , "read stats from file or device directly (not from mounted filesystem)" ),
592596 OPTLINE ("-T" , "show current stats in tabular format" ),
593597 HELPINFO_INSERT_GLOBALS ,
594598 HELPINFO_INSERT_FORMAT ,
@@ -701,28 +705,132 @@ static int print_device_stat_tabular(struct string_table *table, int row,
701705 return err ;
702706}
703707
708+ /*
709+ * Offline version of GET_DEV_STATS ioctl.
710+ */
711+ static int get_device_stats_offline (struct btrfs_fs_info * fs_info , u64 devid ,
712+ struct btrfs_ioctl_get_dev_stats * stats_args )
713+ {
714+ int ret = 0 ;
715+ struct btrfs_path path = { 0 };
716+ struct btrfs_key key ;
717+ struct btrfs_dev_stats_item * stats_item ;
718+
719+ key .objectid = BTRFS_DEV_STATS_OBJECTID ;
720+ key .type = BTRFS_PERSISTENT_ITEM_KEY ;
721+ key .offset = devid ;
722+ ret = btrfs_search_slot (NULL , fs_info -> dev_root , & key , & path , 0 , 0 );
723+ if (ret < 0 )
724+ goto out ;
725+ if (ret > 0 ) {
726+ pr_verbose (LOG_DEBUG , "no device stats found for devid %llu\n" , devid );
727+ ret = 0 ;
728+ goto out ;
729+ }
730+
731+ memset (stats_args -> values , 0 , stats_args -> nr_items * sizeof (u64 ));
732+ stats_item = btrfs_item_ptr (path .nodes [0 ], path .slots [0 ], struct btrfs_dev_stats_item );
733+
734+ stats_args -> values [BTRFS_DEV_STAT_WRITE_ERRS ] =
735+ btrfs_dev_stats_value (path .nodes [0 ], stats_item , BTRFS_DEV_STAT_WRITE_ERRS );
736+ stats_args -> values [BTRFS_DEV_STAT_READ_ERRS ] =
737+ btrfs_dev_stats_value (path .nodes [0 ], stats_item , BTRFS_DEV_STAT_READ_ERRS );
738+ stats_args -> values [BTRFS_DEV_STAT_FLUSH_ERRS ] =
739+ btrfs_dev_stats_value (path .nodes [0 ], stats_item , BTRFS_DEV_STAT_FLUSH_ERRS );
740+ stats_args -> values [BTRFS_DEV_STAT_CORRUPTION_ERRS ] =
741+ btrfs_dev_stats_value (path .nodes [0 ], stats_item , BTRFS_DEV_STAT_CORRUPTION_ERRS );
742+ stats_args -> values [BTRFS_DEV_STAT_GENERATION_ERRS ] =
743+ btrfs_dev_stats_value (path .nodes [0 ], stats_item , BTRFS_DEV_STAT_GENERATION_ERRS );
744+
745+ out :
746+ btrfs_release_path (& path );
747+ return ret ;
748+ }
749+
750+ /*
751+ * Offline version of get_fs_info() with limitations:
752+ *
753+ * - no seeding device support
754+ * - fi_args filled only partially (num_devices)
755+ */
756+ static int get_fs_info_offline (struct btrfs_fs_info * fs_info ,
757+ struct btrfs_ioctl_fs_info_args * fi_args ,
758+ struct btrfs_ioctl_dev_info_args * * di_ret )
759+ {
760+ int i ;
761+ struct btrfs_ioctl_dev_info_args * di_args ;
762+ struct btrfs_fs_devices * cur_fs ;
763+ struct btrfs_device * device ;
764+ struct list_head * fs_uuids ;
765+
766+ fs_uuids = btrfs_scanned_uuids ();
767+ /* Count devices from fs_devices in case some of them are missing */
768+ fi_args -> num_devices = 0 ;
769+ list_for_each_entry (cur_fs , fs_uuids , fs_list ) {
770+ if (memcmp (fs_info -> fs_devices -> fsid , cur_fs -> fsid , BTRFS_FSID_SIZE ) != 0 )
771+ continue ;
772+ list_for_each_entry (device , & cur_fs -> devices , dev_list ) {
773+ fi_args -> num_devices ++ ;
774+ }
775+ }
776+
777+ di_args = malloc (fi_args -> num_devices * sizeof (* di_args ));
778+ if (!di_args )
779+ return - ENOMEM ;
780+
781+ i = 0 ;
782+ list_sort (NULL , & fs_info -> fs_devices -> devices , cmp_device_id );
783+ list_for_each_entry (cur_fs , fs_uuids , fs_list ) {
784+ if (memcmp (fs_info -> fs_devices -> fsid , cur_fs -> fsid , BTRFS_FSID_SIZE ) != 0 )
785+ continue ;
786+
787+ list_for_each_entry (device , & cur_fs -> devices , dev_list ) {
788+ di_args [i ].devid = device -> devid ;
789+ memcpy (di_args [i ].uuid , device -> uuid , BTRFS_FSID_SIZE );
790+ di_args [i ].bytes_used = device -> bytes_used ;
791+ di_args [i ].total_bytes = device -> total_bytes ;
792+ memcpy (di_args [i ].fsid , cur_fs -> fsid , BTRFS_FSID_SIZE );
793+ /*
794+ * File devices without loop device or a missing device
795+ * don't have a path.
796+ */
797+ if (device -> name )
798+ strncpy_null ((char * )di_args [i ].path , device -> name ,
799+ BTRFS_DEVICE_PATH_NAME_MAX + 1 );
800+ i ++ ;
801+ }
802+ }
803+
804+ * di_ret = di_args ;
805+ return 0 ;
806+ }
807+
704808static int cmd_device_stats (const struct cmd_struct * cmd , int argc , char * * argv )
705809{
706810 char * dev_path ;
707811 struct btrfs_ioctl_fs_info_args fi_args ;
708812 struct btrfs_ioctl_dev_info_args * di_args = NULL ;
813+ struct btrfs_fs_info * fs_info = NULL ;
709814 struct string_table * table = NULL ;
710815 int ret ;
711- int fdmnt ;
816+ int fdmnt = -1 ;
712817 int i ;
713818 int err = 0 ;
714819 bool check = false;
715820 bool free_table = false;
716821 bool tabular = false;
822+ bool opt_offline = false;
717823 __u64 flags = 0 ;
718824 struct format_ctx fctx ;
719825
720826 optind = 0 ;
721827 while (1 ) {
722828 int c ;
829+ enum { GETOPT_VAL_OFFLINE = GETOPT_VAL_FIRST };
723830 static const struct option long_options [] = {
724831 {"check" , no_argument , NULL , 'c' },
725832 {"reset" , no_argument , NULL , 'z' },
833+ {"offline" , no_argument , NULL , GETOPT_VAL_OFFLINE },
726834 {NULL , 0 , NULL , 0 }
727835 };
728836
@@ -740,6 +848,9 @@ static int cmd_device_stats(const struct cmd_struct *cmd, int argc, char **argv)
740848 case 'T' :
741849 tabular = true;
742850 break ;
851+ case GETOPT_VAL_OFFLINE :
852+ opt_offline = true;
853+ break ;
743854 default :
744855 usage_unknown_option (cmd , argv );
745856 }
@@ -748,19 +859,48 @@ static int cmd_device_stats(const struct cmd_struct *cmd, int argc, char **argv)
748859 if (check_argc_exact (argc - optind , 1 ))
749860 return 1 ;
750861
862+ if (opt_offline && (flags & BTRFS_DEV_STATS_RESET )) {
863+ error ("--offline and --reset cannot be used together" );
864+ return 1 ;
865+ }
866+
751867 dev_path = argv [optind ];
752868
753- fdmnt = btrfs_open_mnt (dev_path );
754- if (fdmnt < 0 )
755- return 1 ;
869+ if (!opt_offline ) {
870+ fdmnt = btrfs_open_mnt (dev_path );
871+ if (fdmnt < 0 ) {
872+ if (fdmnt == - ENOTDIR && !opt_offline )
873+ error ("to read device stats from a file image use --offline option" );
874+ return 1 ;
875+ }
756876
757- ret = get_fs_info (dev_path , & fi_args , & di_args );
758- if (ret ) {
759- errno = - ret ;
760- error ("getting device info for %s failed: %m" , dev_path );
761- err = 1 ;
762- goto out ;
877+ ret = get_fs_info (dev_path , & fi_args , & di_args );
878+ if (ret ) {
879+ errno = - ret ;
880+ error ("getting device info for %s failed: %m" , dev_path );
881+ err = 1 ;
882+ goto out ;
883+ }
884+ } else {
885+ struct open_ctree_args oca = { 0 };
886+
887+ oca .filename = dev_path ;
888+ fs_info = open_ctree_fs_info (& oca );
889+ if (!fs_info ) {
890+ error ("cannot open filesystem on %s" , dev_path );
891+ err = 1 ;
892+ goto out ;
893+ }
894+
895+ ret = get_fs_info_offline (fs_info , & fi_args , & di_args );
896+ if (ret < 0 ) {
897+ errno = - ret ;
898+ error ("getting device info for %s failed: %m" , dev_path );
899+ err = 1 ;
900+ goto out ;
901+ }
763902 }
903+
764904 if (!fi_args .num_devices ) {
765905 error ("no devices found" );
766906 err = 1 ;
@@ -802,13 +942,28 @@ static int cmd_device_stats(const struct cmd_struct *cmd, int argc, char **argv)
802942
803943 args .devid = di_args [i ].devid ;
804944 args .nr_items = BTRFS_DEV_STAT_VALUES_MAX ;
805- args .flags = flags ;
945+ if (!opt_offline ) {
946+ args .flags = flags ;
806947
807- if (ioctl (fdmnt , BTRFS_IOC_GET_DEV_STATS , & args ) < 0 ) {
808- error ("device stats ioctl failed on %s: %m" ,
809- path );
810- err |= 1 ;
811- goto out ;
948+ if (ioctl (fdmnt , BTRFS_IOC_GET_DEV_STATS , & args ) < 0 ) {
949+ error ("device stats ioctl failed on %s: %m" ,
950+ path );
951+ err |= 1 ;
952+ goto out ;
953+ }
954+ } else {
955+ if (args .flags != 0 ) {
956+ error ("no flags supported" );
957+ err = - EINVAL ;
958+ goto out ;
959+ }
960+ ret = get_device_stats_offline (fs_info , di_args [i ].devid , & args );
961+ if (ret < 0 ) {
962+ error ("cannot read offline stats for devid %llu" ,
963+ di_args [i ].devid );
964+ err = ret ;
965+ goto out ;
966+ }
812967 }
813968
814969 if (tabular )
@@ -836,6 +991,8 @@ static int cmd_device_stats(const struct cmd_struct *cmd, int argc, char **argv)
836991out :
837992 free (di_args );
838993 close (fdmnt );
994+ if (fs_info )
995+ close_ctree_fs_info (fs_info );
839996 if (free_table )
840997 table_free (table );
841998
0 commit comments