github.com/jaypipes/ghw@v0.21.1/pkg/block/block.go (about)

     1  //
     2  // Use and distribution licensed under the Apache license version 2.
     3  //
     4  // See the COPYING file in the root project directory for full text.
     5  //
     6  
     7  package block
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  	"math"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/jaypipes/ghw/pkg/context"
    17  	"github.com/jaypipes/ghw/pkg/marshal"
    18  	"github.com/jaypipes/ghw/pkg/option"
    19  	"github.com/jaypipes/ghw/pkg/unitutil"
    20  	"github.com/jaypipes/ghw/pkg/util"
    21  )
    22  
    23  // DriveType describes the general category of drive device
    24  type DriveType int
    25  
    26  const (
    27  	// DriveTypeUnknown means we could not determine the drive type of the disk
    28  	DriveTypeUnknown DriveType = iota
    29  	// DriveTypeHDD indicates a hard disk drive
    30  	DriveTypeHDD
    31  	// DriveTypeFDD indicates a floppy disk drive
    32  	DriveTypeFDD
    33  	// DriveTypeODD indicates an optical disk drive
    34  	DriveTypeODD
    35  	// DriveTypeSSD indicates a solid-state drive
    36  	DriveTypeSSD
    37  	// DriveTypeVirtual indicates a virtual drive i.e. loop devices
    38  	DriveTypeVirtual
    39  )
    40  
    41  const (
    42  	// DEPRECATED: Please use DriveTypeUnknown
    43  	DRIVE_TYPE_UNKNOWN = DriveTypeUnknown
    44  	// DEPRECATED: Please use DriveTypeHDD
    45  	DRIVE_TYPE_HDD = DriveTypeHDD
    46  	// DEPRECATED: Please use DriveTypeFDD
    47  	DRIVE_TYPE_FDD = DriveTypeFDD
    48  	// DEPRECATED: Please use DriveTypeODD
    49  	DRIVE_TYPE_ODD = DriveTypeODD
    50  	// DEPRECATED: Please use DriveTypeSSD
    51  	DRIVE_TYPE_SSD = DriveTypeSSD
    52  	// DEPRECATED: Please use DriveTypeVirtual
    53  	DRIVE_TYPE_VIRTUAL = DriveTypeVirtual
    54  )
    55  
    56  var (
    57  	driveTypeString = map[DriveType]string{
    58  		DriveTypeUnknown: "Unknown",
    59  		DriveTypeHDD:     "HDD",
    60  		DriveTypeFDD:     "FDD",
    61  		DriveTypeODD:     "ODD",
    62  		DriveTypeSSD:     "SSD",
    63  		DriveTypeVirtual: "virtual",
    64  	}
    65  
    66  	// NOTE(fromani): the keys are all lowercase and do not match
    67  	// the keys in the opposite table `driveTypeString`.
    68  	// This is done because of the choice we made in
    69  	// DriveType::MarshalJSON.
    70  	// We use this table only in UnmarshalJSON, so it should be OK.
    71  	stringDriveType = map[string]DriveType{
    72  		"unknown": DriveTypeUnknown,
    73  		"hdd":     DriveTypeHDD,
    74  		"fdd":     DriveTypeFDD,
    75  		"odd":     DriveTypeODD,
    76  		"ssd":     DriveTypeSSD,
    77  		"virtual": DriveTypeVirtual,
    78  	}
    79  )
    80  
    81  func (dt DriveType) String() string {
    82  	return driveTypeString[dt]
    83  }
    84  
    85  // NOTE(jaypipes): since serialized output is as "official" as we're going to
    86  // get, let's lowercase the string output when serializing, in order to
    87  // "normalize" the expected serialized output
    88  func (dt DriveType) MarshalJSON() ([]byte, error) {
    89  	return []byte(strconv.Quote(strings.ToLower(dt.String()))), nil
    90  }
    91  
    92  func (dt *DriveType) UnmarshalJSON(b []byte) error {
    93  	var s string
    94  	if err := json.Unmarshal(b, &s); err != nil {
    95  		return err
    96  	}
    97  	key := strings.ToLower(s)
    98  	val, ok := stringDriveType[key]
    99  	if !ok {
   100  		return fmt.Errorf("unknown drive type: %q", key)
   101  	}
   102  	*dt = val
   103  	return nil
   104  }
   105  
   106  // StorageController is a category of block storage controller/driver. It
   107  // represents more of the physical hardware interface than the storage
   108  // protocol, which represents more of the software interface.
   109  //
   110  // See discussion on https://github.com/jaypipes/ghw/issues/117
   111  type StorageController int
   112  
   113  const (
   114  	// StorageControllerUnknown indicates we could not determine the storage
   115  	// controller for the disk
   116  	StorageControllerUnknown StorageController = iota
   117  	// StorageControllerIDE indicates a Integrated Drive Electronics (IDE)
   118  	// controller
   119  	StorageControllerIDE
   120  	// StorageControllerSCSI indicates a  Small computer system interface
   121  	// (SCSI) controller
   122  	StorageControllerSCSI
   123  	// StorageControllerNVMe indicates a Non-volatile Memory Express (NVMe)
   124  	// controller
   125  	StorageControllerNVMe
   126  	// StorageControllerVirtIO indicates a virtualized storage
   127  	// controller/driver
   128  	StorageControllerVirtIO
   129  	// StorageControllerMMC indicates a Multi-media controller (used for mobile
   130  	// phone storage devices)
   131  	StorageControllerMMC
   132  	// StorageControllerLoop indicates a loopback storage controller
   133  	StorageControllerLoop
   134  )
   135  
   136  const (
   137  	// DEPRECATED: Please use StorageControllerUnknown
   138  	STORAGE_CONTROLLER_UNKNOWN = StorageControllerUnknown
   139  	// DEPRECATED: Please use StorageControllerIDE
   140  	STORAGE_CONTROLLER_IDE = StorageControllerIDE
   141  	// DEPRECATED: Please use StorageControllerSCSI
   142  	STORAGE_CONTROLLER_SCSI = StorageControllerSCSI
   143  	// DEPRECATED: Please use StorageControllerNVMe
   144  	STORAGE_CONTROLLER_NVME = StorageControllerNVMe
   145  	// DEPRECATED: Please use StorageControllerVirtIO
   146  	STORAGE_CONTROLLER_VIRTIO = StorageControllerVirtIO
   147  	// DEPRECATED: Please use StorageControllerMMC
   148  	STORAGE_CONTROLLER_MMC = StorageControllerMMC
   149  	// DEPRECATED: Please use StorageControllerLoop
   150  	STORAGE_CONTROLLER_LOOP = StorageControllerLoop
   151  )
   152  
   153  var (
   154  	storageControllerString = map[StorageController]string{
   155  		StorageControllerUnknown: "Unknown",
   156  		StorageControllerIDE:     "IDE",
   157  		StorageControllerSCSI:    "SCSI",
   158  		StorageControllerNVMe:    "NVMe",
   159  		StorageControllerVirtIO:  "virtio",
   160  		StorageControllerMMC:     "MMC",
   161  		StorageControllerLoop:    "loop",
   162  	}
   163  
   164  	// NOTE(fromani): the keys are all lowercase and do not match
   165  	// the keys in the opposite table `storageControllerString`.
   166  	// This is done/ because of the choice we made in
   167  	// StorageController::MarshalJSON.
   168  	// We use this table only in UnmarshalJSON, so it should be OK.
   169  	stringStorageController = map[string]StorageController{
   170  		"unknown": StorageControllerUnknown,
   171  		"ide":     StorageControllerIDE,
   172  		"scsi":    StorageControllerSCSI,
   173  		"nvme":    StorageControllerNVMe,
   174  		"virtio":  StorageControllerVirtIO,
   175  		"mmc":     StorageControllerMMC,
   176  		"loop":    StorageControllerLoop,
   177  	}
   178  )
   179  
   180  func (sc StorageController) String() string {
   181  	return storageControllerString[sc]
   182  }
   183  
   184  func (sc *StorageController) UnmarshalJSON(b []byte) error {
   185  	var s string
   186  	if err := json.Unmarshal(b, &s); err != nil {
   187  		return err
   188  	}
   189  	key := strings.ToLower(s)
   190  	val, ok := stringStorageController[key]
   191  	if !ok {
   192  		return fmt.Errorf("unknown storage controller: %q", key)
   193  	}
   194  	*sc = val
   195  	return nil
   196  }
   197  
   198  // NOTE(jaypipes): since serialized output is as "official" as we're going to
   199  // get, let's lowercase the string output when serializing, in order to
   200  // "normalize" the expected serialized output
   201  func (sc StorageController) MarshalJSON() ([]byte, error) {
   202  	return []byte(strconv.Quote(strings.ToLower(sc.String()))), nil
   203  }
   204  
   205  // Disk describes a single disk drive on the host system. Disk drives provide
   206  // raw block storage resources.
   207  type Disk struct {
   208  	// Name contains a short name for the disk, e.g. `sda`
   209  	Name string `json:"name"`
   210  	// SizeBytes contains the total amount of storage, in bytes, for this disk
   211  	SizeBytes uint64 `json:"size_bytes"`
   212  	// PhysicalBlockSizeBytes is the size, in bytes, of the physical blocks in
   213  	// this disk. This is typically the minimum amount of data that can be
   214  	// written to a disk in a single write operation.
   215  	PhysicalBlockSizeBytes uint64 `json:"physical_block_size_bytes"`
   216  	// DriveType is the category of disk drive for this disk.
   217  	DriveType DriveType `json:"drive_type"`
   218  	// IsRemovable indicates if the disk drive is removable.
   219  	IsRemovable bool `json:"removable"`
   220  	// StorageController is the category of storage controller used by the
   221  	// disk.
   222  	StorageController StorageController `json:"storage_controller"`
   223  	// BusPath is the filepath to the bus for this disk.
   224  	BusPath string `json:"bus_path"`
   225  	// NUMANodeID contains the numeric index (0-based) of the NUMA Node this
   226  	// disk is affined to, or -1 if the host system is non-NUMA.
   227  	// TODO(jaypipes): Convert this to a TopologyNode struct pointer and then
   228  	// add to serialized output as "numa_node,omitempty"
   229  	NUMANodeID int `json:"-"`
   230  	// Vendor is the manufacturer of the disk.
   231  	Vendor string `json:"vendor"`
   232  	// Model is the model number of the disk.
   233  	Model string `json:"model"`
   234  	// SerialNumber is the serial number of the disk.
   235  	SerialNumber string `json:"serial_number"`
   236  	// WWN is the World-wide Name of the disk.
   237  	// See: https://en.wikipedia.org/wiki/World_Wide_Name
   238  	WWN string `json:"wwn"`
   239  	// WWNNoExtension is the World-wide Name of the disk with any vendor
   240  	// extensions excluded.
   241  	// See: https://en.wikipedia.org/wiki/World_Wide_Name
   242  	WWNNoExtension string `json:"wwnNoExtension"`
   243  	// Partitions contains an array of pointers to `Partition` structs, one for
   244  	// each partition on the disk.
   245  	Partitions []*Partition `json:"partitions"`
   246  	// TODO(jaypipes): Add PCI field for accessing PCI device information
   247  	// PCI *PCIDevice `json:"pci"`
   248  }
   249  
   250  // Partition describes a logical division of a Disk.
   251  type Partition struct {
   252  	// Disk is a pointer to the `Disk` struct that houses this partition.
   253  	Disk *Disk `json:"-"`
   254  	// Name is the system given or user given name to the partition, e.g. "sda1".
   255  	Name string `json:"name"`
   256  	// Label is the human-readable label given to the partition. On Linux, this
   257  	// is derived from the `ID_PART_ENTRY_NAME` udev entry.
   258  	Label string `json:"label"`
   259  	// MountPoint is the path where this partition is mounted.
   260  	MountPoint string `json:"mount_point"`
   261  	// SizeBytes contains the total amount of storage, in bytes, this partition
   262  	// can consume.
   263  	SizeBytes uint64 `json:"size_bytes"`
   264  	// Type contains the type of the partition.
   265  	Type string `json:"type"`
   266  	// IsReadOnly indicates if the partition is marked read-only.
   267  	IsReadOnly bool `json:"read_only"`
   268  	// UUID is a unique identifier for the partition. Note that for Windows
   269  	// partitions, this field contains a Volume Serial Number which is not
   270  	// in the standard UUID format, e.g. "A8C3D032".
   271  	UUID string `json:"uuid"`
   272  	// FilesystemLabel is the label of the filesystem contained on the
   273  	// partition. On Linux, this is derived from the `ID_FS_NAME` udev entry.
   274  	FilesystemLabel string `json:"filesystem_label"`
   275  }
   276  
   277  // Info describes all disk drives and partitions in the host system.
   278  type Info struct {
   279  	ctx *context.Context
   280  	// TotalSizeBytes contains the total amount of storage, in bytes, on the
   281  	// host system.
   282  	TotalSizeBytes uint64 `json:"total_size_bytes"`
   283  	// DEPRECATED: Please use TotalSizeBytes
   284  	TotalPhysicalBytes uint64 `json:"-"`
   285  	// Disks contains an array of pointers to `Disk` structs, one for each disk
   286  	// drive on the host system.
   287  	Disks []*Disk `json:"disks"`
   288  	// Partitions contains an array of pointers to `Partition` structs, one for
   289  	// each partition on any disk drive on the host system.
   290  	Partitions []*Partition `json:"-"`
   291  }
   292  
   293  // New returns a pointer to an Info struct that describes the block storage
   294  // resources of the host system.
   295  func New(opts ...*option.Option) (*Info, error) {
   296  	ctx := context.New(opts...)
   297  	info := &Info{ctx: ctx}
   298  	if err := ctx.Do(info.load); err != nil {
   299  		return nil, err
   300  	}
   301  	return info, nil
   302  }
   303  
   304  // String returns a short string indicating important information about the
   305  // block storage on the host system.
   306  func (i *Info) String() string {
   307  	tpbs := util.UNKNOWN
   308  	if i.TotalPhysicalBytes > 0 {
   309  		tpb := i.TotalPhysicalBytes
   310  		unit, unitStr := unitutil.AmountString(int64(tpb))
   311  		tpb = uint64(math.Ceil(float64(tpb) / float64(unit)))
   312  		tpbs = fmt.Sprintf("%d%s", tpb, unitStr)
   313  	}
   314  	dplural := "disks"
   315  	if len(i.Disks) == 1 {
   316  		dplural = "disk"
   317  	}
   318  	return fmt.Sprintf("block storage (%d %s, %s physical storage)",
   319  		len(i.Disks), dplural, tpbs)
   320  }
   321  
   322  // String returns a short string indicating important information about the
   323  // disk.
   324  func (d *Disk) String() string {
   325  	sizeStr := util.UNKNOWN
   326  	if d.SizeBytes > 0 {
   327  		size := d.SizeBytes
   328  		unit, unitStr := unitutil.AmountString(int64(size))
   329  		size = uint64(math.Ceil(float64(size) / float64(unit)))
   330  		sizeStr = fmt.Sprintf("%d%s", size, unitStr)
   331  	}
   332  	atNode := ""
   333  	if d.NUMANodeID >= 0 {
   334  		atNode = fmt.Sprintf(" (node #%d)", d.NUMANodeID)
   335  	}
   336  	vendor := ""
   337  	if d.Vendor != "" {
   338  		vendor = " vendor=" + d.Vendor
   339  	}
   340  	model := ""
   341  	if d.Model != util.UNKNOWN {
   342  		model = " model=" + d.Model
   343  	}
   344  	serial := ""
   345  	if d.SerialNumber != util.UNKNOWN {
   346  		serial = " serial=" + d.SerialNumber
   347  	}
   348  	wwn := ""
   349  	if d.WWN != util.UNKNOWN {
   350  		wwn = " WWN=" + d.WWN
   351  	}
   352  	removable := ""
   353  	if d.IsRemovable {
   354  		removable = " removable=true"
   355  	}
   356  	return fmt.Sprintf(
   357  		"%s %s (%s) %s [@%s%s]%s",
   358  		d.Name,
   359  		d.DriveType.String(),
   360  		sizeStr,
   361  		d.StorageController.String(),
   362  		d.BusPath,
   363  		atNode,
   364  		util.ConcatStrings(
   365  			vendor,
   366  			model,
   367  			serial,
   368  			wwn,
   369  			removable,
   370  		),
   371  	)
   372  }
   373  
   374  // String returns a short string indicating important information about the
   375  // partition.
   376  func (p *Partition) String() string {
   377  	typeStr := ""
   378  	if p.Type != "" {
   379  		typeStr = fmt.Sprintf("[%s]", p.Type)
   380  	}
   381  	mountStr := ""
   382  	if p.MountPoint != "" {
   383  		mountStr = fmt.Sprintf(" mounted@%s", p.MountPoint)
   384  	}
   385  	sizeStr := util.UNKNOWN
   386  	if p.SizeBytes > 0 {
   387  		size := p.SizeBytes
   388  		unit, unitStr := unitutil.AmountString(int64(size))
   389  		size = uint64(math.Ceil(float64(size) / float64(unit)))
   390  		sizeStr = fmt.Sprintf("%d%s", size, unitStr)
   391  	}
   392  	return fmt.Sprintf(
   393  		"%s (%s) %s%s",
   394  		p.Name,
   395  		sizeStr,
   396  		typeStr,
   397  		mountStr,
   398  	)
   399  }
   400  
   401  // simple private struct used to encapsulate block information in a top-level
   402  // "block" YAML/JSON map/object key
   403  type blockPrinter struct {
   404  	Info *Info `json:"block" yaml:"block"`
   405  }
   406  
   407  // YAMLString returns a string with the block information formatted as YAML
   408  // under a top-level "block:" key
   409  func (i *Info) YAMLString() string {
   410  	return marshal.SafeYAML(i.ctx, blockPrinter{i})
   411  }
   412  
   413  // JSONString returns a string with the block information formatted as JSON
   414  // under a top-level "block:" key
   415  func (i *Info) JSONString(indent bool) string {
   416  	return marshal.SafeJSON(i.ctx, blockPrinter{i}, indent)
   417  }