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  }