Skip to content

Commit 09d6426

Browse files
authored
Merge pull request #1511 from kevpar/named-pipe-mounts
Support named pipe mounts for Windows containers
2 parents 682d158 + 210561a commit 09d6426

2 files changed

Lines changed: 59 additions & 17 deletions

File tree

pkg/containerd/opts/spec_windows.go

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"context"
2323
"path/filepath"
2424
"sort"
25+
"strings"
2526

2627
"github.com/containerd/containerd/containers"
2728
"github.com/containerd/containerd/oci"
@@ -47,6 +48,20 @@ func WithWindowsNetworkNamespace(path string) oci.SpecOpts {
4748
}
4849
}
4950

51+
// namedPipePath returns true if the given path is to a named pipe.
52+
func 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.
58+
func cleanMount(p string) string {
59+
if namedPipePath(p) {
60+
return p
61+
}
62+
return filepath.Clean(p)
63+
}
64+
5065
// WithWindowsMounts sorts and adds runtime and CRI mounts to the spec for
5166
// windows container.
5267
func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount) oci.SpecOpts {
@@ -62,7 +77,7 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
6277
for _, e := range extra {
6378
found := false
6479
for _, c := range criMounts {
65-
if filepath.Clean(e.ContainerPath) == filepath.Clean(c.ContainerPath) {
80+
if cleanMount(e.ContainerPath) == cleanMount(c.ContainerPath) {
6681
found = true
6782
break
6883
}
@@ -80,14 +95,14 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
8095
// mounts overridden by supplied mount;
8196
mountSet := make(map[string]struct{})
8297
for _, m := range mounts {
83-
mountSet[filepath.Clean(m.ContainerPath)] = struct{}{}
98+
mountSet[cleanMount(m.ContainerPath)] = struct{}{}
8499
}
85100

86101
defaultMounts := s.Mounts
87102
s.Mounts = nil
88103

89104
for _, m := range defaultMounts {
90-
dst := filepath.Clean(m.Destination)
105+
dst := cleanMount(m.Destination)
91106
if _, ok := mountSet[dst]; ok {
92107
// filter out mount overridden by a supplied mount
93108
continue
@@ -100,17 +115,25 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
100115
dst = mount.GetContainerPath()
101116
src = mount.GetHostPath()
102117
)
103-
// TODO(windows): Support special mount sources, e.g. named pipe.
104-
// Create the host path if it doesn't exist.
105-
if _, err := osi.Stat(src); err != nil {
106-
// If the source doesn't exist, return an error instead
107-
// of creating the source. This aligns with Docker's
108-
// behavior on windows.
109-
return errors.Wrapf(err, "failed to stat %q", src)
110-
}
111-
src, err := osi.ResolveSymbolicLink(src)
112-
if err != nil {
113-
return errors.Wrapf(err, "failed to resolve symlink %q", src)
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)
114137
}
115138

116139
var options []string
@@ -122,9 +145,8 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
122145
options = append(options, "rw")
123146
}
124147
s.Mounts = append(s.Mounts, runtimespec.Mount{
125-
// hcsshim requires clean path, especially '/' -> '\'.
126-
Source: filepath.Clean(src),
127-
Destination: filepath.Clean(dst),
148+
Source: src,
149+
Destination: dst,
128150
Options: options,
129151
})
130152
}

pkg/server/container_create_windows_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,23 @@ func TestMountCleanPath(t *testing.T) {
167167
specCheck(t, testID, testSandboxID, testPid, spec)
168168
checkMount(t, spec.Mounts, "c:\\test\\host-path", "c:\\test\\container-path", "", []string{"rw"}, nil)
169169
}
170+
171+
func TestMountNamedPipe(t *testing.T) {
172+
testID := "test-id"
173+
testSandboxID := "sandbox-id"
174+
testContainerName := "container-name"
175+
testPid := uint32(1234)
176+
nsPath := "test-cni"
177+
c := newTestCRIService()
178+
179+
containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
180+
containerConfig.Mounts = append(containerConfig.Mounts, &runtime.Mount{
181+
ContainerPath: `\\.\pipe\foo`,
182+
HostPath: `\\.\pipe\foo`,
183+
})
184+
spec, err := c.containerSpec(testID, testSandboxID, testPid, nsPath, testContainerName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{})
185+
assert.NoError(t, err)
186+
assert.NotNil(t, spec)
187+
specCheck(t, testID, testSandboxID, testPid, spec)
188+
checkMount(t, spec.Mounts, `\\.\pipe\foo`, `\\.\pipe\foo`, "", []string{"rw"}, nil)
189+
}

0 commit comments

Comments
 (0)