1
2
3 package uvm
4
5 import (
6 "context"
7 "fmt"
8 "os"
9 "path/filepath"
10 "strconv"
11 "unsafe"
12
13 "github.com/sirupsen/logrus"
14 "golang.org/x/sys/windows"
15
16 "github.com/Microsoft/hcsshim/internal/hcs"
17 "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths"
18 hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
19 "github.com/Microsoft/hcsshim/internal/log"
20 "github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
21 "github.com/Microsoft/hcsshim/internal/winapi"
22 "github.com/Microsoft/hcsshim/osversion"
23 )
24
25 const (
26 vsmbSharePrefix = `\\?\VMSMB\VSMB-{dcc079ae-60ba-4d07-847c-3493609c0870}\`
27 )
28
29
30 type VSMBShare struct {
31
32 vm *UtilityVM
33 HostPath string
34 refCount uint32
35 name string
36 allowedFiles []string
37 guestPath string
38 options hcsschema.VirtualSmbShareOptions
39 }
40
41
42 func (vsmb *VSMBShare) Release(ctx context.Context) error {
43 if err := vsmb.vm.RemoveVSMB(ctx, vsmb.HostPath, vsmb.options.ReadOnly); err != nil {
44 return fmt.Errorf("failed to remove VSMB share: %s", err)
45 }
46 return nil
47 }
48
49
50
51 func (uvm *UtilityVM) DefaultVSMBOptions(readOnly bool) *hcsschema.VirtualSmbShareOptions {
52 opts := &hcsschema.VirtualSmbShareOptions{
53 NoDirectmap: uvm.DevicesPhysicallyBacked() || uvm.VSMBNoDirectMap(),
54 }
55 if readOnly {
56 opts.ShareRead = true
57 opts.CacheIo = true
58 opts.ReadOnly = true
59 opts.PseudoOplocks = true
60 }
61 return opts
62 }
63
64
65 func (uvm *UtilityVM) findVSMBShare(ctx context.Context, m map[string]*VSMBShare, shareKey string) (*VSMBShare, error) {
66 share, ok := m[shareKey]
67 if !ok {
68 return nil, ErrNotAttached
69 }
70 return share, nil
71 }
72
73
74
75
76
77
78
79
80
81
82
83
84
85 func openHostPath(path string) (windows.Handle, error) {
86 u16, err := windows.UTF16PtrFromString(path)
87 if err != nil {
88 return 0, err
89 }
90 h, err := windows.CreateFile(
91 u16,
92 0,
93 windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
94 nil,
95 windows.OPEN_EXISTING,
96 windows.FILE_FLAG_BACKUP_SEMANTICS,
97 0)
98 if err != nil {
99 return 0, &os.PathError{
100 Op: "CreateFile",
101 Path: path,
102 Err: err,
103 }
104 }
105 return h, nil
106 }
107
108
109
110
111
112
113
114
115
116
117
118 func forceNoDirectMap(path string) (bool, error) {
119 if ver := osversion.Build(); ver < osversion.V19H1 || ver > osversion.V20H2 {
120 return false, nil
121 }
122 h, err := openHostPath(path)
123 if err != nil {
124 return false, err
125 }
126 defer func() {
127 _ = windows.CloseHandle(h)
128 }()
129 var info winapi.FILE_ID_INFO
130
131
132 if err := windows.GetFileInformationByHandleEx(h, winapi.FileIdInfo, (*byte)(unsafe.Pointer(&info)), uint32(unsafe.Sizeof(info))); err != nil {
133 return true, nil
134 }
135 return false, nil
136 }
137
138
139
140
141 func (uvm *UtilityVM) AddVSMB(ctx context.Context, hostPath string, options *hcsschema.VirtualSmbShareOptions) (*VSMBShare, error) {
142 if uvm.operatingSystem != "windows" {
143 return nil, errNotSupported
144 }
145
146 if !options.ReadOnly && uvm.NoWritableFileShares() {
147 return nil, fmt.Errorf("adding writable shares is denied: %w", hcs.ErrOperationDenied)
148 }
149
150 uvm.m.Lock()
151 defer uvm.m.Unlock()
152
153
154
155
156
157
158
159 st, err := os.Stat(hostPath)
160 if err != nil {
161 return nil, err
162 }
163 var file string
164 m := uvm.vsmbDirShares
165 if !st.IsDir() {
166 m = uvm.vsmbFileShares
167 file = hostPath
168 hostPath = filepath.Dir(hostPath)
169 options.RestrictFileAccess = true
170 options.SingleFileMapping = true
171 }
172 hostPath = filepath.Clean(hostPath)
173
174 if force, err := forceNoDirectMap(hostPath); err != nil {
175 return nil, err
176 } else if force {
177 log.G(ctx).WithField("path", hostPath).Info("Forcing NoDirectmap for VSMB mount")
178 options.NoDirectmap = true
179 }
180
181 var requestType = guestrequest.RequestTypeUpdate
182 shareKey := getVSMBShareKey(hostPath, options.ReadOnly)
183 share, err := uvm.findVSMBShare(ctx, m, shareKey)
184 if err == ErrNotAttached {
185 requestType = guestrequest.RequestTypeAdd
186 uvm.vsmbCounter++
187 shareName := "s" + strconv.FormatUint(uvm.vsmbCounter, 16)
188
189 share = &VSMBShare{
190 vm: uvm,
191 name: shareName,
192 guestPath: vsmbSharePrefix + shareName,
193 HostPath: hostPath,
194 }
195 }
196 newAllowedFiles := share.allowedFiles
197 if options.RestrictFileAccess {
198 newAllowedFiles = append(newAllowedFiles, file)
199 }
200
201
202
203
204
205 if requestType == guestrequest.RequestTypeAdd || options.RestrictFileAccess {
206 log.G(ctx).WithFields(logrus.Fields{
207 "name": share.name,
208 "path": hostPath,
209 "options": fmt.Sprintf("%+#v", options),
210 "operation": requestType,
211 }).Info("Modifying VSMB share")
212 modification := &hcsschema.ModifySettingRequest{
213 RequestType: requestType,
214 Settings: hcsschema.VirtualSmbShare{
215 Name: share.name,
216 Options: options,
217 Path: hostPath,
218 AllowedFiles: newAllowedFiles,
219 },
220 ResourcePath: resourcepaths.VSMBShareResourcePath,
221 }
222 if err := uvm.modify(ctx, modification); err != nil {
223 return nil, err
224 }
225 }
226
227 share.allowedFiles = newAllowedFiles
228 share.refCount++
229 share.options = *options
230 m[shareKey] = share
231 return share, nil
232 }
233
234
235
236 func (uvm *UtilityVM) RemoveVSMB(ctx context.Context, hostPath string, readOnly bool) error {
237 if uvm.operatingSystem != "windows" {
238 return errNotSupported
239 }
240
241 uvm.m.Lock()
242 defer uvm.m.Unlock()
243
244 st, err := os.Stat(hostPath)
245 if err != nil {
246 return err
247 }
248 m := uvm.vsmbDirShares
249 if !st.IsDir() {
250 m = uvm.vsmbFileShares
251 hostPath = filepath.Dir(hostPath)
252 }
253 hostPath = filepath.Clean(hostPath)
254 shareKey := getVSMBShareKey(hostPath, readOnly)
255 share, err := uvm.findVSMBShare(ctx, m, shareKey)
256 if err != nil {
257 return fmt.Errorf("%s is not present as a VSMB share in %s, cannot remove", hostPath, uvm.id)
258 }
259
260 share.refCount--
261 if share.refCount > 0 {
262 return nil
263 }
264
265 modification := &hcsschema.ModifySettingRequest{
266 RequestType: guestrequest.RequestTypeRemove,
267 Settings: hcsschema.VirtualSmbShare{Name: share.name},
268 ResourcePath: resourcepaths.VSMBShareResourcePath,
269 }
270 if err := uvm.modify(ctx, modification); err != nil {
271 return fmt.Errorf("failed to remove vsmb share %s from %s: %+v: %s", hostPath, uvm.id, modification, err)
272 }
273
274 delete(m, shareKey)
275 return nil
276 }
277
278
279 func (uvm *UtilityVM) GetVSMBUvmPath(ctx context.Context, hostPath string, readOnly bool) (string, error) {
280 if hostPath == "" {
281 return "", fmt.Errorf("no hostPath passed to GetVSMBUvmPath")
282 }
283
284 uvm.m.Lock()
285 defer uvm.m.Unlock()
286
287 st, err := os.Stat(hostPath)
288 if err != nil {
289 return "", err
290 }
291 m := uvm.vsmbDirShares
292 f := ""
293 if !st.IsDir() {
294 m = uvm.vsmbFileShares
295 hostPath, f = filepath.Split(hostPath)
296 }
297 hostPath = filepath.Clean(hostPath)
298 shareKey := getVSMBShareKey(hostPath, readOnly)
299 share, err := uvm.findVSMBShare(ctx, m, shareKey)
300 if err != nil {
301 return "", err
302 }
303 return filepath.Join(share.guestPath, f), nil
304 }
305
306
307
308
309
310 func getVSMBShareKey(hostPath string, readOnly bool) string {
311 return fmt.Sprintf("%v-%v", hostPath, readOnly)
312 }
313
View as plain text