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  }