github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/pkg/storage/blockdev.go (about) 1 // Copyright 2017-2019 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package storage 6 7 import ( 8 "bufio" 9 "encoding/binary" 10 "errors" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "log" 15 "os" 16 "path" 17 "path/filepath" 18 "strconv" 19 "strings" 20 21 "github.com/rekby/gpt" 22 "github.com/u-root/u-root/pkg/mount" 23 ) 24 25 var ( 26 // LinuxMountsPath is the standard mountpoint list path 27 LinuxMountsPath = "/proc/mounts" 28 ) 29 30 // BlockDev maps a device name to a BlockStat structure for a given block device 31 type BlockDev struct { 32 Name string 33 FSType string 34 Stat BlockStat 35 FsUUID string 36 } 37 38 // String implements fmt.Stringer. 39 func (b BlockDev) String() string { 40 return fmt.Sprintf("BlockDevice(name=%s, fs_uuid=%s)", b.Name, b.FsUUID) 41 } 42 43 // Mount implements mount.Mounter. 44 func (b BlockDev) Mount(path string, flags uintptr) (*mount.MountPoint, error) { 45 devpath := filepath.Join("/dev", b.Name) 46 if len(b.FSType) > 0 { 47 return mount.Mount(devpath, path, b.FSType, "", flags) 48 } 49 50 return mount.TryMount(devpath, path, flags) 51 } 52 53 // Summary prints a multiline summary of the BlockDev object 54 // https://www.kernel.org/doc/Documentation/block/stat.txt 55 func (b BlockDev) Summary() string { 56 return fmt.Sprintf(`BlockStat{ 57 Name: %v 58 ReadIOs: %v 59 ReadMerges: %v 60 ReadSectors: %v 61 ReadTicks: %v 62 WriteIOs: %v 63 WriteMerges: %v 64 WriteSectors: %v 65 WriteTicks: %v 66 InFlight: %v 67 IOTicks: %v 68 TimeInQueue: %v 69 }`, 70 b.Name, 71 b.Stat.ReadIOs, 72 b.Stat.ReadMerges, 73 b.Stat.ReadSectors, 74 b.Stat.ReadTicks, 75 b.Stat.WriteIOs, 76 b.Stat.WriteMerges, 77 b.Stat.WriteSectors, 78 b.Stat.WriteTicks, 79 b.Stat.InFlight, 80 b.Stat.IOTicks, 81 b.Stat.TimeInQueue, 82 ) 83 } 84 85 // BlockStat provides block device information as contained in 86 // /sys/class/block/<device_name>/stat 87 type BlockStat struct { 88 ReadIOs uint64 89 ReadMerges uint64 90 ReadSectors uint64 91 ReadTicks uint64 92 WriteIOs uint64 93 WriteMerges uint64 94 WriteSectors uint64 95 WriteTicks uint64 96 InFlight uint64 97 IOTicks uint64 98 TimeInQueue uint64 99 // Kernel 4.18 added four fields for discard tracking, see 100 // https://github.com/torvalds/linux/commit/bdca3c87fb7ad1cc61d231d37eb0d8f90d001e0c 101 DiscardIOs uint64 102 DiscardMerges uint64 103 DiscardSectors uint64 104 DiscardTicks uint64 105 } 106 107 // SystemPartitionGUID is the GUID of EFI system partitions 108 // EFI System partitions have GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B 109 var SystemPartitionGUID = gpt.Guid([...]byte{ 110 0x28, 0x73, 0x2a, 0xc1, 111 0x1f, 0xf8, 112 0xd2, 0x11, 113 0xba, 0x4b, 114 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b, 115 }) 116 117 // BlockStatFromBytes parses a block stat file and returns a BlockStat object. 118 // The format of the block stat file is the one defined by Linux for 119 // /sys/class/block/<device_name>/stat 120 func BlockStatFromBytes(buf []byte) (*BlockStat, error) { 121 fields := strings.Fields(string(buf)) 122 // BlockStat has 11 fields 123 if len(fields) < 11 { 124 return nil, fmt.Errorf("BlockStatFromBytes: parsing %q: got %d fields(%q), want at least 11", buf, len(fields), fields) 125 } 126 intfields := make([]uint64, 0) 127 for _, field := range fields { 128 v, err := strconv.ParseUint(field, 10, 64) 129 if err != nil { 130 return nil, err 131 } 132 intfields = append(intfields, v) 133 } 134 bs := BlockStat{ 135 ReadIOs: intfields[0], 136 ReadMerges: intfields[1], 137 ReadSectors: intfields[2], 138 ReadTicks: intfields[3], 139 WriteIOs: intfields[4], 140 WriteMerges: intfields[5], 141 WriteSectors: intfields[6], 142 WriteTicks: intfields[7], 143 InFlight: intfields[8], 144 IOTicks: intfields[9], 145 TimeInQueue: intfields[10], 146 } 147 if len(fields) >= 15 { 148 bs.DiscardIOs = intfields[11] 149 bs.DiscardMerges = intfields[12] 150 bs.DiscardSectors = intfields[13] 151 bs.DiscardTicks = intfields[14] 152 } 153 return &bs, nil 154 } 155 156 // GetBlockStats iterates over /sys/class/block entries and returns a list of 157 // BlockDev objects, or an error if any 158 func GetBlockStats() ([]BlockDev, error) { 159 blockdevs := make([]BlockDev, 0) 160 devnames := make([]string, 0) 161 root := "/sys/class/block" 162 err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 163 if err != nil { 164 return err 165 } 166 rel, err := filepath.Rel(root, path) 167 if err != nil { 168 return err 169 } 170 if rel == "." { 171 return nil 172 } 173 devnames = append(devnames, rel) 174 return nil 175 }) 176 if err != nil { 177 return nil, err 178 } 179 for _, devname := range devnames { 180 buf, err := ioutil.ReadFile(fmt.Sprintf("%s/%s/stat", root, devname)) 181 if err != nil { 182 return nil, err 183 } 184 bstat, err := BlockStatFromBytes(buf) 185 if err != nil { 186 return nil, err 187 } 188 devpath := path.Join("/dev/", devname) 189 if uuid, err := getUUID(devpath); err != nil { 190 blockdevs = append(blockdevs, BlockDev{Name: devname, Stat: *bstat}) 191 } else { 192 blockdevs = append(blockdevs, BlockDev{Name: devname, Stat: *bstat, FsUUID: uuid}) 193 } 194 } 195 return blockdevs, nil 196 } 197 198 func getUUID(devpath string) (string, error) { 199 file, err := os.Open(devpath) 200 if err != nil { 201 return "", err 202 } 203 defer file.Close() 204 205 fsuuid, err := tryVFAT(file) 206 if err == nil { 207 return fsuuid, nil 208 } 209 fsuuid, err = tryEXT4(file) 210 if err == nil { 211 return fsuuid, nil 212 } 213 fsuuid, err = tryXFS(file) 214 if err == nil { 215 return fsuuid, nil 216 } 217 return "", fmt.Errorf("unknown UUID (not vfat, ext4, nor xfs)") 218 } 219 220 // See https://www.nongnu.org/ext2-doc/ext2.html#DISK-ORGANISATION. 221 const ( 222 // Offset of superblock in partition. 223 ext2SprblkOff = 1024 224 225 // Offset of magic number in suberblock. 226 ext2SprblkMagicOff = 56 227 ext2SprblkMagicSize = 2 228 229 ext2SprblkMagic = 0xEF53 230 231 // Offset of UUID in superblock. 232 ext2SprblkUUIDOff = 104 233 ext2SprblkUUIDSize = 16 234 ) 235 236 func tryEXT4(file io.ReaderAt) (string, error) { 237 var off int64 238 239 // Read magic number. 240 b := make([]byte, ext2SprblkMagicSize) 241 off = ext2SprblkOff + ext2SprblkMagicOff 242 if _, err := file.ReadAt(b, off); err != nil { 243 return "", err 244 } 245 magic := binary.LittleEndian.Uint16(b[:2]) 246 if magic != ext2SprblkMagic { 247 return "", fmt.Errorf("ext4 magic not found") 248 } 249 250 // Filesystem UUID. 251 b = make([]byte, ext2SprblkUUIDSize) 252 off = ext2SprblkOff + ext2SprblkUUIDOff 253 if _, err := file.ReadAt(b, off); err != nil { 254 return "", err 255 } 256 257 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil 258 } 259 260 // See https://de.wikipedia.org/wiki/File_Allocation_Table#Aufbau. 261 const ( 262 fat32Magic = "FAT32 " 263 264 // Offset of magic number. 265 fat32MagicOff = 82 266 fat32MagicSize = 8 267 268 // Offset of filesystem ID / serial number. Treated as short filesystem UUID. 269 fat32IDOff = 67 270 fat32IDSize = 4 271 ) 272 273 func tryVFAT(file io.ReaderAt) (string, error) { 274 // Read magic number. 275 b := make([]byte, fat32MagicSize) 276 if _, err := file.ReadAt(b, fat32MagicOff); err != nil { 277 return "", err 278 } 279 magic := string(b) 280 if magic != fat32Magic { 281 return "", fmt.Errorf("fat32 magic not found") 282 } 283 284 // Filesystem UUID. 285 b = make([]byte, fat32IDSize) 286 if _, err := file.ReadAt(b, fat32IDOff); err != nil { 287 return "", err 288 } 289 290 return fmt.Sprintf("%02x%02x-%02x%02x", b[3], b[2], b[1], b[0]), nil 291 } 292 293 const ( 294 xfsMagic = "XFSB" 295 xfsMagicSize = 4 296 xfsUUIDOff = 32 297 xfsUUIDSize = 16 298 ) 299 300 func tryXFS(file io.ReaderAt) (string, error) { 301 // Read magic number. 302 b := make([]byte, xfsMagicSize) 303 if _, err := file.ReadAt(b, 0); err != nil { 304 return "", err 305 } 306 magic := string(b) 307 if magic != xfsMagic { 308 return "", fmt.Errorf("xfs magic not found") 309 } 310 311 // Filesystem UUID. 312 b = make([]byte, xfsUUIDSize) 313 if _, err := file.ReadAt(b, xfsUUIDOff); err != nil { 314 return "", err 315 } 316 317 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil 318 } 319 320 // GetGPTTable tries to read a GPT table from the block device described by the 321 // passed BlockDev object, and returns a gpt.Table object, or an error if any 322 func GetGPTTable(device BlockDev) (*gpt.Table, error) { 323 fd, err := os.Open(fmt.Sprintf("/dev/%s", device.Name)) 324 if err != nil { 325 return nil, err 326 } 327 defer fd.Close() 328 if _, err = fd.Seek(512, io.SeekStart); err != nil { 329 return nil, err 330 } 331 table, err := gpt.ReadTable(fd, 512) 332 if err != nil { 333 return nil, err 334 } 335 return &table, nil 336 } 337 338 // FilterEFISystemPartitions returns a list of BlockDev objects whose underlying 339 // block device is a valid EFI system partition, or an error if any 340 func FilterEFISystemPartitions(devices []BlockDev) ([]BlockDev, error) { 341 return PartitionsByGUID(devices, SystemPartitionGUID.String()) 342 } 343 344 // PartitionsByGUID returns a list of BlockDev objects whose underlying 345 // block device has the given GUID 346 func PartitionsByGUID(devices []BlockDev, guid string) ([]BlockDev, error) { 347 partitions := make([]BlockDev, 0) 348 for _, device := range devices { 349 table, err := GetGPTTable(device) 350 if err != nil { 351 log.Printf("Skipping %s: %v", device.Name, err) 352 continue 353 } 354 for _, part := range table.Partitions { 355 if part.IsEmpty() { 356 continue 357 } 358 if part.Type.String() == guid { 359 partitions = append(partitions, device) 360 } 361 } 362 } 363 return partitions, nil 364 } 365 366 // PartitionsByFsUUID returns a list of BlockDev objects whose underlying 367 // block device has a filesystem with the given UUID 368 func PartitionsByFsUUID(devices []BlockDev, fsuuid string) []BlockDev { 369 partitions := make([]BlockDev, 0) 370 for _, device := range devices { 371 if device.FsUUID == fsuuid { 372 partitions = append(partitions, device) 373 } 374 } 375 return partitions 376 } 377 378 // GetMountpointByDevice gets the mountpoint by given 379 // device name. Returns on first match 380 func GetMountpointByDevice(devicePath string) (*string, error) { 381 file, err := os.Open(LinuxMountsPath) 382 if err != nil { 383 return nil, err 384 } 385 386 defer file.Close() 387 scanner := bufio.NewScanner(file) 388 389 for scanner.Scan() { 390 deviceInfo := strings.Fields(scanner.Text()) 391 if deviceInfo[0] == devicePath { 392 return &deviceInfo[1], nil 393 } 394 } 395 396 return nil, errors.New("Mountpoint not found") 397 }