1
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
28
29 ErrMaxVPMemLayerSize = errors.New("layer size is to large for VPMEM max size")
30 )
31
32
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
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
70
71 func readVeritySuperBlock(ctx context.Context, layerPath string) (*guestresource.DeviceVerityInfo, error) {
72
73
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
104
105
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
120
121
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
138
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
201
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