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 }