summaryrefslogtreecommitdiff
path: root/vendor/github.com/Microsoft/hcsshim/internal/wclayer/expandscratchsize.go
blob: 93f27da8a0a6db6e56868879773ed5f7f0d89218 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package wclayer

import (
	"context"
	"os"
	"path/filepath"
	"syscall"
	"unsafe"

	"github.com/Microsoft/hcsshim/internal/hcserror"
	"github.com/Microsoft/hcsshim/internal/oc"
	"github.com/Microsoft/hcsshim/osversion"
	"go.opencensus.io/trace"
)

// ExpandScratchSize expands the size of a layer to at least size bytes.
func ExpandScratchSize(ctx context.Context, path string, size uint64) (err error) {
	title := "hcsshim::ExpandScratchSize"
	ctx, span := trace.StartSpan(ctx, title)
	defer span.End()
	defer func() { oc.SetSpanStatus(span, err) }()
	span.AddAttributes(
		trace.StringAttribute("path", path),
		trace.Int64Attribute("size", int64(size)))

	err = expandSandboxSize(&stdDriverInfo, path, size)
	if err != nil {
		return hcserror.New(err, title+" - failed", "")
	}

	// Manually expand the volume now in order to work around bugs in 19H1 and
	// prerelease versions of Vb. Remove once this is fixed in Windows.
	if build := osversion.Get().Build; build >= osversion.V19H1 && build < 19020 {
		err = expandSandboxVolume(ctx, path)
		if err != nil {
			return err
		}
	}
	return nil
}

type virtualStorageType struct {
	DeviceID uint32
	VendorID [16]byte
}

type openVersion2 struct {
	GetInfoOnly    int32    // bool but 4-byte aligned
	ReadOnly       int32    // bool but 4-byte aligned
	ResiliencyGUID [16]byte // GUID
}

type openVirtualDiskParameters struct {
	Version  uint32 // Must always be set to 2
	Version2 openVersion2
}

func attachVhd(path string) (syscall.Handle, error) {
	var (
		defaultType virtualStorageType
		handle      syscall.Handle
	)
	parameters := openVirtualDiskParameters{Version: 2}
	err := openVirtualDisk(
		&defaultType,
		path,
		0,
		0,
		&parameters,
		&handle)
	if err != nil {
		return 0, &os.PathError{Op: "OpenVirtualDisk", Path: path, Err: err}
	}
	err = attachVirtualDisk(handle, 0, 0, 0, 0, 0)
	if err != nil {
		syscall.Close(handle)
		return 0, &os.PathError{Op: "AttachVirtualDisk", Path: path, Err: err}
	}
	return handle, nil
}

func expandSandboxVolume(ctx context.Context, path string) error {
	// Mount the sandbox VHD temporarily.
	vhdPath := filepath.Join(path, "sandbox.vhdx")
	vhd, err := attachVhd(vhdPath)
	if err != nil {
		return &os.PathError{Op: "OpenVirtualDisk", Path: vhdPath, Err: err}
	}
	defer syscall.Close(vhd)

	// Open the volume.
	volumePath, err := GetLayerMountPath(ctx, path)
	if err != nil {
		return err
	}
	if volumePath[len(volumePath)-1] == '\\' {
		volumePath = volumePath[:len(volumePath)-1]
	}
	volume, err := os.OpenFile(volumePath, os.O_RDWR, 0)
	if err != nil {
		return err
	}
	defer volume.Close()

	// Get the volume's underlying partition size in NTFS clusters.
	var (
		partitionSize int64
		bytes         uint32
	)
	const _IOCTL_DISK_GET_LENGTH_INFO = 0x0007405C
	err = syscall.DeviceIoControl(syscall.Handle(volume.Fd()), _IOCTL_DISK_GET_LENGTH_INFO, nil, 0, (*byte)(unsafe.Pointer(&partitionSize)), 8, &bytes, nil)
	if err != nil {
		return &os.PathError{Op: "IOCTL_DISK_GET_LENGTH_INFO", Path: volume.Name(), Err: err}
	}
	const (
		clusterSize = 4096
		sectorSize  = 512
	)
	targetClusters := partitionSize / clusterSize

	// Get the volume's current size in NTFS clusters.
	var volumeSize int64
	err = getDiskFreeSpaceEx(volume.Name()+"\\", nil, &volumeSize, nil)
	if err != nil {
		return &os.PathError{Op: "GetDiskFreeSpaceEx", Path: volume.Name(), Err: err}
	}
	volumeClusters := volumeSize / clusterSize

	// Only resize the volume if there is space to grow, otherwise this will
	// fail with invalid parameter. NTFS reserves one cluster.
	if volumeClusters+1 < targetClusters {
		targetSectors := targetClusters * (clusterSize / sectorSize)
		const _FSCTL_EXTEND_VOLUME = 0x000900F0
		err = syscall.DeviceIoControl(syscall.Handle(volume.Fd()), _FSCTL_EXTEND_VOLUME, (*byte)(unsafe.Pointer(&targetSectors)), 8, nil, 0, &bytes, nil)
		if err != nil {
			return &os.PathError{Op: "FSCTL_EXTEND_VOLUME", Path: volume.Name(), Err: err}
		}
	}
	return nil
}