1// +build windows
2
3/*
4   Copyright The containerd Authors.
5
6   Licensed under the Apache License, Version 2.0 (the "License");
7   you may not use this file except in compliance with the License.
8   You may obtain a copy of the License at
9
10       http://www.apache.org/licenses/LICENSE-2.0
11
12   Unless required by applicable law or agreed to in writing, software
13   distributed under the License is distributed on an "AS IS" BASIS,
14   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   See the License for the specific language governing permissions and
16   limitations under the License.
17*/
18
19package opts
20
21import (
22	"context"
23	"path/filepath"
24	"sort"
25	"strings"
26
27	"github.com/containerd/containerd/containers"
28	"github.com/containerd/containerd/oci"
29	runtimespec "github.com/opencontainers/runtime-spec/specs-go"
30	"github.com/pkg/errors"
31	runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
32
33	osinterface "github.com/containerd/containerd/pkg/os"
34)
35
36// WithWindowsNetworkNamespace sets windows network namespace for container.
37// TODO(windows): Move this into container/containerd.
38func WithWindowsNetworkNamespace(path string) oci.SpecOpts {
39	return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
40		if s.Windows == nil {
41			s.Windows = &runtimespec.Windows{}
42		}
43		if s.Windows.Network == nil {
44			s.Windows.Network = &runtimespec.WindowsNetwork{}
45		}
46		s.Windows.Network.NetworkNamespace = path
47		return nil
48	}
49}
50
51// namedPipePath returns true if the given path is to a named pipe.
52func namedPipePath(p string) bool {
53	return strings.HasPrefix(p, `\\.\pipe\`)
54}
55
56// cleanMount returns a cleaned version of the mount path. The input is returned
57// as-is if it is a named pipe path.
58func cleanMount(p string) string {
59	if namedPipePath(p) {
60		return p
61	}
62	return filepath.Clean(p)
63}
64
65// WithWindowsMounts sorts and adds runtime and CRI mounts to the spec for
66// windows container.
67func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount) oci.SpecOpts {
68	return func(ctx context.Context, client oci.Client, _ *containers.Container, s *runtimespec.Spec) error {
69		// mergeMounts merge CRI mounts with extra mounts. If a mount destination
70		// is mounted by both a CRI mount and an extra mount, the CRI mount will
71		// be kept.
72		var (
73			criMounts = config.GetMounts()
74			mounts    = append([]*runtime.Mount{}, criMounts...)
75		)
76		// Copy all mounts from extra mounts, except for mounts overridden by CRI.
77		for _, e := range extra {
78			found := false
79			for _, c := range criMounts {
80				if cleanMount(e.ContainerPath) == cleanMount(c.ContainerPath) {
81					found = true
82					break
83				}
84			}
85			if !found {
86				mounts = append(mounts, e)
87			}
88		}
89
90		// Sort mounts in number of parts. This ensures that high level mounts don't
91		// shadow other mounts.
92		sort.Sort(orderedMounts(mounts))
93
94		// Copy all mounts from default mounts, except for
95		// mounts overridden by supplied mount;
96		mountSet := make(map[string]struct{})
97		for _, m := range mounts {
98			mountSet[cleanMount(m.ContainerPath)] = struct{}{}
99		}
100
101		defaultMounts := s.Mounts
102		s.Mounts = nil
103
104		for _, m := range defaultMounts {
105			dst := cleanMount(m.Destination)
106			if _, ok := mountSet[dst]; ok {
107				// filter out mount overridden by a supplied mount
108				continue
109			}
110			s.Mounts = append(s.Mounts, m)
111		}
112
113		for _, mount := range mounts {
114			var (
115				dst = mount.GetContainerPath()
116				src = mount.GetHostPath()
117			)
118			// In the case of a named pipe mount on Windows, don't stat the file
119			// or do other operations that open it, as that could interfere with
120			// the listening process. filepath.Clean also breaks named pipe
121			// paths, so don't use it.
122			if !namedPipePath(src) {
123				if _, err := osi.Stat(src); err != nil {
124					// If the source doesn't exist, return an error instead
125					// of creating the source. This aligns with Docker's
126					// behavior on windows.
127					return errors.Wrapf(err, "failed to stat %q", src)
128				}
129				var err error
130				src, err = osi.ResolveSymbolicLink(src)
131				if err != nil {
132					return errors.Wrapf(err, "failed to resolve symlink %q", src)
133				}
134				// hcsshim requires clean path, especially '/' -> '\'.
135				src = filepath.Clean(src)
136				dst = filepath.Clean(dst)
137			}
138
139			var options []string
140			// NOTE(random-liu): we don't change all mounts to `ro` when root filesystem
141			// is readonly. This is different from docker's behavior, but make more sense.
142			if mount.GetReadonly() {
143				options = append(options, "ro")
144			} else {
145				options = append(options, "rw")
146			}
147			s.Mounts = append(s.Mounts, runtimespec.Mount{
148				Source:      src,
149				Destination: dst,
150				Options:     options,
151			})
152		}
153		return nil
154	}
155}
156
157// WithWindowsResources sets the provided resource restrictions for windows.
158func WithWindowsResources(resources *runtime.WindowsContainerResources) oci.SpecOpts {
159	return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
160		if resources == nil {
161			return nil
162		}
163		if s.Windows == nil {
164			s.Windows = &runtimespec.Windows{}
165		}
166		if s.Windows.Resources == nil {
167			s.Windows.Resources = &runtimespec.WindowsResources{}
168		}
169		if s.Windows.Resources.CPU == nil {
170			s.Windows.Resources.CPU = &runtimespec.WindowsCPUResources{}
171		}
172		if s.Windows.Resources.Memory == nil {
173			s.Windows.Resources.Memory = &runtimespec.WindowsMemoryResources{}
174		}
175
176		var (
177			count  = uint64(resources.GetCpuCount())
178			shares = uint16(resources.GetCpuShares())
179			max    = uint16(resources.GetCpuMaximum())
180			limit  = uint64(resources.GetMemoryLimitInBytes())
181		)
182		if count != 0 {
183			s.Windows.Resources.CPU.Count = &count
184		}
185		if shares != 0 {
186			s.Windows.Resources.CPU.Shares = &shares
187		}
188		if max != 0 {
189			s.Windows.Resources.CPU.Maximum = &max
190		}
191		if limit != 0 {
192			s.Windows.Resources.Memory.Limit = &limit
193		}
194		return nil
195	}
196}
197
198// WithWindowsDefaultSandboxShares sets the default sandbox CPU shares
199func WithWindowsDefaultSandboxShares(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
200	if s.Windows == nil {
201		s.Windows = &runtimespec.Windows{}
202	}
203	if s.Windows.Resources == nil {
204		s.Windows.Resources = &runtimespec.WindowsResources{}
205	}
206	if s.Windows.Resources.CPU == nil {
207		s.Windows.Resources.CPU = &runtimespec.WindowsCPUResources{}
208	}
209	i := uint16(DefaultSandboxCPUshares)
210	s.Windows.Resources.CPU.Shares = &i
211	return nil
212}
213
214// WithWindowsCredentialSpec assigns `credentialSpec` to the
215// `runtime.Spec.Windows.CredentialSpec` field.
216func WithWindowsCredentialSpec(credentialSpec string) oci.SpecOpts {
217	return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
218		if s.Windows == nil {
219			s.Windows = &runtimespec.Windows{}
220		}
221		s.Windows.CredentialSpec = credentialSpec
222		return nil
223	}
224}
225