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