github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/blockdev/blockdev.go (about)

     1  /*
     2  Copyright 2018 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package blockdev
    18  
    19  import (
    20  	"crypto/sha256"
    21  	"encoding/binary"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"github.com/Mirantis/virtlet/pkg/utils"
    30  	"github.com/golang/glog"
    31  )
    32  
    33  const (
    34  	// VirtletLogicalDevicePrefix denotes the required prefix for
    35  	// the virtual block devices created by Virtlet.
    36  	VirtletLogicalDevicePrefix   = "virtlet-dm-"
    37  	virtletRootfsMagic           = 0x263dbe52ba576702
    38  	virtletRootfsMetadataVersion = 1
    39  	sectorSize                   = 512
    40  	devnameUeventVar             = "DEVNAME="
    41  )
    42  
    43  type virtletRootfsHeader struct {
    44  	Magic           uint64
    45  	MetadataVersion uint16
    46  	ImageHash       [sha256.Size]byte
    47  }
    48  
    49  // LogicalDeviceHandler makes it possible to store metadata in the
    50  // first sector of a block device, making the rest of the device
    51  // available as another logical device managed by the device mapper.
    52  type LogicalDeviceHandler struct {
    53  	commander utils.Commander
    54  	devPath   string
    55  	sysfsPath string
    56  }
    57  
    58  // NewLogicalDeviceHandler creates a new LogicalDeviceHandler using
    59  // the specified commander and paths that should be used in place of
    60  // /dev and /sys directories (empty string to use /dev and /sys,
    61  // respectively)
    62  func NewLogicalDeviceHandler(commander utils.Commander, devPath, sysfsPath string) *LogicalDeviceHandler {
    63  	if devPath == "" {
    64  		devPath = "/dev"
    65  	}
    66  	if sysfsPath == "" {
    67  		sysfsPath = "/sys"
    68  	}
    69  	return &LogicalDeviceHandler{commander, devPath, sysfsPath}
    70  }
    71  
    72  // EnsureDevHeaderMatches returns true if the specified block device
    73  // has proper Virtlet header that matches the specified image hash
    74  func (ldh *LogicalDeviceHandler) EnsureDevHeaderMatches(devPath string, imageHash [sha256.Size]byte) (bool, error) {
    75  	f, err := os.OpenFile(devPath, os.O_RDWR|os.O_SYNC, 0)
    76  	if err != nil {
    77  		return false, fmt.Errorf("open %q: %v", devPath, err)
    78  	}
    79  	defer func() {
    80  		if f != nil {
    81  			f.Close()
    82  		}
    83  	}()
    84  
    85  	var hdr virtletRootfsHeader
    86  	if err := binary.Read(f, binary.BigEndian, &hdr); err != nil {
    87  		return false, fmt.Errorf("reading rootfs header: %v", err)
    88  	}
    89  
    90  	headerMatch := true
    91  	switch {
    92  	case hdr.Magic != virtletRootfsMagic || hdr.ImageHash != imageHash:
    93  		headerMatch = false
    94  		if _, err := f.Seek(0, os.SEEK_SET); err != nil {
    95  			return false, fmt.Errorf("seek: %v", err)
    96  		}
    97  		if err := binary.Write(f, binary.BigEndian, virtletRootfsHeader{
    98  			Magic:           virtletRootfsMagic,
    99  			MetadataVersion: virtletRootfsMetadataVersion,
   100  			ImageHash:       imageHash,
   101  		}); err != nil {
   102  			return false, fmt.Errorf("writing rootfs header: %v", err)
   103  		}
   104  	case hdr.MetadataVersion != virtletRootfsMetadataVersion:
   105  		// NOTE: we should handle earlier metadata versions
   106  		// after we introduce new ones. But we can't handle
   107  		// future metadata versions and any non-matching
   108  		// metadata versions are future ones currently, so we
   109  		// don't want to lose any data here.
   110  		return false, fmt.Errorf("unsupported virtlet root device metadata version %v", hdr.MetadataVersion)
   111  	}
   112  
   113  	if err := f.Close(); err != nil {
   114  		return false, fmt.Errorf("error closing rootfs device: %v", err)
   115  	}
   116  	f = nil
   117  	return headerMatch, nil
   118  }
   119  
   120  // blockDevSizeInSectors returns the size of the block device in sectors
   121  func (ldh *LogicalDeviceHandler) blockDevSizeInSectors(devPath string) (uint64, error) {
   122  	// NOTE: this is also doable via ioctl but this way it's
   123  	// shorter (no need for fake non-linux impl, extra interface,
   124  	// extra fake impl for it). Some links that may help if we
   125  	// decide to use the ioctl later on:
   126  	// https://github.com/karelzak/util-linux/blob/master/disk-utils/blockdev.c
   127  	// https://github.com/aicodix/smr/blob/24aa589f378827a69a07d220f114c169693dacec/smr.go#L29
   128  	out, err := ldh.commander.Command("blockdev", "--getsz", devPath).Run(nil)
   129  	if err != nil {
   130  		return 0, err
   131  	}
   132  	nSectors, err := strconv.ParseUint(strings.TrimSpace(string(out)), 10, 64)
   133  	if err != nil {
   134  		return 0, fmt.Errorf("bad size value returned by blockdev: %q: %v", out, err)
   135  	}
   136  	return nSectors, nil
   137  }
   138  
   139  // Map maps the device sectors starting from 1 to a new virtual block
   140  // device. dmName specifies the name of the new device.
   141  func (ldh *LogicalDeviceHandler) Map(devPath, dmName string, imageSize uint64) error {
   142  	if !strings.HasPrefix(dmName, VirtletLogicalDevicePrefix) {
   143  		return fmt.Errorf("bad logical device name %q: must have prefix %q", dmName, VirtletLogicalDevicePrefix)
   144  	}
   145  
   146  	nSectors, err := ldh.blockDevSizeInSectors(devPath)
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	// sector 0 is reserved for the Virtlet metadata
   152  	minSectors := (imageSize+sectorSize-1)/sectorSize + 1
   153  	if nSectors < minSectors {
   154  		return fmt.Errorf("block device too small for the image: need at least %d bytes (%d sectors) but got %d bytes (%d sectors)",
   155  			minSectors*sectorSize,
   156  			minSectors,
   157  			nSectors*sectorSize,
   158  			nSectors)
   159  	}
   160  
   161  	hostPath, err := filepath.EvalSymlinks(devPath)
   162  	if err != nil {
   163  		return err
   164  	}
   165  
   166  	dmTable := fmt.Sprintf("0 %d linear %s 1\n", nSectors-1, hostPath)
   167  	_, err = ldh.commander.Command("dmsetup", "create", dmName).Run([]byte(dmTable))
   168  	return err
   169  }
   170  
   171  // Unmap unmaps the virtual block device
   172  func (ldh *LogicalDeviceHandler) Unmap(dmName string) error {
   173  	_, err := ldh.commander.Command("dmsetup", "remove", dmName).Run(nil)
   174  	return err
   175  }
   176  
   177  // ListVirtletLogicalDevices returns a list of logical devices managed
   178  // by Virtlet
   179  func (ldh *LogicalDeviceHandler) ListVirtletLogicalDevices() ([]string, error) {
   180  	table, err := ldh.commander.Command("dmsetup", "table").Run(nil)
   181  	if err != nil {
   182  		return nil, fmt.Errorf("dmsetup table: %v", err)
   183  	}
   184  	var r []string
   185  	for _, l := range strings.Split(string(table), "\n") {
   186  		if l == "" {
   187  			continue
   188  		}
   189  		fields := strings.Fields(l)
   190  		if len(fields) != 6 || fields[3] != "linear" {
   191  			continue
   192  		}
   193  		virtDevName := fields[0]
   194  		if strings.HasSuffix(virtDevName, ":") {
   195  			virtDevName = virtDevName[:len(virtDevName)-1]
   196  		}
   197  
   198  		devID := fields[4]
   199  		ueventFile := filepath.Join(ldh.sysfsPath, "dev/block", devID, "uevent")
   200  		ueventContent, err := ioutil.ReadFile(ueventFile)
   201  		if err != nil {
   202  			glog.Warningf("error reading %q: %v", ueventFile, err)
   203  			continue
   204  		}
   205  		devName := ""
   206  		for _, ul := range strings.Split(string(ueventContent), "\n") {
   207  			ul = strings.TrimSpace(ul)
   208  			if strings.HasPrefix(ul, devnameUeventVar) {
   209  				devName = ul[len(devnameUeventVar):]
   210  				break
   211  			}
   212  		}
   213  		if devName == "" {
   214  			glog.Warningf("bad uevent file %q: no DEVNAME", ueventFile)
   215  			continue
   216  		}
   217  
   218  		isVbd, err := ldh.deviceHasVirtletHeader(devName)
   219  		if err != nil {
   220  			glog.Warningf("checking device file %q: %v", devName, err)
   221  			continue
   222  		}
   223  
   224  		if isVbd {
   225  			r = append(r, virtDevName)
   226  		}
   227  	}
   228  
   229  	return r, nil
   230  }
   231  
   232  func (ldh *LogicalDeviceHandler) deviceHasVirtletHeader(devName string) (bool, error) {
   233  	f, err := os.Open(filepath.Join(ldh.devPath, devName))
   234  	if err != nil {
   235  		return false, err
   236  	}
   237  	defer f.Close()
   238  
   239  	var hdr virtletRootfsHeader
   240  	if err := binary.Read(f, binary.BigEndian, &hdr); err != nil {
   241  		return false, err
   242  	}
   243  
   244  	return hdr.Magic == virtletRootfsMagic, nil
   245  }