1package uvm
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"strconv"
8
9	"github.com/Microsoft/hcsshim/internal/guestrequest"
10	"github.com/Microsoft/hcsshim/internal/requesttype"
11	hcsschema "github.com/Microsoft/hcsshim/internal/schema2"
12	"github.com/Microsoft/hcsshim/osversion"
13)
14
15// Plan9Share is a struct containing host paths for the UVM
16type Plan9Share struct {
17	// UVM resource belongs to
18	vm            *UtilityVM
19	name, uvmPath string
20}
21
22// Release frees the resources of the corresponding Plan9 share
23func (p9 *Plan9Share) Release(ctx context.Context) error {
24	if err := p9.vm.RemovePlan9(ctx, p9); err != nil {
25		return fmt.Errorf("failed to remove plan9 share: %s", err)
26	}
27	return nil
28}
29
30const plan9Port = 564
31
32// AddPlan9 adds a Plan9 share to a utility VM.
33func (uvm *UtilityVM) AddPlan9(ctx context.Context, hostPath string, uvmPath string, readOnly bool, restrict bool, allowedNames []string) (*Plan9Share, error) {
34	if uvm.operatingSystem != "linux" {
35		return nil, errNotSupported
36	}
37	if restrict && osversion.Get().Build < osversion.V19H1 {
38		return nil, errors.New("single-file mappings are not supported on this build of Windows")
39	}
40	if uvmPath == "" {
41		return nil, fmt.Errorf("uvmPath must be passed to AddPlan9")
42	}
43
44	// TODO: JTERRY75 - These are marked private in the schema. For now use them
45	// but when there are public variants we need to switch to them.
46	const (
47		shareFlagsReadOnly           int32 = 0x00000001
48		shareFlagsLinuxMetadata      int32 = 0x00000004
49		shareFlagsCaseSensitive      int32 = 0x00000008
50		shareFlagsRestrictFileAccess int32 = 0x00000080
51	)
52
53	// TODO: JTERRY75 - `shareFlagsCaseSensitive` only works if the Windows
54	// `hostPath` supports case sensitivity. We need to detect this case before
55	// forwarding this flag in all cases.
56	flags := shareFlagsLinuxMetadata // | shareFlagsCaseSensitive
57	if readOnly {
58		flags |= shareFlagsReadOnly
59	}
60	if restrict {
61		flags |= shareFlagsRestrictFileAccess
62	}
63
64	uvm.m.Lock()
65	index := uvm.plan9Counter
66	uvm.plan9Counter++
67	uvm.m.Unlock()
68	name := strconv.FormatUint(index, 10)
69
70	modification := &hcsschema.ModifySettingRequest{
71		RequestType: requesttype.Add,
72		Settings: hcsschema.Plan9Share{
73			Name:         name,
74			AccessName:   name,
75			Path:         hostPath,
76			Port:         plan9Port,
77			Flags:        flags,
78			AllowedFiles: allowedNames,
79		},
80		ResourcePath: plan9ShareResourcePath,
81		GuestRequest: guestrequest.GuestRequest{
82			ResourceType: guestrequest.ResourceTypeMappedDirectory,
83			RequestType:  requesttype.Add,
84			Settings: guestrequest.LCOWMappedDirectory{
85				MountPath: uvmPath,
86				ShareName: name,
87				Port:      plan9Port,
88				ReadOnly:  readOnly,
89			},
90		},
91	}
92
93	if err := uvm.modify(ctx, modification); err != nil {
94		return nil, err
95	}
96
97	return &Plan9Share{
98		vm:      uvm,
99		name:    name,
100		uvmPath: uvmPath,
101	}, nil
102}
103
104// RemovePlan9 removes a Plan9 share from a utility VM. Each Plan9 share is ref-counted
105// and only actually removed when the ref-count drops to zero.
106func (uvm *UtilityVM) RemovePlan9(ctx context.Context, share *Plan9Share) error {
107	if uvm.operatingSystem != "linux" {
108		return errNotSupported
109	}
110
111	modification := &hcsschema.ModifySettingRequest{
112		RequestType: requesttype.Remove,
113		Settings: hcsschema.Plan9Share{
114			Name:       share.name,
115			AccessName: share.name,
116			Port:       plan9Port,
117		},
118		ResourcePath: plan9ShareResourcePath,
119		GuestRequest: guestrequest.GuestRequest{
120			ResourceType: guestrequest.ResourceTypeMappedDirectory,
121			RequestType:  requesttype.Remove,
122			Settings: guestrequest.LCOWMappedDirectory{
123				MountPath: share.uvmPath,
124				ShareName: share.name,
125				Port:      plan9Port,
126			},
127		},
128	}
129	if err := uvm.modify(ctx, modification); err != nil {
130		return fmt.Errorf("failed to remove plan9 share %s from %s: %+v: %s", share.name, uvm.id, modification, err)
131	}
132	return nil
133}
134