github.com/system-transparency/u-root@v6.0.1-0.20190919065413-ed07a650de4c+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  	"errors"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"log"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/rekby/gpt"
    20  )
    21  
    22  var (
    23  	// LinuxMountsPath is the standard mountpoint list path
    24  	LinuxMountsPath = "/proc/mounts"
    25  )
    26  
    27  // BlockDev maps a device name to a BlockStat structure for a given block device
    28  type BlockDev struct {
    29  	Name   string
    30  	Stat   BlockStat
    31  	FsUUID string
    32  }
    33  
    34  // Summary prints a multiline summary of the BlockDev object
    35  // https://www.kernel.org/doc/Documentation/block/stat.txt
    36  func (b BlockDev) Summary() string {
    37  	return fmt.Sprintf(`BlockStat{
    38      Name: %v
    39      ReadIOs: %v
    40      ReadMerges: %v
    41      ReadSectors: %v
    42      ReadTicks: %v
    43      WriteIOs: %v
    44      WriteMerges: %v
    45      WriteSectors: %v
    46      WriteTicks: %v
    47      InFlight: %v
    48      IOTicks: %v
    49      TimeInQueue: %v
    50  }`,
    51  		b.Name,
    52  		b.Stat.ReadIOs,
    53  		b.Stat.ReadMerges,
    54  		b.Stat.ReadSectors,
    55  		b.Stat.ReadTicks,
    56  		b.Stat.WriteIOs,
    57  		b.Stat.WriteMerges,
    58  		b.Stat.WriteSectors,
    59  		b.Stat.WriteTicks,
    60  		b.Stat.InFlight,
    61  		b.Stat.IOTicks,
    62  		b.Stat.TimeInQueue,
    63  	)
    64  }
    65  
    66  // BlockStat provides block device information as contained in
    67  // /sys/class/block/<device_name>/stat
    68  type BlockStat struct {
    69  	ReadIOs      uint64
    70  	ReadMerges   uint64
    71  	ReadSectors  uint64
    72  	ReadTicks    uint64
    73  	WriteIOs     uint64
    74  	WriteMerges  uint64
    75  	WriteSectors uint64
    76  	WriteTicks   uint64
    77  	InFlight     uint64
    78  	IOTicks      uint64
    79  	TimeInQueue  uint64
    80  	// Kernel 4.18 added four fields for discard tracking, see
    81  	// https://github.com/torvalds/linux/commit/bdca3c87fb7ad1cc61d231d37eb0d8f90d001e0c
    82  	DiscardIOs     uint64
    83  	DiscardMerges  uint64
    84  	DiscardSectors uint64
    85  	DiscardTicks   uint64
    86  }
    87  
    88  // SystemPartitionGUID is the GUID of EFI system partitions
    89  // EFI System partitions have GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B
    90  var SystemPartitionGUID = gpt.Guid([...]byte{
    91  	0x28, 0x73, 0x2a, 0xc1,
    92  	0x1f, 0xf8,
    93  	0xd2, 0x11,
    94  	0xba, 0x4b,
    95  	0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b,
    96  })
    97  
    98  // BlockStatFromBytes parses a block stat file and returns a BlockStat object.
    99  // The format of the block stat file is the one defined by Linux for
   100  // /sys/class/block/<device_name>/stat
   101  func BlockStatFromBytes(buf []byte) (*BlockStat, error) {
   102  	fields := strings.Fields(string(buf))
   103  	// BlockStat has 11 fields
   104  	if len(fields) < 11 {
   105  		return nil, fmt.Errorf("BlockStatFromBytes: parsing %q: got %d fields(%q), want at least 11", buf, len(fields), fields)
   106  	}
   107  	intfields := make([]uint64, 0)
   108  	for _, field := range fields {
   109  		v, err := strconv.ParseUint(field, 10, 64)
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  		intfields = append(intfields, v)
   114  	}
   115  	bs := BlockStat{
   116  		ReadIOs:      intfields[0],
   117  		ReadMerges:   intfields[1],
   118  		ReadSectors:  intfields[2],
   119  		ReadTicks:    intfields[3],
   120  		WriteIOs:     intfields[4],
   121  		WriteMerges:  intfields[5],
   122  		WriteSectors: intfields[6],
   123  		WriteTicks:   intfields[7],
   124  		InFlight:     intfields[8],
   125  		IOTicks:      intfields[9],
   126  		TimeInQueue:  intfields[10],
   127  	}
   128  	if len(fields) >= 15 {
   129  		bs.DiscardIOs = intfields[11]
   130  		bs.DiscardMerges = intfields[12]
   131  		bs.DiscardSectors = intfields[13]
   132  		bs.DiscardTicks = intfields[14]
   133  	}
   134  	return &bs, nil
   135  }
   136  
   137  // GetBlockStats iterates over /sys/class/block entries and returns a list of
   138  // BlockDev objects, or an error if any
   139  func GetBlockStats() ([]BlockDev, error) {
   140  	blockdevs := make([]BlockDev, 0)
   141  	devnames := make([]string, 0)
   142  	root := "/sys/class/block"
   143  	err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
   144  		if err != nil {
   145  			return err
   146  		}
   147  		rel, err := filepath.Rel(root, path)
   148  		if err != nil {
   149  			return err
   150  		}
   151  		if rel == "." {
   152  			return nil
   153  		}
   154  		devnames = append(devnames, rel)
   155  		return nil
   156  	})
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	for _, devname := range devnames {
   161  		fd, err := os.Open(fmt.Sprintf("%s/%s/stat", root, devname))
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  		defer fd.Close()
   166  		buf, err := ioutil.ReadAll(fd)
   167  		if err != nil {
   168  			return nil, err
   169  		}
   170  		bstat, err := BlockStatFromBytes(buf)
   171  		if err != nil {
   172  			return nil, err
   173  		}
   174  		devpath := path.Join("/dev/", devname)
   175  		uuid := getUUID(devpath)
   176  		blockdevs = append(blockdevs, BlockDev{Name: devname, Stat: *bstat, FsUUID: uuid})
   177  	}
   178  	return blockdevs, nil
   179  }
   180  
   181  func getUUID(devpath string) (fsuuid string) {
   182  
   183  	fsuuid = tryVFAT(devpath)
   184  	if fsuuid != "" {
   185  		log.Printf("###### FsUUIS in %s: %s", devpath, fsuuid)
   186  		return fsuuid
   187  	}
   188  	fsuuid = tryEXT4(devpath)
   189  	if fsuuid != "" {
   190  		log.Printf("###### FsUUIS in %s: %s", devpath, fsuuid)
   191  		return fsuuid
   192  	}
   193  	log.Printf("###### FsUUIS in %s: NONE", devpath)
   194  	return ""
   195  }
   196  
   197  //see https://www.nongnu.org/ext2-doc/ext2.html#DISK-ORGANISATION
   198  const (
   199  	EXT2SprblkOff       = 1024 // Offset of superblock in partition
   200  	EXT2SprblkSize      = 512  // Actually 1024 but most of the last byters are reserved
   201  	EXT2SprblkMagicOff  = 56   // Offset of magic number in suberblock
   202  	EXT2SprblkMagicSize = 2
   203  	EXT2SprblkMagic     = '\uEF53' // fixed value
   204  	EXT2SprblkUUIDOff   = 104      // Offset of UUID in superblock
   205  	EXT2SprblkUUIDSize  = 16
   206  )
   207  
   208  func tryEXT4(devname string) (uuid string) {
   209  	log.Printf("try ext4")
   210  	var off int64
   211  
   212  	file, err := os.Open(devname)
   213  	if err != nil {
   214  		log.Println(err)
   215  		return ""
   216  	}
   217  	defer file.Close()
   218  
   219  	fileinfo, err := file.Stat()
   220  	if err != nil {
   221  		log.Println(err)
   222  		return ""
   223  	}
   224  	fmt.Printf("%s %d\n", fileinfo.Name(), fileinfo.Size())
   225  
   226  	// magic number
   227  	b := make([]byte, EXT2SprblkMagicSize)
   228  	off = EXT2SprblkOff + EXT2SprblkMagicOff
   229  	_, err = file.ReadAt(b, off)
   230  	if err != nil {
   231  		log.Println(err)
   232  		return ""
   233  	}
   234  	magic := uint16(b[1])<<8 + uint16(b[0])
   235  	fmt.Printf("magic: 0x%x\n", magic)
   236  	if magic != EXT2SprblkMagic {
   237  		log.Printf("try ext4")
   238  		return ""
   239  	}
   240  
   241  	// filesystem UUID
   242  	b = make([]byte, EXT2SprblkUUIDSize)
   243  	off = EXT2SprblkOff + EXT2SprblkUUIDOff
   244  	_, err = file.ReadAt(b, off)
   245  	if err != nil {
   246  		fmt.Println(err)
   247  		return ""
   248  	}
   249  	uuid = fmt.Sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
   250  		b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8],
   251  		b[9], b[10], b[11], b[12], b[13], b[14], b[15])
   252  	fmt.Printf("UUID=%s\n", uuid)
   253  
   254  	return uuid
   255  }
   256  
   257  // see https://de.wikipedia.org/wiki/File_Allocation_Table#Aufbau
   258  const (
   259  	FAT32MagicOff  = 82 // Offset of magic number
   260  	FAT32MagicSize = 8
   261  	FAT32Magic     = "FAT32   " // fixed value
   262  	FAT32IDOff     = 67         // Offset of filesystem-ID / serielnumber. Treated as short filesystem UUID
   263  	FAT32IDSize    = 4
   264  )
   265  
   266  func tryVFAT(devname string) (uuid string) {
   267  	log.Printf("try vfat")
   268  	var off int64
   269  
   270  	file, err := os.Open(devname)
   271  	if err != nil {
   272  		fmt.Println(err)
   273  		return ""
   274  	}
   275  	defer file.Close()
   276  
   277  	fileinfo, err := file.Stat()
   278  	if err != nil {
   279  		fmt.Println(err)
   280  		return ""
   281  	}
   282  	fmt.Printf("%s %d\n", fileinfo.Name(), fileinfo.Size())
   283  
   284  	// magic number
   285  	b := make([]byte, FAT32MagicSize)
   286  	off = 0 + FAT32MagicOff
   287  	_, err = file.ReadAt(b, off)
   288  	if err != nil {
   289  		fmt.Println(err)
   290  		return ""
   291  	}
   292  	magic := string(b)
   293  	fmt.Printf("magic: %s\n", magic)
   294  	if magic != FAT32Magic {
   295  		log.Printf("no vfat")
   296  		return ""
   297  	}
   298  
   299  	// filesystem UUID
   300  	b = make([]byte, FAT32IDSize)
   301  	off = 0 + FAT32IDOff
   302  	_, err = file.ReadAt(b, off)
   303  	if err != nil {
   304  		fmt.Println(err)
   305  		return ""
   306  	}
   307  	uuid = fmt.Sprintf("%02x%02x-%02x%02x",
   308  		b[3], b[2], b[1], b[0])
   309  	fmt.Printf("UUID=%s\n", uuid)
   310  
   311  	return uuid
   312  }
   313  
   314  // GetGPTTable tries to read a GPT table from the block device described by the
   315  // passed BlockDev object, and returns a gpt.Table object, or an error if any
   316  func GetGPTTable(device BlockDev) (*gpt.Table, error) {
   317  	fd, err := os.Open(fmt.Sprintf("/dev/%s", device.Name))
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  	defer fd.Close()
   322  	if _, err = fd.Seek(512, os.SEEK_SET); err != nil {
   323  		return nil, err
   324  	}
   325  	table, err := gpt.ReadTable(fd, 512)
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  	return &table, nil
   330  }
   331  
   332  // FilterEFISystemPartitions returns a list of BlockDev objects whose underlying
   333  // block device is a valid EFI system partition, or an error if any
   334  func FilterEFISystemPartitions(devices []BlockDev) ([]BlockDev, error) {
   335  	return PartitionsByGUID(devices, SystemPartitionGUID.String())
   336  }
   337  
   338  // PartitionsByGUID returns a list of BlockDev objects whose underlying
   339  // block device has the given GUID
   340  func PartitionsByGUID(devices []BlockDev, guid string) ([]BlockDev, error) {
   341  	partitions := make([]BlockDev, 0)
   342  	for _, device := range devices {
   343  		table, err := GetGPTTable(device)
   344  		if err != nil {
   345  			log.Printf("Skipping %s: %v", device.Name, err)
   346  			continue
   347  		}
   348  		for _, part := range table.Partitions {
   349  			if part.IsEmpty() {
   350  				continue
   351  			}
   352  			if part.Type.String() == guid {
   353  				partitions = append(partitions, device)
   354  			}
   355  		}
   356  	}
   357  	return partitions, nil
   358  }
   359  
   360  // PartitionsByFsUUID returns a list of BlockDev objects whose underlying
   361  // block device has a filesystem with the given UUID
   362  func PartitionsByFsUUID(devices []BlockDev, fsuuid string) []BlockDev {
   363  	partitions := make([]BlockDev, 0)
   364  	for _, device := range devices {
   365  		if device.FsUUID == fsuuid {
   366  			partitions = append(partitions, device)
   367  		}
   368  	}
   369  	return partitions
   370  }
   371  
   372  // GetMountpointByDevice gets the mountpoint by given
   373  // device name. Returns on first match
   374  func GetMountpointByDevice(devicePath string) (*string, error) {
   375  	file, err := os.Open(LinuxMountsPath)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  
   380  	defer file.Close()
   381  	scanner := bufio.NewScanner(file)
   382  
   383  	for scanner.Scan() {
   384  		deviceInfo := strings.Fields(scanner.Text())
   385  		if deviceInfo[0] == devicePath {
   386  			return &deviceInfo[1], nil
   387  		}
   388  	}
   389  
   390  	return nil, errors.New("Mountpoint not found")
   391  }