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