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