Dismount sandbox VHD on snapshot remove

Signed-off-by: Justin Terry (VM) <juterry@microsoft.com>
This commit is contained in:
Justin Terry (VM) 2018-10-09 15:38:23 -07:00
parent 31aa418f84
commit 00242e1668
5 changed files with 359 additions and 140 deletions

View File

@ -27,6 +27,7 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Microsoft/go-winio/vhd"
"github.com/Microsoft/hcsshim" "github.com/Microsoft/hcsshim"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
@ -206,9 +207,20 @@ func (s *snapshotter) Remove(ctx context.Context, key string) error {
path := s.getSnapshotDir(id) path := s.getSnapshotDir(id)
renamedID := "rm-" + id renamedID := "rm-" + id
renamed := filepath.Join(s.root, "snapshots", renamedID) renamed := s.getSnapshotDir(renamedID)
if err := os.Rename(path, renamed); err != nil && !os.IsNotExist(err) { if err := os.Rename(path, renamed); err != nil && !os.IsNotExist(err) {
return err if !os.IsPermission(err) {
return err
}
// If permission denied, it's possible that the scratch is still mounted, an
// artifact after a hard daemon crash for example. Worth a shot to try detaching it
// before retrying the rename.
if detachErr := vhd.DetachVhd(filepath.Join(path, "sandbox.vhdx")); detachErr != nil {
return errors.Wrapf(err, "failed to detach VHD: %s", detachErr)
}
if renameErr := os.Rename(path, renamed); renameErr != nil && !os.IsNotExist(renameErr) {
return errors.Wrapf(err, "second rename attempt following detach failed: %s", renameErr)
}
} }
if err := t.Commit(); err != nil { if err := t.Commit(); err != nil {

View File

@ -32,7 +32,7 @@ github.com/opencontainers/image-spec v1.0.1
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895 github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0 github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
github.com/Microsoft/go-winio v0.4.10 github.com/Microsoft/go-winio v0.4.11
github.com/Microsoft/hcsshim v0.7.9 github.com/Microsoft/hcsshim v0.7.9
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4 golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4

View File

@ -1,137 +1,137 @@
package winio package winio
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
) )
type fileFullEaInformation struct { type fileFullEaInformation struct {
NextEntryOffset uint32 NextEntryOffset uint32
Flags uint8 Flags uint8
NameLength uint8 NameLength uint8
ValueLength uint16 ValueLength uint16
} }
var ( var (
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
errInvalidEaBuffer = errors.New("invalid extended attribute buffer") errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
errEaNameTooLarge = errors.New("extended attribute name too large") errEaNameTooLarge = errors.New("extended attribute name too large")
errEaValueTooLarge = errors.New("extended attribute value too large") errEaValueTooLarge = errors.New("extended attribute value too large")
) )
// ExtendedAttribute represents a single Windows EA. // ExtendedAttribute represents a single Windows EA.
type ExtendedAttribute struct { type ExtendedAttribute struct {
Name string Name string
Value []byte Value []byte
Flags uint8 Flags uint8
} }
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
var info fileFullEaInformation var info fileFullEaInformation
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
if err != nil { if err != nil {
err = errInvalidEaBuffer err = errInvalidEaBuffer
return return
} }
nameOffset := fileFullEaInformationSize nameOffset := fileFullEaInformationSize
nameLen := int(info.NameLength) nameLen := int(info.NameLength)
valueOffset := nameOffset + int(info.NameLength) + 1 valueOffset := nameOffset + int(info.NameLength) + 1
valueLen := int(info.ValueLength) valueLen := int(info.ValueLength)
nextOffset := int(info.NextEntryOffset) nextOffset := int(info.NextEntryOffset)
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
err = errInvalidEaBuffer err = errInvalidEaBuffer
return return
} }
ea.Name = string(b[nameOffset : nameOffset+nameLen]) ea.Name = string(b[nameOffset : nameOffset+nameLen])
ea.Value = b[valueOffset : valueOffset+valueLen] ea.Value = b[valueOffset : valueOffset+valueLen]
ea.Flags = info.Flags ea.Flags = info.Flags
if info.NextEntryOffset != 0 { if info.NextEntryOffset != 0 {
nb = b[info.NextEntryOffset:] nb = b[info.NextEntryOffset:]
} }
return return
} }
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION // DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
// buffer retrieved from BackupRead, ZwQueryEaFile, etc. // buffer retrieved from BackupRead, ZwQueryEaFile, etc.
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
for len(b) != 0 { for len(b) != 0 {
ea, nb, err := parseEa(b) ea, nb, err := parseEa(b)
if err != nil { if err != nil {
return nil, err return nil, err
} }
eas = append(eas, ea) eas = append(eas, ea)
b = nb b = nb
} }
return return
} }
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
if int(uint8(len(ea.Name))) != len(ea.Name) { if int(uint8(len(ea.Name))) != len(ea.Name) {
return errEaNameTooLarge return errEaNameTooLarge
} }
if int(uint16(len(ea.Value))) != len(ea.Value) { if int(uint16(len(ea.Value))) != len(ea.Value) {
return errEaValueTooLarge return errEaValueTooLarge
} }
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
withPadding := (entrySize + 3) &^ 3 withPadding := (entrySize + 3) &^ 3
nextOffset := uint32(0) nextOffset := uint32(0)
if !last { if !last {
nextOffset = withPadding nextOffset = withPadding
} }
info := fileFullEaInformation{ info := fileFullEaInformation{
NextEntryOffset: nextOffset, NextEntryOffset: nextOffset,
Flags: ea.Flags, Flags: ea.Flags,
NameLength: uint8(len(ea.Name)), NameLength: uint8(len(ea.Name)),
ValueLength: uint16(len(ea.Value)), ValueLength: uint16(len(ea.Value)),
} }
err := binary.Write(buf, binary.LittleEndian, &info) err := binary.Write(buf, binary.LittleEndian, &info)
if err != nil { if err != nil {
return err return err
} }
_, err = buf.Write([]byte(ea.Name)) _, err = buf.Write([]byte(ea.Name))
if err != nil { if err != nil {
return err return err
} }
err = buf.WriteByte(0) err = buf.WriteByte(0)
if err != nil { if err != nil {
return err return err
} }
_, err = buf.Write(ea.Value) _, err = buf.Write(ea.Value)
if err != nil { if err != nil {
return err return err
} }
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION // EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
// buffer for use with BackupWrite, ZwSetEaFile, etc. // buffer for use with BackupWrite, ZwSetEaFile, etc.
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
var buf bytes.Buffer var buf bytes.Buffer
for i := range eas { for i := range eas {
last := false last := false
if i == len(eas)-1 { if i == len(eas)-1 {
last = true last = true
} }
err := writeEa(&buf, &eas[i], last) err := writeEa(&buf, &eas[i], last)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
return buf.Bytes(), nil return buf.Bytes(), nil
} }

108
vendor/github.com/Microsoft/go-winio/vhd/vhd.go generated vendored Normal file
View File

@ -0,0 +1,108 @@
// +build windows
package vhd
import "syscall"
//go:generate go run mksyscall_windows.go -output zvhd.go vhd.go
//sys createVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) [failretval != 0] = VirtDisk.CreateVirtualDisk
//sys openVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, flags uint32, parameters *uintptr, handle *syscall.Handle) (err error) [failretval != 0] = VirtDisk.OpenVirtualDisk
//sys detachVirtualDisk(handle syscall.Handle, flags uint32, providerSpecificFlags uint32) (err error) [failretval != 0] = VirtDisk.DetachVirtualDisk
type virtualStorageType struct {
DeviceID uint32
VendorID [16]byte
}
const virtualDiskAccessNONE uint32 = 0
const virtualDiskAccessATTACHRO uint32 = 65536
const virtualDiskAccessATTACHRW uint32 = 131072
const virtualDiskAccessDETACH uint32 = 262144
const virtualDiskAccessGETINFO uint32 = 524288
const virtualDiskAccessCREATE uint32 = 1048576
const virtualDiskAccessMETAOPS uint32 = 2097152
const virtualDiskAccessREAD uint32 = 851968
const virtualDiskAccessALL uint32 = 4128768
const virtualDiskAccessWRITABLE uint32 = 3276800
const createVirtualDiskFlagNone uint32 = 0
const createVirtualDiskFlagFullPhysicalAllocation uint32 = 1
const createVirtualDiskFlagPreventWritesToSourceDisk uint32 = 2
const createVirtualDiskFlagDoNotCopyMetadataFromParent uint32 = 4
type version2 struct {
UniqueID [16]byte // GUID
MaximumSize uint64
BlockSizeInBytes uint32
SectorSizeInBytes uint32
ParentPath *uint16 // string
SourcePath *uint16 // string
OpenFlags uint32
ParentVirtualStorageType virtualStorageType
SourceVirtualStorageType virtualStorageType
ResiliencyGUID [16]byte // GUID
}
type createVirtualDiskParameters struct {
Version uint32 // Must always be set to 2
Version2 version2
}
// CreateVhdx will create a simple vhdx file at the given path using default values.
func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error {
var defaultType virtualStorageType
parameters := createVirtualDiskParameters{
Version: 2,
Version2: version2{
MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024,
BlockSizeInBytes: blockSizeInMb * 1024 * 1024,
},
}
var handle syscall.Handle
if err := createVirtualDisk(
&defaultType,
path,
virtualDiskAccessNONE,
nil,
createVirtualDiskFlagNone,
0,
&parameters,
nil,
&handle); err != nil {
return err
}
if err := syscall.CloseHandle(handle); err != nil {
return err
}
return nil
}
// DetachVhd detaches a VHD attached at the given path.
func DetachVhd(path string) error {
var (
defaultType virtualStorageType
handle syscall.Handle
)
if err := openVirtualDisk(
&defaultType,
path,
virtualDiskAccessDETACH,
0,
nil,
&handle); err != nil {
return err
}
defer syscall.CloseHandle(handle)
if err := detachVirtualDisk(handle, 0, 0); err != nil {
return err
}
return nil
}

99
vendor/github.com/Microsoft/go-winio/vhd/zvhd.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
package vhd
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modVirtDisk = windows.NewLazySystemDLL("VirtDisk.dll")
procCreateVirtualDisk = modVirtDisk.NewProc("CreateVirtualDisk")
procOpenVirtualDisk = modVirtDisk.NewProc("OpenVirtualDisk")
procDetachVirtualDisk = modVirtDisk.NewProc("DetachVirtualDisk")
)
func createVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(path)
if err != nil {
return
}
return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, flags, providerSpecificFlags, parameters, o, handle)
}
func _createVirtualDisk(virtualStorageType *virtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) {
r1, _, e1 := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(flags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(handle)))
if r1 != 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func openVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, flags uint32, parameters *uintptr, handle *syscall.Handle) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(path)
if err != nil {
return
}
return _openVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, flags, parameters, handle)
}
func _openVirtualDisk(virtualStorageType *virtualStorageType, path *uint16, virtualDiskAccessMask uint32, flags uint32, parameters *uintptr, handle *syscall.Handle) (err error) {
r1, _, e1 := syscall.Syscall6(procOpenVirtualDisk.Addr(), 6, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(flags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle)))
if r1 != 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func detachVirtualDisk(handle syscall.Handle, flags uint32, providerSpecificFlags uint32) (err error) {
r1, _, e1 := syscall.Syscall(procDetachVirtualDisk.Addr(), 3, uintptr(handle), uintptr(flags), uintptr(providerSpecificFlags))
if r1 != 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}