github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/mount/block/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 block finds, mounts, and modifies block devices on Linux systems. 6 package block 7 8 import ( 9 "bufio" 10 "encoding/binary" 11 "errors" 12 "fmt" 13 "io" 14 "io/ioutil" 15 "log" 16 "os" 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 "github.com/u-root/u-root/pkg/pci" 25 "golang.org/x/sys/unix" 26 ) 27 28 var ( 29 // LinuxMountsPath is the standard mountpoint list path 30 LinuxMountsPath = "/proc/mounts" 31 32 Debug = func(string, ...interface{}) {} 33 ) 34 35 // BlockDev maps a device name to a BlockStat structure for a given block device 36 type BlockDev struct { 37 Name string 38 FSType string 39 FsUUID string 40 } 41 42 // Device makes sure the block device exists and returns a handle to it. 43 // 44 // maybeDevpath can be path like /dev/sda1, /sys/class/block/sda1 or just sda1. 45 // We will just use the last component. 46 func Device(maybeDevpath string) (*BlockDev, error) { 47 devname := filepath.Base(maybeDevpath) 48 if _, err := os.Stat(filepath.Join("/sys/class/block", devname)); err != nil { 49 return nil, err 50 } 51 52 devpath := filepath.Join("/dev/", devname) 53 if uuid, err := getFSUUID(devpath); err == nil { 54 return &BlockDev{Name: devname, FsUUID: uuid}, nil 55 } 56 return &BlockDev{Name: devname}, nil 57 } 58 59 // String implements fmt.Stringer. 60 func (b *BlockDev) String() string { 61 if len(b.FSType) > 0 { 62 return fmt.Sprintf("BlockDevice(name=%s, fs_type=%s, fs_uuid=%s)", b.Name, b.FSType, b.FsUUID) 63 } 64 return fmt.Sprintf("BlockDevice(name=%s, fs_uuid=%s)", b.Name, b.FsUUID) 65 } 66 67 // DevicePath is the path to the actual device. 68 func (b BlockDev) DevicePath() string { 69 return filepath.Join("/dev/", b.Name) 70 } 71 72 // Mount implements mount.Mounter. 73 func (b *BlockDev) Mount(path string, flags uintptr) (*mount.MountPoint, error) { 74 devpath := filepath.Join("/dev", b.Name) 75 if len(b.FSType) > 0 { 76 return mount.Mount(devpath, path, b.FSType, "", flags) 77 } 78 79 return mount.TryMount(devpath, path, "", flags) 80 } 81 82 // GPTTable tries to read a GPT table from the block device described by the 83 // passed BlockDev object, and returns a gpt.Table object, or an error if any 84 func (b *BlockDev) GPTTable() (*gpt.Table, error) { 85 fd, err := os.Open(b.DevicePath()) 86 if err != nil { 87 return nil, err 88 } 89 defer fd.Close() 90 91 blkSize, err := b.BlockSize() 92 if err != nil { 93 blkSize = 512 94 } 95 96 if _, err := fd.Seek(int64(blkSize), io.SeekStart); err != nil { 97 return nil, err 98 } 99 table, err := gpt.ReadTable(fd, uint64(blkSize)) 100 if err != nil { 101 return nil, err 102 } 103 return &table, nil 104 } 105 106 // PhysicalBlockSize returns the physical block size. 107 func (b *BlockDev) PhysicalBlockSize() (int, error) { 108 f, err := os.Open(b.DevicePath()) 109 if err != nil { 110 return 0, err 111 } 112 defer f.Close() 113 return unix.IoctlGetInt(int(f.Fd()), unix.BLKPBSZGET) 114 } 115 116 // BlockSize returns the logical block size (BLKSSZGET). 117 func (b *BlockDev) BlockSize() (int, error) { 118 f, err := os.Open(b.DevicePath()) 119 if err != nil { 120 return 0, err 121 } 122 defer f.Close() 123 return unix.IoctlGetInt(int(f.Fd()), unix.BLKSSZGET) 124 } 125 126 // KernelBlockSize returns the soft block size used inside the kernel (BLKBSZGET). 127 func (b *BlockDev) KernelBlockSize() (int, error) { 128 f, err := os.Open(b.DevicePath()) 129 if err != nil { 130 return 0, err 131 } 132 defer f.Close() 133 return unix.IoctlGetInt(int(f.Fd()), unix.BLKBSZGET) 134 } 135 136 func ioctlGetUint64(fd int, req uint) (uint64, error) { 137 var value uint64 138 _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(unsafe.Pointer(&value))) 139 if err != 0 { 140 return 0, err 141 } 142 return value, nil 143 } 144 145 // Size returns the size in bytes. 146 func (b *BlockDev) Size() (uint64, error) { 147 f, err := os.Open(b.DevicePath()) 148 if err != nil { 149 return 0, err 150 } 151 defer f.Close() 152 153 sz, err := ioctlGetUint64(int(f.Fd()), unix.BLKGETSIZE64) 154 if err != nil { 155 return 0, &os.PathError{ 156 Op: "get size", 157 Path: b.DevicePath(), 158 Err: os.NewSyscallError("ioctl(BLKGETSIZE64)", err), 159 } 160 } 161 return sz, nil 162 } 163 164 // ReadPartitionTable prompts the kernel to re-read the partition table on this block device. 165 func (b *BlockDev) ReadPartitionTable() error { 166 f, err := os.OpenFile(b.DevicePath(), os.O_RDWR, 0) 167 if err != nil { 168 return err 169 } 170 defer f.Close() 171 return unix.IoctlSetInt(int(f.Fd()), unix.BLKRRPART, 0) 172 } 173 174 // PCIInfo searches sysfs for the PCI vendor and device id. 175 // We fill in the PCI struct with just those two elements. 176 func (b *BlockDev) PCIInfo() (*pci.PCI, error) { 177 p, err := filepath.EvalSymlinks(filepath.Join("/sys/class/block", b.Name)) 178 if err != nil { 179 return nil, err 180 } 181 // Loop through devices until we find the actual backing pci device. 182 // For Example: 183 // /sys/class/block/nvme0n1p1 usually resolves to something like 184 // /sys/devices/pci..../.../.../nvme/nvme0/nvme0n1/nvme0n1p1. This leads us to the 185 // first partition of the first namespace of the nvme0 device. In this case, the actual pci device and vendor 186 // is found in nvme, three levels up. We traverse back up to the parent device 187 // and we keep going until we find a device and vendor file. 188 dp := filepath.Join(p, "device") 189 vp := filepath.Join(p, "vendor") 190 found := false 191 for p != "/sys/devices" { 192 // Check if there is a vendor and device file in this directory. 193 if d, err := os.Stat(dp); err == nil && !d.IsDir() { 194 if v, err := os.Stat(vp); err == nil && !v.IsDir() { 195 found = true 196 break 197 } 198 } 199 p = filepath.Dir(p) 200 dp = filepath.Join(p, "device") 201 vp = filepath.Join(p, "vendor") 202 } 203 if !found { 204 return nil, fmt.Errorf("Unable to find backing pci device with device and vendor files for %v", b.Name) 205 } 206 207 // Read both files into the pci struct and return 208 device, err := ioutil.ReadFile(dp) 209 if err != nil { 210 return nil, fmt.Errorf("Error reading device file: %v", err) 211 } 212 vendor, err := ioutil.ReadFile(vp) 213 if err != nil { 214 return nil, fmt.Errorf("Error reading vendor file: %v", err) 215 } 216 return &pci.PCI{ 217 Vendor: strings.TrimSpace(string(vendor)), 218 Device: strings.TrimSpace(string(device)), 219 FullPath: p, 220 }, nil 221 } 222 223 // SystemPartitionGUID is the GUID of EFI system partitions 224 // EFI System partitions have GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B 225 var SystemPartitionGUID = gpt.Guid([...]byte{ 226 0x28, 0x73, 0x2a, 0xc1, 227 0x1f, 0xf8, 228 0xd2, 0x11, 229 0xba, 0x4b, 230 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b, 231 }) 232 233 // GetBlockDevices iterates over /sys/class/block entries and returns a list of 234 // BlockDev objects, or an error if any 235 func GetBlockDevices() (BlockDevices, error) { 236 var blockdevs []*BlockDev 237 var devnames []string 238 239 root := "/sys/class/block" 240 err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 241 if err != nil { 242 return err 243 } 244 rel, err := filepath.Rel(root, path) 245 if err != nil { 246 return err 247 } 248 if rel == "." { 249 return nil 250 } 251 devnames = append(devnames, rel) 252 dev, err := Device(rel) 253 if err != nil { 254 return err 255 } 256 blockdevs = append(blockdevs, dev) 257 return nil 258 }) 259 if err != nil { 260 return nil, err 261 } 262 return blockdevs, nil 263 } 264 265 func getFSUUID(devpath string) (string, error) { 266 file, err := os.Open(devpath) 267 if err != nil { 268 return "", err 269 } 270 defer file.Close() 271 272 fsuuid, err := tryFAT32(file) 273 if err == nil { 274 return fsuuid, nil 275 } 276 fsuuid, err = tryFAT16(file) 277 if err == nil { 278 return fsuuid, nil 279 } 280 fsuuid, err = tryEXT4(file) 281 if err == nil { 282 return fsuuid, nil 283 } 284 fsuuid, err = tryXFS(file) 285 if err == nil { 286 return fsuuid, nil 287 } 288 return "", fmt.Errorf("unknown UUID (not vfat, ext4, nor xfs)") 289 } 290 291 // See https://www.nongnu.org/ext2-doc/ext2.html#DISK-ORGANISATION. 292 const ( 293 // Offset of superblock in partition. 294 ext2SprblkOff = 1024 295 296 // Offset of magic number in suberblock. 297 ext2SprblkMagicOff = 56 298 ext2SprblkMagicSize = 2 299 300 ext2SprblkMagic = 0xEF53 301 302 // Offset of UUID in superblock. 303 ext2SprblkUUIDOff = 104 304 ext2SprblkUUIDSize = 16 305 ) 306 307 func tryEXT4(file io.ReaderAt) (string, error) { 308 var off int64 309 310 // Read magic number. 311 b := make([]byte, ext2SprblkMagicSize) 312 off = ext2SprblkOff + ext2SprblkMagicOff 313 if _, err := file.ReadAt(b, off); err != nil { 314 return "", err 315 } 316 magic := binary.LittleEndian.Uint16(b[:2]) 317 if magic != ext2SprblkMagic { 318 return "", fmt.Errorf("ext4 magic not found") 319 } 320 321 // Filesystem UUID. 322 b = make([]byte, ext2SprblkUUIDSize) 323 off = ext2SprblkOff + ext2SprblkUUIDOff 324 if _, err := file.ReadAt(b, off); err != nil { 325 return "", err 326 } 327 328 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil 329 } 330 331 // See https://de.wikipedia.org/wiki/File_Allocation_Table#Aufbau. 332 const ( 333 fat12Magic = "FAT12 " 334 fat16Magic = "FAT16 " 335 336 // Offset of magic number. 337 fat16MagicOff = 0x36 338 fat16MagicSize = 8 339 340 // Offset of filesystem ID / serial number. Treated as short filesystem UUID. 341 fat16IDOff = 0x27 342 fat16IDSize = 4 343 ) 344 345 func tryFAT16(file io.ReaderAt) (string, error) { 346 // Read magic number. 347 b := make([]byte, fat16MagicSize) 348 if _, err := file.ReadAt(b, fat16MagicOff); err != nil { 349 return "", err 350 } 351 magic := string(b) 352 if magic != fat16Magic && magic != fat12Magic { 353 return "", fmt.Errorf("fat16 magic not found") 354 } 355 356 // Filesystem UUID. 357 b = make([]byte, fat16IDSize) 358 if _, err := file.ReadAt(b, fat16IDOff); err != nil { 359 return "", err 360 } 361 362 return fmt.Sprintf("%02x%02x-%02x%02x", b[3], b[2], b[1], b[0]), nil 363 } 364 365 // See https://de.wikipedia.org/wiki/File_Allocation_Table#Aufbau. 366 const ( 367 fat32Magic = "FAT32 " 368 369 // Offset of magic number. 370 fat32MagicOff = 0x52 371 fat32MagicSize = 8 372 373 // Offset of filesystem ID / serial number. Treated as short filesystem UUID. 374 fat32IDOff = 67 375 fat32IDSize = 4 376 ) 377 378 func tryFAT32(file io.ReaderAt) (string, error) { 379 // Read magic number. 380 b := make([]byte, fat32MagicSize) 381 if _, err := file.ReadAt(b, fat32MagicOff); err != nil { 382 return "", err 383 } 384 magic := string(b) 385 if magic != fat32Magic { 386 return "", fmt.Errorf("fat32 magic not found") 387 } 388 389 // Filesystem UUID. 390 b = make([]byte, fat32IDSize) 391 if _, err := file.ReadAt(b, fat32IDOff); err != nil { 392 return "", err 393 } 394 395 return fmt.Sprintf("%02x%02x-%02x%02x", b[3], b[2], b[1], b[0]), nil 396 } 397 398 const ( 399 xfsMagic = "XFSB" 400 xfsMagicSize = 4 401 xfsUUIDOff = 32 402 xfsUUIDSize = 16 403 ) 404 405 func tryXFS(file io.ReaderAt) (string, error) { 406 // Read magic number. 407 b := make([]byte, xfsMagicSize) 408 if _, err := file.ReadAt(b, 0); err != nil { 409 return "", err 410 } 411 magic := string(b) 412 if magic != xfsMagic { 413 return "", fmt.Errorf("xfs magic not found") 414 } 415 416 // Filesystem UUID. 417 b = make([]byte, xfsUUIDSize) 418 if _, err := file.ReadAt(b, xfsUUIDOff); err != nil { 419 return "", err 420 } 421 422 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil 423 } 424 425 // BlockDevices is a list of block devices. 426 type BlockDevices []*BlockDev 427 428 // FilterZeroSize attempts to find block devices that have at least one block 429 // of content. 430 // 431 // This serves to eliminate block devices that have no backing storage, but 432 // appear in /sys/class/block anyway (like some loop, nbd, or ram devices). 433 func (b BlockDevices) FilterZeroSize() BlockDevices { 434 var nb BlockDevices 435 for _, device := range b { 436 if n, err := device.Size(); err != nil || n == 0 { 437 continue 438 } 439 nb = append(nb, device) 440 } 441 return nb 442 } 443 444 // FilterPartID returns partitions with the given partition ID GUID. 445 func (b BlockDevices) FilterPartID(guid string) BlockDevices { 446 var names []string 447 for _, device := range b { 448 table, err := device.GPTTable() 449 if err != nil { 450 continue 451 } 452 for i, part := range table.Partitions { 453 if part.IsEmpty() { 454 continue 455 } 456 if strings.ToLower(part.Id.String()) == strings.ToLower(guid) { 457 names = append(names, fmt.Sprintf("%s%d", device.Name, i+1)) 458 } 459 } 460 } 461 return b.FilterNames(names...) 462 } 463 464 // FilterPartType returns partitions with the given partition type GUID. 465 func (b BlockDevices) FilterPartType(guid string) BlockDevices { 466 var names []string 467 for _, device := range b { 468 table, err := device.GPTTable() 469 if err != nil { 470 continue 471 } 472 for i, part := range table.Partitions { 473 if part.IsEmpty() { 474 continue 475 } 476 if strings.ToLower(part.Type.String()) == strings.ToLower(guid) { 477 names = append(names, fmt.Sprintf("%s%d", device.Name, i+1)) 478 } 479 } 480 } 481 return b.FilterNames(names...) 482 } 483 484 // FilterNames filters block devices by the given list of device names (e.g. 485 // /dev/sda1 sda2 /sys/class/block/sda3). 486 func (b BlockDevices) FilterNames(names ...string) BlockDevices { 487 m := make(map[string]struct{}) 488 for _, n := range names { 489 m[filepath.Base(n)] = struct{}{} 490 } 491 492 var devices BlockDevices 493 for _, device := range b { 494 if _, ok := m[device.Name]; ok { 495 devices = append(devices, device) 496 } 497 } 498 return devices 499 } 500 501 // FilterFSUUID returns a list of BlockDev objects whose underlying block 502 // device has a filesystem with the given UUID. 503 func (b BlockDevices) FilterFSUUID(fsuuid string) BlockDevices { 504 partitions := make(BlockDevices, 0) 505 for _, device := range b { 506 if device.FsUUID == fsuuid { 507 partitions = append(partitions, device) 508 } 509 } 510 return partitions 511 } 512 513 // FilterName returns a list of BlockDev objects whose underlying 514 // block device has a Name with the given Name 515 func (b BlockDevices) FilterName(name string) BlockDevices { 516 partitions := make(BlockDevices, 0) 517 for _, device := range b { 518 if device.Name == name { 519 partitions = append(partitions, device) 520 } 521 } 522 return partitions 523 } 524 525 // parsePCIBlockList parses a string in the format vendor:device,vendor:device 526 // and returns a list of PCI devices containing the vendor and device pairs to block. 527 func parsePCIBlockList(blockList string) (pci.Devices, error) { 528 pciList := pci.Devices{} 529 bL := strings.Split(blockList, ",") 530 for _, b := range bL { 531 p := strings.Split(b, ":") 532 if len(p) != 2 { 533 return nil, fmt.Errorf("BlockList needs to be of format vendor1:device1,vendor2:device2...! got %v", blockList) 534 } 535 // Check that values are hex and convert them to sysfs formats 536 // This accepts 0xABCD and turns it into 0xabcd 537 // abcd also turns into 0xabcd 538 v, err := strconv.ParseUint(strings.TrimPrefix(p[0], "0x"), 16, 16) 539 if err != nil { 540 return nil, fmt.Errorf("BlockList needs to contain a hex vendor ID, got %v, err %v", p[0], err) 541 } 542 vs := fmt.Sprintf("%#04x", v) 543 544 d, err := strconv.ParseUint(strings.TrimPrefix(p[1], "0x"), 16, 16) 545 if err != nil { 546 return nil, fmt.Errorf("BlockList needs to contain a hex device ID, got %v, err %v", p[1], err) 547 } 548 ds := fmt.Sprintf("%#04x", d) 549 pciList = append(pciList, &pci.PCI{Vendor: vs, Device: ds}) 550 } 551 return pciList, nil 552 } 553 554 // FilterBlockPCIString parses a string in the format vendor:device,vendor:device 555 // and returns a list of BlockDev objects whose backing pci devices do not match 556 // the vendor:device pairs passed in. All values are treated as hex. 557 // E.g. 0x8086:0xABCD,8086:0x1234 558 func (b BlockDevices) FilterBlockPCIString(blocklist string) (BlockDevices, error) { 559 pciList, err := parsePCIBlockList(blocklist) 560 if err != nil { 561 return nil, err 562 } 563 return b.FilterBlockPCI(pciList), nil 564 } 565 566 // FilterBlockPCI returns a list of BlockDev objects whose backing 567 // pci devices do not match the blocklist of PCI devices passed in. 568 // FilterBlockPCI discards entries which have a matching PCI vendor 569 // and device ID as an entry in the blocklist. 570 func (b BlockDevices) FilterBlockPCI(blocklist pci.Devices) BlockDevices { 571 type mapKey struct { 572 vendor, device string 573 } 574 m := make(map[mapKey]bool) 575 576 for _, v := range blocklist { 577 m[mapKey{v.Vendor, v.Device}] = true 578 } 579 Debug("block map is %v", m) 580 581 partitions := make(BlockDevices, 0) 582 for _, device := range b { 583 p, err := device.PCIInfo() 584 if err != nil { 585 // In the case of an error, we err on the safe side and choose not to block it. 586 // Not all block devices are backed by a pci device, for example SATA drives. 587 Debug("Failed to find PCI info; %v", err) 588 partitions = append(partitions, device) 589 continue 590 } 591 if _, ok := m[mapKey{p.Vendor, p.Device}]; !ok { 592 // Not in blocklist, we're good to go 593 Debug("Not blocking device %v, with pci %v, not in map", device, p) 594 partitions = append(partitions, device) 595 } else { 596 log.Printf("Blocking device %v since it appears in blocklist", device.Name) 597 } 598 } 599 return partitions 600 } 601 602 // GetMountpointByDevice gets the mountpoint by given 603 // device name. Returns on first match 604 func GetMountpointByDevice(devicePath string) (*string, error) { 605 file, err := os.Open(LinuxMountsPath) 606 if err != nil { 607 return nil, err 608 } 609 610 defer file.Close() 611 scanner := bufio.NewScanner(file) 612 613 for scanner.Scan() { 614 deviceInfo := strings.Fields(scanner.Text()) 615 if deviceInfo[0] == devicePath { 616 return &deviceInfo[1], nil 617 } 618 } 619 620 return nil, errors.New("Mountpoint not found") 621 }