github.com/insomniacslk/u-root@v0.0.0-20200717035308-96b791510d76/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 "os" 15 "path/filepath" 16 "strings" 17 "unsafe" 18 19 "github.com/rekby/gpt" 20 "github.com/u-root/u-root/pkg/mount" 21 "golang.org/x/sys/unix" 22 ) 23 24 var ( 25 // LinuxMountsPath is the standard mountpoint list path 26 LinuxMountsPath = "/proc/mounts" 27 ) 28 29 // BlockDev maps a device name to a BlockStat structure for a given block device 30 type BlockDev struct { 31 Name string 32 FSType string 33 FsUUID string 34 } 35 36 // Device makes sure the block device exists and returns a handle to it. 37 // 38 // maybeDevpath can be path like /dev/sda1, /sys/class/block/sda1 or just sda1. 39 // We will just use the last component. 40 func Device(maybeDevpath string) (*BlockDev, error) { 41 devname := filepath.Base(maybeDevpath) 42 if _, err := os.Stat(filepath.Join("/sys/class/block", devname)); err != nil { 43 return nil, err 44 } 45 46 devpath := filepath.Join("/dev/", devname) 47 if uuid, err := getFSUUID(devpath); err == nil { 48 return &BlockDev{Name: devname, FsUUID: uuid}, nil 49 } 50 return &BlockDev{Name: devname}, nil 51 } 52 53 // String implements fmt.Stringer. 54 func (b *BlockDev) String() string { 55 if len(b.FSType) > 0 { 56 return fmt.Sprintf("BlockDevice(name=%s, fs_type=%s, fs_uuid=%s)", b.Name, b.FSType, b.FsUUID) 57 } 58 return fmt.Sprintf("BlockDevice(name=%s, fs_uuid=%s)", b.Name, b.FsUUID) 59 } 60 61 // DevicePath is the path to the actual device. 62 func (b BlockDev) DevicePath() string { 63 return filepath.Join("/dev/", b.Name) 64 } 65 66 // Mount implements mount.Mounter. 67 func (b *BlockDev) Mount(path string, flags uintptr) (*mount.MountPoint, error) { 68 devpath := filepath.Join("/dev", b.Name) 69 if len(b.FSType) > 0 { 70 return mount.Mount(devpath, path, b.FSType, "", flags) 71 } 72 73 return mount.TryMount(devpath, path, "", flags) 74 } 75 76 // GPTTable tries to read a GPT table from the block device described by the 77 // passed BlockDev object, and returns a gpt.Table object, or an error if any 78 func (b *BlockDev) GPTTable() (*gpt.Table, error) { 79 fd, err := os.Open(b.DevicePath()) 80 if err != nil { 81 return nil, err 82 } 83 defer fd.Close() 84 85 blkSize, err := b.BlockSize() 86 if err != nil { 87 blkSize = 512 88 } 89 90 if _, err := fd.Seek(int64(blkSize), io.SeekStart); err != nil { 91 return nil, err 92 } 93 table, err := gpt.ReadTable(fd, uint64(blkSize)) 94 if err != nil { 95 return nil, err 96 } 97 return &table, nil 98 } 99 100 // PhysicalBlockSize returns the physical block size. 101 func (b *BlockDev) PhysicalBlockSize() (int, error) { 102 f, err := os.Open(b.DevicePath()) 103 if err != nil { 104 return 0, err 105 } 106 defer f.Close() 107 return unix.IoctlGetInt(int(f.Fd()), unix.BLKPBSZGET) 108 } 109 110 // BlockSize returns the logical block size (BLKSSZGET). 111 func (b *BlockDev) BlockSize() (int, error) { 112 f, err := os.Open(b.DevicePath()) 113 if err != nil { 114 return 0, err 115 } 116 defer f.Close() 117 return unix.IoctlGetInt(int(f.Fd()), unix.BLKSSZGET) 118 } 119 120 // KernelBlockSize returns the soft block size used inside the kernel (BLKBSZGET). 121 func (b *BlockDev) KernelBlockSize() (int, error) { 122 f, err := os.Open(b.DevicePath()) 123 if err != nil { 124 return 0, err 125 } 126 defer f.Close() 127 return unix.IoctlGetInt(int(f.Fd()), unix.BLKBSZGET) 128 } 129 130 func ioctlGetUint64(fd int, req uint) (uint64, error) { 131 var value uint64 132 _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(unsafe.Pointer(&value))) 133 if err != 0 { 134 return 0, err 135 } 136 return value, nil 137 } 138 139 // Size returns the size in bytes. 140 func (b *BlockDev) Size() (uint64, error) { 141 f, err := os.Open(b.DevicePath()) 142 if err != nil { 143 return 0, err 144 } 145 defer f.Close() 146 147 sz, err := ioctlGetUint64(int(f.Fd()), unix.BLKGETSIZE64) 148 if err != nil { 149 return 0, &os.PathError{ 150 Op: "get size", 151 Path: b.DevicePath(), 152 Err: os.NewSyscallError("ioctl(BLKGETSIZE64)", err), 153 } 154 } 155 return sz, nil 156 } 157 158 // ReadPartitionTable prompts the kernel to re-read the partition table on this block device. 159 func (b *BlockDev) ReadPartitionTable() error { 160 f, err := os.OpenFile(b.DevicePath(), os.O_RDWR, 0) 161 if err != nil { 162 return err 163 } 164 defer f.Close() 165 return unix.IoctlSetInt(int(f.Fd()), unix.BLKRRPART, 0) 166 } 167 168 // SystemPartitionGUID is the GUID of EFI system partitions 169 // EFI System partitions have GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B 170 var SystemPartitionGUID = gpt.Guid([...]byte{ 171 0x28, 0x73, 0x2a, 0xc1, 172 0x1f, 0xf8, 173 0xd2, 0x11, 174 0xba, 0x4b, 175 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b, 176 }) 177 178 // GetBlockDevices iterates over /sys/class/block entries and returns a list of 179 // BlockDev objects, or an error if any 180 func GetBlockDevices() (BlockDevices, error) { 181 var blockdevs []*BlockDev 182 var devnames []string 183 184 root := "/sys/class/block" 185 err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 186 if err != nil { 187 return err 188 } 189 rel, err := filepath.Rel(root, path) 190 if err != nil { 191 return err 192 } 193 if rel == "." { 194 return nil 195 } 196 devnames = append(devnames, rel) 197 dev, err := Device(rel) 198 if err != nil { 199 return err 200 } 201 blockdevs = append(blockdevs, dev) 202 return nil 203 }) 204 if err != nil { 205 return nil, err 206 } 207 return blockdevs, nil 208 } 209 210 func getFSUUID(devpath string) (string, error) { 211 file, err := os.Open(devpath) 212 if err != nil { 213 return "", err 214 } 215 defer file.Close() 216 217 fsuuid, err := tryFAT32(file) 218 if err == nil { 219 return fsuuid, nil 220 } 221 fsuuid, err = tryFAT16(file) 222 if err == nil { 223 return fsuuid, nil 224 } 225 fsuuid, err = tryEXT4(file) 226 if err == nil { 227 return fsuuid, nil 228 } 229 fsuuid, err = tryXFS(file) 230 if err == nil { 231 return fsuuid, nil 232 } 233 return "", fmt.Errorf("unknown UUID (not vfat, ext4, nor xfs)") 234 } 235 236 // See https://www.nongnu.org/ext2-doc/ext2.html#DISK-ORGANISATION. 237 const ( 238 // Offset of superblock in partition. 239 ext2SprblkOff = 1024 240 241 // Offset of magic number in suberblock. 242 ext2SprblkMagicOff = 56 243 ext2SprblkMagicSize = 2 244 245 ext2SprblkMagic = 0xEF53 246 247 // Offset of UUID in superblock. 248 ext2SprblkUUIDOff = 104 249 ext2SprblkUUIDSize = 16 250 ) 251 252 func tryEXT4(file io.ReaderAt) (string, error) { 253 var off int64 254 255 // Read magic number. 256 b := make([]byte, ext2SprblkMagicSize) 257 off = ext2SprblkOff + ext2SprblkMagicOff 258 if _, err := file.ReadAt(b, off); err != nil { 259 return "", err 260 } 261 magic := binary.LittleEndian.Uint16(b[:2]) 262 if magic != ext2SprblkMagic { 263 return "", fmt.Errorf("ext4 magic not found") 264 } 265 266 // Filesystem UUID. 267 b = make([]byte, ext2SprblkUUIDSize) 268 off = ext2SprblkOff + ext2SprblkUUIDOff 269 if _, err := file.ReadAt(b, off); err != nil { 270 return "", err 271 } 272 273 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil 274 } 275 276 // See https://de.wikipedia.org/wiki/File_Allocation_Table#Aufbau. 277 const ( 278 fat12Magic = "FAT12 " 279 fat16Magic = "FAT16 " 280 281 // Offset of magic number. 282 fat16MagicOff = 0x36 283 fat16MagicSize = 8 284 285 // Offset of filesystem ID / serial number. Treated as short filesystem UUID. 286 fat16IDOff = 0x27 287 fat16IDSize = 4 288 ) 289 290 func tryFAT16(file io.ReaderAt) (string, error) { 291 // Read magic number. 292 b := make([]byte, fat16MagicSize) 293 if _, err := file.ReadAt(b, fat16MagicOff); err != nil { 294 return "", err 295 } 296 magic := string(b) 297 if magic != fat16Magic && magic != fat12Magic { 298 return "", fmt.Errorf("fat16 magic not found") 299 } 300 301 // Filesystem UUID. 302 b = make([]byte, fat16IDSize) 303 if _, err := file.ReadAt(b, fat16IDOff); err != nil { 304 return "", err 305 } 306 307 return fmt.Sprintf("%02x%02x-%02x%02x", b[3], b[2], b[1], b[0]), nil 308 } 309 310 // See https://de.wikipedia.org/wiki/File_Allocation_Table#Aufbau. 311 const ( 312 fat32Magic = "FAT32 " 313 314 // Offset of magic number. 315 fat32MagicOff = 0x52 316 fat32MagicSize = 8 317 318 // Offset of filesystem ID / serial number. Treated as short filesystem UUID. 319 fat32IDOff = 67 320 fat32IDSize = 4 321 ) 322 323 func tryFAT32(file io.ReaderAt) (string, error) { 324 // Read magic number. 325 b := make([]byte, fat32MagicSize) 326 if _, err := file.ReadAt(b, fat32MagicOff); err != nil { 327 return "", err 328 } 329 magic := string(b) 330 if magic != fat32Magic { 331 return "", fmt.Errorf("fat32 magic not found") 332 } 333 334 // Filesystem UUID. 335 b = make([]byte, fat32IDSize) 336 if _, err := file.ReadAt(b, fat32IDOff); err != nil { 337 return "", err 338 } 339 340 return fmt.Sprintf("%02x%02x-%02x%02x", b[3], b[2], b[1], b[0]), nil 341 } 342 343 const ( 344 xfsMagic = "XFSB" 345 xfsMagicSize = 4 346 xfsUUIDOff = 32 347 xfsUUIDSize = 16 348 ) 349 350 func tryXFS(file io.ReaderAt) (string, error) { 351 // Read magic number. 352 b := make([]byte, xfsMagicSize) 353 if _, err := file.ReadAt(b, 0); err != nil { 354 return "", err 355 } 356 magic := string(b) 357 if magic != xfsMagic { 358 return "", fmt.Errorf("xfs magic not found") 359 } 360 361 // Filesystem UUID. 362 b = make([]byte, xfsUUIDSize) 363 if _, err := file.ReadAt(b, xfsUUIDOff); err != nil { 364 return "", err 365 } 366 367 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil 368 } 369 370 // BlockDevices is a list of block devices. 371 type BlockDevices []*BlockDev 372 373 // FilterZeroSize attempts to find block devices that have at least one block 374 // of content. 375 // 376 // This serves to eliminate block devices that have no backing storage, but 377 // appear in /sys/class/block anyway (like some loop, nbd, or ram devices). 378 func (b BlockDevices) FilterZeroSize() BlockDevices { 379 var nb BlockDevices 380 for _, device := range b { 381 if n, err := device.Size(); err != nil || n == 0 { 382 continue 383 } 384 nb = append(nb, device) 385 } 386 return nb 387 } 388 389 // FilterPartID returns partitions with the given partition ID GUID. 390 func (b BlockDevices) FilterPartID(guid string) BlockDevices { 391 var names []string 392 for _, device := range b { 393 table, err := device.GPTTable() 394 if err != nil { 395 continue 396 } 397 for i, part := range table.Partitions { 398 if part.IsEmpty() { 399 continue 400 } 401 if strings.ToLower(part.Id.String()) == strings.ToLower(guid) { 402 names = append(names, fmt.Sprintf("%s%d", device.Name, i+1)) 403 } 404 } 405 } 406 return b.FilterNames(names...) 407 } 408 409 // FilterPartType returns partitions with the given partition type GUID. 410 func (b BlockDevices) FilterPartType(guid string) BlockDevices { 411 var names []string 412 for _, device := range b { 413 table, err := device.GPTTable() 414 if err != nil { 415 continue 416 } 417 for i, part := range table.Partitions { 418 if part.IsEmpty() { 419 continue 420 } 421 if strings.ToLower(part.Type.String()) == strings.ToLower(guid) { 422 names = append(names, fmt.Sprintf("%s%d", device.Name, i+1)) 423 } 424 } 425 } 426 return b.FilterNames(names...) 427 } 428 429 // FilterNames filters block devices by the given list of device names (e.g. 430 // /dev/sda1 sda2 /sys/class/block/sda3). 431 func (b BlockDevices) FilterNames(names ...string) BlockDevices { 432 m := make(map[string]struct{}) 433 for _, n := range names { 434 m[filepath.Base(n)] = struct{}{} 435 } 436 437 var devices BlockDevices 438 for _, device := range b { 439 if _, ok := m[device.Name]; ok { 440 devices = append(devices, device) 441 } 442 } 443 return devices 444 } 445 446 // FilterFSUUID returns a list of BlockDev objects whose underlying block 447 // device has a filesystem with the given UUID. 448 func (b BlockDevices) FilterFSUUID(fsuuid string) BlockDevices { 449 partitions := make(BlockDevices, 0) 450 for _, device := range b { 451 if device.FsUUID == fsuuid { 452 partitions = append(partitions, device) 453 } 454 } 455 return partitions 456 } 457 458 // FilterName returns a list of BlockDev objects whose underlying 459 // block device has a Name with the given Name 460 func (b BlockDevices) FilterName(name string) BlockDevices { 461 partitions := make(BlockDevices, 0) 462 for _, device := range b { 463 if device.Name == name { 464 partitions = append(partitions, device) 465 } 466 } 467 return partitions 468 } 469 470 // GetMountpointByDevice gets the mountpoint by given 471 // device name. Returns on first match 472 func GetMountpointByDevice(devicePath string) (*string, error) { 473 file, err := os.Open(LinuxMountsPath) 474 if err != nil { 475 return nil, err 476 } 477 478 defer file.Close() 479 scanner := bufio.NewScanner(file) 480 481 for scanner.Scan() { 482 deviceInfo := strings.Fields(scanner.Text()) 483 if deviceInfo[0] == devicePath { 484 return &deviceInfo[1], nil 485 } 486 } 487 488 return nil, errors.New("Mountpoint not found") 489 }