forked from jaypipes/ghw
-
Notifications
You must be signed in to change notification settings - Fork 0
/
block_darwin.go
297 lines (263 loc) · 10.1 KB
/
block_darwin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//
package ghw
import (
"fmt"
"os"
"os/exec"
"path"
"strings"
"github.com/pkg/errors"
"howett.net/plist"
)
type diskOrPartitionPlistNode struct {
Content string
DeviceIdentifier string
DiskUUID string
VolumeName string
VolumeUUID string
Size int64
MountPoint string
Partitions []diskOrPartitionPlistNode
APFSVolumes []diskOrPartitionPlistNode
}
type diskUtilListPlist struct {
AllDisks []string
AllDisksAndPartitions []diskOrPartitionPlistNode
VolumesFromDisks []string
WholeDisks []string
}
type diskUtilInfoPlist struct {
AESHardware bool // true
Bootable bool // true
BooterDeviceIdentifier string // disk1s2
BusProtocol string // PCI-Express
CanBeMadeBootable bool // false
CanBeMadeBootableRequiresDestroy bool // false
Content string // some-uuid-foo-bar
DeviceBlockSize int64 // 4096
DeviceIdentifier string // disk1s1
DeviceNode string // /dev/disk1s1
DeviceTreePath string // IODeviceTree:/PCI0@0/RP17@1B/ANS2@0/AppleANS2Controller
DiskUUID string // some-uuid-foo-bar
Ejectable bool // false
EjectableMediaAutomaticUnderSoftwareControl bool // false
EjectableOnly bool // false
FilesystemName string // APFS
FilesystemType string // apfs
FilesystemUserVisibleName string // APFS
FreeSpace int64 // 343975677952
GlobalPermissionsEnabled bool // true
IOKitSize int64 // 499963174912
IORegistryEntryName string // Macintosh HD
Internal bool // true
MediaName string //
MediaType string // Generic
MountPoint string // /
ParentWholeDisk string // disk1
PartitionMapPartition bool // false
RAIDMaster bool // false
RAIDSlice bool // false
RecoveryDeviceIdentifier string // disk1s3
Removable bool // false
RemovableMedia bool // false
RemovableMediaOrExternalDevice bool // false
SMARTStatus string // Verified
Size int64 // 499963174912
SolidState bool // true
SupportsGlobalPermissionsDisable bool // true
SystemImage bool // false
TotalSize int64 // 499963174912
VolumeAllocationBlockSize int64 // 4096
VolumeName string // Macintosh HD
VolumeSize int64 // 499963174912
VolumeUUID string // some-uuid-foo-bar
WholeDisk bool // false
Writable bool // true
WritableMedia bool // true
WritableVolume bool // true
// also has a SMARTDeviceSpecificKeysMayVaryNotGuaranteed dict with various info
// NOTE: VolumeUUID sometimes == DiskUUID, but not always. So far Content is always a different UUID.
}
type ioregPlist struct {
// there's a lot more than just this...
ModelNumber string `plist:"Model Number"`
SerialNumber string `plist:"Serial Number"`
VendorName string `plist:"Vendor Name"`
}
func (ctx *context) getDiskUtilListPlist() (*diskUtilListPlist, error) {
out, err := exec.Command("diskutil", "list", "-plist").Output()
if err != nil {
return nil, errors.Wrap(err, "diskutil list failed")
}
var data diskUtilListPlist
if _, err := plist.Unmarshal(out, &data); err != nil {
return nil, errors.Wrap(err, "diskutil list plist unmarshal failed")
}
return &data, nil
}
func (ctx *context) getDiskUtilInfoPlist(device string) (*diskUtilInfoPlist, error) {
out, err := exec.Command("diskutil", "info", "-plist", device).Output()
if err != nil {
return nil, errors.Wrapf(err, "diskutil info for %q failed", device)
}
var data diskUtilInfoPlist
if _, err := plist.Unmarshal(out, &data); err != nil {
return nil, errors.Wrapf(err, "diskutil info plist unmarshal for %q failed", device)
}
return &data, nil
}
func (ctx *context) getIoregPlist(ioDeviceTreePath string) (*ioregPlist, error) {
name := path.Base(ioDeviceTreePath)
args := []string{
"ioreg",
"-a", // use XML output
"-d", "1", // limit device tree output depth to root node
"-r", // root device tree at matched node
"-n", name, // match by name
}
out, err := exec.Command(args[0], args[1:]...).Output()
if err != nil {
return nil, errors.Wrapf(err, "ioreg query for %q failed", ioDeviceTreePath)
}
if out == nil || len(out) == 0 {
return nil, nil
}
var data []ioregPlist
if _, err := plist.Unmarshal(out, &data); err != nil {
return nil, errors.Wrapf(err, "ioreg unmarshal for %q failed", ioDeviceTreePath)
}
if len(data) != 1 {
err := errors.Errorf("ioreg unmarshal resulted in %d I/O device tree nodes (expected 1)", len(data))
return nil, err
}
return &data[0], nil
}
func (ctx *context) makePartition(disk, s diskOrPartitionPlistNode, isAPFS bool) (*Partition, error) {
if s.Size < 0 {
return nil, errors.Errorf("invalid size %q of partition %q", s.Size, s.DeviceIdentifier)
}
var partType string
if isAPFS {
partType = "APFS Volume"
} else {
partType = s.Content
}
info, err := ctx.getDiskUtilInfoPlist(s.DeviceIdentifier)
if err != nil {
return nil, err
}
return &Partition{
Disk: nil, // filled in later
Name: s.DeviceIdentifier,
Label: s.VolumeName,
MountPoint: s.MountPoint,
SizeBytes: uint64(s.Size),
Type: partType,
IsReadOnly: !info.WritableVolume,
}, nil
}
// driveTypeFromPlist looks at the supplied property list struct and attempts to
// determine the disk type
func (ctx *context) driveTypeFromPlist(
infoPlist *diskUtilInfoPlist,
) DriveType {
dt := DRIVE_TYPE_HDD
if infoPlist.SolidState {
dt = DRIVE_TYPE_SSD
}
// TODO(jaypipes): Figure out how to determine floppy and/or CD/optical
// drive type on Mac
return dt
}
// storageControllerFromPlist looks at the supplied property list struct and
// attempts to determine the storage controller in use for the device
func (ctx *context) storageControllerFromPlist(
infoPlist *diskUtilInfoPlist,
) StorageController {
sc := STORAGE_CONTROLLER_SCSI
if strings.HasSuffix(infoPlist.DeviceTreePath, "IONVMeController") {
sc = STORAGE_CONTROLLER_NVME
}
// TODO(jaypipes): I don't know if Mac even supports IDE controllers and
// the "virtio" controller is libvirt-specific
return sc
}
// busTypeFromPlist looks at the supplied property list struct and attempts to
// determine the bus type in use for the device
func (ctx *context) busTypeFromPlist(
infoPlist *diskUtilInfoPlist,
) BusType {
// TODO(jaypipes): Find out if Macs support any bus other than
// PCIe... it doesn't seem like they do
return BUS_TYPE_PCI
}
func (ctx *context) blockFillInfo(info *BlockInfo) error {
listPlist, err := ctx.getDiskUtilListPlist()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return err
}
info.TotalPhysicalBytes = 0
info.Disks = make([]*Disk, 0, len(listPlist.AllDisksAndPartitions))
info.Partitions = []*Partition{}
for _, disk := range listPlist.AllDisksAndPartitions {
if disk.Size < 0 {
return errors.Errorf("invalid size %q of disk %q", disk.Size, disk.DeviceIdentifier)
}
infoPlist, err := ctx.getDiskUtilInfoPlist(disk.DeviceIdentifier)
if err != nil {
return err
}
if infoPlist.DeviceBlockSize < 0 {
return errors.Errorf("invalid block size %q of disk %q", infoPlist.DeviceBlockSize, disk.DeviceIdentifier)
}
busPath := strings.TrimPrefix(infoPlist.DeviceTreePath, "IODeviceTree:")
ioregPlist, err := ctx.getIoregPlist(infoPlist.DeviceTreePath)
if err != nil {
return err
}
if ioregPlist == nil {
continue
}
// The NUMA node & WWN don't seem to be reported by any tools available by default in macOS.
diskReport := &Disk{
Name: disk.DeviceIdentifier,
SizeBytes: uint64(disk.Size),
PhysicalBlockSizeBytes: uint64(infoPlist.DeviceBlockSize),
DriveType: ctx.driveTypeFromPlist(infoPlist),
IsRemovable: infoPlist.Removable,
StorageController: ctx.storageControllerFromPlist(infoPlist),
BusType: ctx.busTypeFromPlist(infoPlist),
BusPath: busPath,
NUMANodeID: -1,
Vendor: ioregPlist.VendorName,
Model: ioregPlist.ModelNumber,
SerialNumber: ioregPlist.SerialNumber,
WWN: "",
Partitions: make([]*Partition, 0, len(disk.Partitions)+len(disk.APFSVolumes)),
}
for _, partition := range disk.Partitions {
part, err := ctx.makePartition(disk, partition, false)
if err != nil {
return err
}
part.Disk = diskReport
diskReport.Partitions = append(diskReport.Partitions, part)
}
for _, volume := range disk.APFSVolumes {
part, err := ctx.makePartition(disk, volume, true)
if err != nil {
return err
}
part.Disk = diskReport
diskReport.Partitions = append(diskReport.Partitions, part)
}
info.TotalPhysicalBytes += uint64(disk.Size)
info.Disks = append(info.Disks, diskReport)
info.Partitions = append(info.Partitions, diskReport.Partitions...)
}
return nil
}