...

Source file src/github.com/Microsoft/hcsshim/internal/uvm/vpmem.go

Documentation: github.com/Microsoft/hcsshim/internal/uvm

     1  //go:build windows
     2  
     3  package uvm
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"os"
     9  
    10  	"github.com/pkg/errors"
    11  	"github.com/sirupsen/logrus"
    12  
    13  	"github.com/Microsoft/hcsshim/ext4/dmverity"
    14  	"github.com/Microsoft/hcsshim/ext4/tar2ext4"
    15  	"github.com/Microsoft/hcsshim/internal/hcs/resourcepaths"
    16  	hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
    17  	"github.com/Microsoft/hcsshim/internal/log"
    18  	"github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
    19  	"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
    20  )
    21  
    22  const (
    23  	lcowDefaultVPMemLayerFmt = "/run/layers/p%d"
    24  )
    25  
    26  var (
    27  	// ErrMaxVPMemLayerSize is the error returned when the size of `hostPath` is
    28  	// greater than the max vPMem layer size set at create time.
    29  	ErrMaxVPMemLayerSize = errors.New("layer size is to large for VPMEM max size")
    30  )
    31  
    32  // var _ resources.ResourceCloser = &VPMEMMount{} -- Causes an import cycle.
    33  
    34  type VPMEMMount struct {
    35  	GuestPath string
    36  	uvm       *UtilityVM
    37  	hostPath  string
    38  }
    39  
    40  func (vc *VPMEMMount) Release(ctx context.Context) error {
    41  	return vc.uvm.RemoveVPMem(ctx, vc.hostPath)
    42  }
    43  
    44  type vPMemInfoDefault struct {
    45  	hostPath string
    46  	uvmPath  string
    47  	refCount uint32
    48  }
    49  
    50  func newDefaultVPMemInfo(hostPath, uvmPath string) *vPMemInfoDefault {
    51  	return &vPMemInfoDefault{
    52  		hostPath: hostPath,
    53  		uvmPath:  uvmPath,
    54  		refCount: 1,
    55  	}
    56  }
    57  
    58  // fileSystemSize retrieves ext4 fs SuperBlock and returns the file system size and block size
    59  func fileSystemSize(vhdPath string) (int64, int, error) {
    60  	sb, err := tar2ext4.ReadExt4SuperBlock(vhdPath)
    61  	if err != nil {
    62  		return 0, 0, errors.Wrap(err, "failed to read ext4 super block")
    63  	}
    64  	blockSize := 1024 * (1 << sb.LogBlockSize)
    65  	fsSize := int64(blockSize) * int64(sb.BlocksCountLow)
    66  	return fsSize, blockSize, nil
    67  }
    68  
    69  // readVeritySuperBlock reads ext4 super block for a given VHD to then further read the dm-verity super block
    70  // and root hash
    71  func readVeritySuperBlock(ctx context.Context, layerPath string) (*guestresource.DeviceVerityInfo, error) {
    72  	// dm-verity information is expected to be appended, the size of ext4 data will be the offset
    73  	// of the dm-verity super block, followed by merkle hash tree
    74  	ext4SizeInBytes, ext4BlockSize, err := fileSystemSize(layerPath)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	dmvsb, err := dmverity.ReadDMVerityInfo(layerPath, ext4SizeInBytes)
    80  	if err != nil {
    81  		return nil, errors.Wrap(err, "failed to read dm-verity super block")
    82  	}
    83  	log.G(ctx).WithFields(logrus.Fields{
    84  		"layerPath":     layerPath,
    85  		"rootHash":      dmvsb.RootDigest,
    86  		"algorithm":     dmvsb.Algorithm,
    87  		"salt":          dmvsb.Salt,
    88  		"dataBlocks":    dmvsb.DataBlocks,
    89  		"dataBlockSize": dmvsb.DataBlockSize,
    90  	}).Debug("dm-verity information")
    91  
    92  	return &guestresource.DeviceVerityInfo{
    93  		Ext4SizeInBytes: ext4SizeInBytes,
    94  		BlockSize:       ext4BlockSize,
    95  		RootDigest:      dmvsb.RootDigest,
    96  		Algorithm:       dmvsb.Algorithm,
    97  		Salt:            dmvsb.Salt,
    98  		Version:         int(dmvsb.Version),
    99  		SuperBlock:      true,
   100  	}, nil
   101  }
   102  
   103  // findNextVPMemSlot finds next available VPMem slot.
   104  //
   105  // Lock MUST be held when calling this function.
   106  func (uvm *UtilityVM) findNextVPMemSlot(ctx context.Context, hostPath string) (uint32, error) {
   107  	for i := uint32(0); i < uvm.vpmemMaxCount; i++ {
   108  		if uvm.vpmemDevicesDefault[i] == nil {
   109  			log.G(ctx).WithFields(logrus.Fields{
   110  				"hostPath":     hostPath,
   111  				"deviceNumber": i,
   112  			}).Debug("allocated VPMem location")
   113  			return i, nil
   114  		}
   115  	}
   116  	return 0, ErrNoAvailableLocation
   117  }
   118  
   119  // findVPMemSlot looks up `findThisHostPath` in already mounted VPMem devices
   120  //
   121  // Lock MUST be held when calling this function
   122  func (uvm *UtilityVM) findVPMemSlot(ctx context.Context, findThisHostPath string) (uint32, error) {
   123  	for i := uint32(0); i < uvm.vpmemMaxCount; i++ {
   124  		if vi := uvm.vpmemDevicesDefault[i]; vi != nil && vi.hostPath == findThisHostPath {
   125  			log.G(ctx).WithFields(logrus.Fields{
   126  				"hostPath":     vi.hostPath,
   127  				"uvmPath":      vi.uvmPath,
   128  				"refCount":     vi.refCount,
   129  				"deviceNumber": i,
   130  			}).Debug("found VPMem location")
   131  			return i, nil
   132  		}
   133  	}
   134  	return 0, ErrNotAttached
   135  }
   136  
   137  // addVPMemDefault adds a VPMem disk to a utility VM at the next available location and
   138  // returns the UVM path where the layer was mounted.
   139  func (uvm *UtilityVM) addVPMemDefault(ctx context.Context, hostPath string) (_ string, err error) {
   140  	if devNumber, err := uvm.findVPMemSlot(ctx, hostPath); err == nil {
   141  		device := uvm.vpmemDevicesDefault[devNumber]
   142  		device.refCount++
   143  		return device.uvmPath, nil
   144  	}
   145  
   146  	fi, err := os.Stat(hostPath)
   147  	if err != nil {
   148  		return "", err
   149  	}
   150  	if uint64(fi.Size()) > uvm.vpmemMaxSizeBytes {
   151  		return "", ErrMaxVPMemLayerSize
   152  	}
   153  
   154  	deviceNumber, err := uvm.findNextVPMemSlot(ctx, hostPath)
   155  	if err != nil {
   156  		return "", err
   157  	}
   158  
   159  	modification := &hcsschema.ModifySettingRequest{
   160  		RequestType: guestrequest.RequestTypeAdd,
   161  		Settings: hcsschema.VirtualPMemDevice{
   162  			HostPath:    hostPath,
   163  			ReadOnly:    true,
   164  			ImageFormat: "Vhd1",
   165  		},
   166  		ResourcePath: fmt.Sprintf(resourcepaths.VPMemControllerResourceFormat, deviceNumber),
   167  	}
   168  
   169  	uvmPath := fmt.Sprintf(lcowDefaultVPMemLayerFmt, deviceNumber)
   170  	guestSettings := guestresource.LCOWMappedVPMemDevice{
   171  		DeviceNumber: deviceNumber,
   172  		MountPath:    uvmPath,
   173  	}
   174  	if v, iErr := readVeritySuperBlock(ctx, hostPath); iErr != nil {
   175  		log.G(ctx).WithError(iErr).WithField("hostPath", hostPath).Debug("unable to read dm-verity information from VHD")
   176  	} else {
   177  		if v != nil {
   178  			log.G(ctx).WithFields(logrus.Fields{
   179  				"hostPath":   hostPath,
   180  				"rootDigest": v.RootDigest,
   181  			}).Debug("adding VPMem with dm-verity")
   182  		}
   183  		guestSettings.VerityInfo = v
   184  	}
   185  
   186  	modification.GuestRequest = guestrequest.ModificationRequest{
   187  		ResourceType: guestresource.ResourceTypeVPMemDevice,
   188  		RequestType:  guestrequest.RequestTypeAdd,
   189  		Settings:     guestSettings,
   190  	}
   191  
   192  	if err := uvm.modify(ctx, modification); err != nil {
   193  		return "", errors.Errorf("uvm::addVPMemDefault: failed to modify utility VM configuration: %s", err)
   194  	}
   195  
   196  	uvm.vpmemDevicesDefault[deviceNumber] = newDefaultVPMemInfo(hostPath, uvmPath)
   197  	return uvmPath, nil
   198  }
   199  
   200  // removeVPMemDefault removes a VPMem disk from a Utility VM. If the `hostPath` is not
   201  // attached returns `ErrNotAttached`.
   202  func (uvm *UtilityVM) removeVPMemDefault(ctx context.Context, hostPath string) error {
   203  	deviceNumber, err := uvm.findVPMemSlot(ctx, hostPath)
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	device := uvm.vpmemDevicesDefault[deviceNumber]
   209  	if device.refCount > 1 {
   210  		device.refCount--
   211  		return nil
   212  	}
   213  
   214  	var verity *guestresource.DeviceVerityInfo
   215  	if v, _ := readVeritySuperBlock(ctx, hostPath); v != nil {
   216  		log.G(ctx).WithFields(logrus.Fields{
   217  			"hostPath":   hostPath,
   218  			"rootDigest": v.RootDigest,
   219  		}).Debug("removing VPMem with dm-verity")
   220  		verity = v
   221  	}
   222  	modification := &hcsschema.ModifySettingRequest{
   223  		RequestType:  guestrequest.RequestTypeRemove,
   224  		ResourcePath: fmt.Sprintf(resourcepaths.VPMemControllerResourceFormat, deviceNumber),
   225  		GuestRequest: guestrequest.ModificationRequest{
   226  			ResourceType: guestresource.ResourceTypeVPMemDevice,
   227  			RequestType:  guestrequest.RequestTypeRemove,
   228  			Settings: guestresource.LCOWMappedVPMemDevice{
   229  				DeviceNumber: deviceNumber,
   230  				MountPath:    device.uvmPath,
   231  				VerityInfo:   verity,
   232  			},
   233  		},
   234  	}
   235  	if err := uvm.modify(ctx, modification); err != nil {
   236  		return errors.Errorf("failed to remove VPMEM %s from utility VM %s: %s", hostPath, uvm.id, err)
   237  	}
   238  	log.G(ctx).WithFields(logrus.Fields{
   239  		"hostPath":     device.hostPath,
   240  		"uvmPath":      device.uvmPath,
   241  		"refCount":     device.refCount,
   242  		"deviceNumber": deviceNumber,
   243  	}).Debug("removed VPMEM location")
   244  
   245  	uvm.vpmemDevicesDefault[deviceNumber] = nil
   246  
   247  	return nil
   248  }
   249  
   250  func (uvm *UtilityVM) AddVPMem(ctx context.Context, hostPath string) (*VPMEMMount, error) {
   251  	if uvm.operatingSystem != "linux" {
   252  		return nil, errNotSupported
   253  	}
   254  
   255  	uvm.m.Lock()
   256  	defer uvm.m.Unlock()
   257  
   258  	var (
   259  		guestPath string
   260  		err       error
   261  	)
   262  	if uvm.vpmemMultiMapping {
   263  		guestPath, err = uvm.addVPMemMappedDevice(ctx, hostPath)
   264  	} else {
   265  		guestPath, err = uvm.addVPMemDefault(ctx, hostPath)
   266  	}
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	return &VPMEMMount{GuestPath: guestPath, uvm: uvm, hostPath: hostPath}, nil
   271  }
   272  
   273  func (uvm *UtilityVM) RemoveVPMem(ctx context.Context, hostPath string) error {
   274  	if uvm.operatingSystem != "linux" {
   275  		return errNotSupported
   276  	}
   277  
   278  	uvm.m.Lock()
   279  	defer uvm.m.Unlock()
   280  
   281  	if uvm.vpmemMultiMapping {
   282  		return uvm.removeVPMemMappedDevice(ctx, hostPath)
   283  	}
   284  	return uvm.removeVPMemDefault(ctx, hostPath)
   285  }
   286  

View as plain text