mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #33852 from jstarks/win_named_pipes
Windows: named pipe mounts
This commit is contained in:
commit
202cf001dd
10 changed files with 209 additions and 62 deletions
|
@ -15,6 +15,8 @@ const (
|
||||||
TypeVolume Type = "volume"
|
TypeVolume Type = "volume"
|
||||||
// TypeTmpfs is the type for mounting tmpfs
|
// TypeTmpfs is the type for mounting tmpfs
|
||||||
TypeTmpfs Type = "tmpfs"
|
TypeTmpfs Type = "tmpfs"
|
||||||
|
// TypeNamedPipe is the type for mounting Windows named pipes
|
||||||
|
TypeNamedPipe Type = "npipe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mount represents a mount (volume).
|
// Mount represents a mount (volume).
|
||||||
|
|
71
integration-cli/docker_api_containers_windows_test.go
Normal file
71
integration-cli/docker_api_containers_windows_test.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
winio "github.com/Microsoft/go-winio"
|
||||||
|
"github.com/docker/docker/integration-cli/checker"
|
||||||
|
"github.com/docker/docker/integration-cli/request"
|
||||||
|
"github.com/go-check/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestContainersAPICreateMountsBindNamedPipe(c *check.C) {
|
||||||
|
testRequires(c, SameHostDaemon, DaemonIsWindowsAtLeastBuild(16210)) // Named pipe support was added in RS3
|
||||||
|
|
||||||
|
// Create a host pipe to map into the container
|
||||||
|
hostPipeName := fmt.Sprintf(`\\.\pipe\docker-cli-test-pipe-%x`, rand.Uint64())
|
||||||
|
pc := &winio.PipeConfig{
|
||||||
|
SecurityDescriptor: "D:P(A;;GA;;;AU)", // Allow all users access to the pipe
|
||||||
|
}
|
||||||
|
l, err := winio.ListenPipe(hostPipeName, pc)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
// Asynchronously read data that the container writes to the mapped pipe.
|
||||||
|
var b []byte
|
||||||
|
ch := make(chan error)
|
||||||
|
go func() {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
if err == nil {
|
||||||
|
b, err = ioutil.ReadAll(conn)
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
ch <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
containerPipeName := `\\.\pipe\docker-cli-test-pipe`
|
||||||
|
text := "hello from a pipe"
|
||||||
|
cmd := fmt.Sprintf("echo %s > %s", text, containerPipeName)
|
||||||
|
|
||||||
|
name := "test-bind-npipe"
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"Image": testEnv.MinimalBaseImage(),
|
||||||
|
"Cmd": []string{"cmd", "/c", cmd},
|
||||||
|
"HostConfig": map[string]interface{}{"Mounts": []map[string]interface{}{{"Type": "npipe", "Source": hostPipeName, "Target": containerPipeName}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
status, resp, err := request.SockRequest("POST", "/containers/create?name="+name, data, daemonHost())
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf(string(resp)))
|
||||||
|
c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(resp)))
|
||||||
|
|
||||||
|
status, _, err = request.SockRequest("POST", "/containers/"+name+"/start", nil, daemonHost())
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(status, checker.Equals, http.StatusNoContent)
|
||||||
|
|
||||||
|
err = <-ch
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
result := strings.TrimSpace(string(b))
|
||||||
|
if result != text {
|
||||||
|
c.Errorf("expected pipe to contain %s, got %s", text, result)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4610,10 +4610,7 @@ func (s *DockerSuite) TestRunAddDeviceCgroupRule(c *check.C) {
|
||||||
|
|
||||||
// Verifies that running as local system is operating correctly on Windows
|
// Verifies that running as local system is operating correctly on Windows
|
||||||
func (s *DockerSuite) TestWindowsRunAsSystem(c *check.C) {
|
func (s *DockerSuite) TestWindowsRunAsSystem(c *check.C) {
|
||||||
testRequires(c, DaemonIsWindows)
|
testRequires(c, DaemonIsWindowsAtLeastBuild(15000))
|
||||||
if testEnv.DaemonKernelVersionNumeric() < 15000 {
|
|
||||||
c.Skip("Requires build 15000 or later")
|
|
||||||
}
|
|
||||||
out, _ := dockerCmd(c, "run", "--net=none", `--user=nt authority\system`, "--hostname=XYZZY", minimalBaseImage(), "cmd", "/c", `@echo %USERNAME%`)
|
out, _ := dockerCmd(c, "run", "--net=none", `--user=nt authority\system`, "--hostname=XYZZY", minimalBaseImage(), "cmd", "/c", `@echo %USERNAME%`)
|
||||||
c.Assert(strings.TrimSpace(out), checker.Equals, "XYZZY$")
|
c.Assert(strings.TrimSpace(out), checker.Equals, "XYZZY$")
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,12 @@ func DaemonIsWindows() bool {
|
||||||
return PlatformIs("windows")
|
return PlatformIs("windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DaemonIsWindowsAtLeastBuild(buildNumber int) func() bool {
|
||||||
|
return func() bool {
|
||||||
|
return DaemonIsWindows() && testEnv.DaemonKernelVersionNumeric() >= buildNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func DaemonIsLinux() bool {
|
func DaemonIsLinux() bool {
|
||||||
return PlatformIs("linux")
|
return PlatformIs("linux")
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim"
|
"github.com/Microsoft/hcsshim"
|
||||||
"github.com/docker/docker/pkg/sysinfo"
|
"github.com/docker/docker/pkg/sysinfo"
|
||||||
|
"github.com/docker/docker/pkg/system"
|
||||||
opengcs "github.com/jhowardmsft/opengcs/gogcs/client"
|
opengcs "github.com/jhowardmsft/opengcs/gogcs/client"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -230,20 +231,35 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the mounts (volumes, bind mounts etc) to the structure
|
// Add the mounts (volumes, bind mounts etc) to the structure
|
||||||
mds := make([]hcsshim.MappedDir, len(spec.Mounts))
|
var mds []hcsshim.MappedDir
|
||||||
for i, mount := range spec.Mounts {
|
var mps []hcsshim.MappedPipe
|
||||||
mds[i] = hcsshim.MappedDir{
|
for _, mount := range spec.Mounts {
|
||||||
|
const pipePrefix = `\\.\pipe\`
|
||||||
|
if strings.HasPrefix(mount.Destination, pipePrefix) {
|
||||||
|
mp := hcsshim.MappedPipe{
|
||||||
|
HostPath: mount.Source,
|
||||||
|
ContainerPipeName: mount.Destination[len(pipePrefix):],
|
||||||
|
}
|
||||||
|
mps = append(mps, mp)
|
||||||
|
} else {
|
||||||
|
md := hcsshim.MappedDir{
|
||||||
HostPath: mount.Source,
|
HostPath: mount.Source,
|
||||||
ContainerPath: mount.Destination,
|
ContainerPath: mount.Destination,
|
||||||
ReadOnly: false,
|
ReadOnly: false,
|
||||||
}
|
}
|
||||||
for _, o := range mount.Options {
|
for _, o := range mount.Options {
|
||||||
if strings.ToLower(o) == "ro" {
|
if strings.ToLower(o) == "ro" {
|
||||||
mds[i].ReadOnly = true
|
md.ReadOnly = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mds = append(mds, md)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
configuration.MappedDirectories = mds
|
configuration.MappedDirectories = mds
|
||||||
|
if len(mps) > 0 && system.GetOSVersion().Build < 16210 { // replace with Win10 RS3 build number at RTM
|
||||||
|
return errors.New("named pipe mounts are not supported on this version of Windows")
|
||||||
|
}
|
||||||
|
configuration.MappedPipes = mps
|
||||||
|
|
||||||
hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
|
hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"runtime"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
)
|
)
|
||||||
|
@ -13,7 +13,6 @@ var errBindNotExist = errors.New("bind source path does not exist")
|
||||||
|
|
||||||
type validateOpts struct {
|
type validateOpts struct {
|
||||||
skipBindSourceCheck bool
|
skipBindSourceCheck bool
|
||||||
skipAbsolutePathCheck bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error {
|
func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error {
|
||||||
|
@ -30,11 +29,9 @@ func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error
|
||||||
return &errMountConfig{mnt, err}
|
return &errMountConfig{mnt, err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.skipAbsolutePathCheck {
|
|
||||||
if err := validateAbsolute(mnt.Target); err != nil {
|
if err := validateAbsolute(mnt.Target); err != nil {
|
||||||
return &errMountConfig{mnt, err}
|
return &errMountConfig{mnt, err}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
switch mnt.Type {
|
switch mnt.Type {
|
||||||
case mount.TypeBind:
|
case mount.TypeBind:
|
||||||
|
@ -97,6 +94,31 @@ func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error
|
||||||
if _, err := ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
|
if _, err := ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
|
||||||
return &errMountConfig{mnt, err}
|
return &errMountConfig{mnt, err}
|
||||||
}
|
}
|
||||||
|
case mount.TypeNamedPipe:
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
return &errMountConfig{mnt, errors.New("named pipe bind mounts are not supported on this OS")}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(mnt.Source) == 0 {
|
||||||
|
return &errMountConfig{mnt, errMissingField("Source")}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mnt.BindOptions != nil {
|
||||||
|
return &errMountConfig{mnt, errExtraField("BindOptions")}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mnt.ReadOnly {
|
||||||
|
return &errMountConfig{mnt, errExtraField("ReadOnly")}
|
||||||
|
}
|
||||||
|
|
||||||
|
if detectMountType(mnt.Source) != mount.TypeNamedPipe {
|
||||||
|
return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Source)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if detectMountType(mnt.Target) != mount.TypeNamedPipe {
|
||||||
|
return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Target)}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return &errMountConfig{mnt, errors.New("mount type unknown")}
|
return &errMountConfig{mnt, errors.New("mount type unknown")}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +143,7 @@ func errMissingField(name string) error {
|
||||||
|
|
||||||
func validateAbsolute(p string) error {
|
func validateAbsolute(p string) error {
|
||||||
p = convertSlash(p)
|
p = convertSlash(p)
|
||||||
if filepath.IsAbs(p) {
|
if isAbsPath(p) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
|
return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package volume
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
@ -284,12 +283,7 @@ func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
|
||||||
return nil, errInvalidMode(mode)
|
return nil, errInvalidMode(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if filepath.IsAbs(spec.Source) {
|
spec.Type = detectMountType(spec.Source)
|
||||||
spec.Type = mounttypes.TypeBind
|
|
||||||
} else {
|
|
||||||
spec.Type = mounttypes.TypeVolume
|
|
||||||
}
|
|
||||||
|
|
||||||
spec.ReadOnly = !ReadWrite(mode)
|
spec.ReadOnly = !ReadWrite(mode)
|
||||||
|
|
||||||
// cannot assume that if a volume driver is passed in that we should set it
|
// cannot assume that if a volume driver is passed in that we should set it
|
||||||
|
@ -350,7 +344,7 @@ func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*Moun
|
||||||
mp.CopyData = false
|
mp.CopyData = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case mounttypes.TypeBind:
|
case mounttypes.TypeBind, mounttypes.TypeNamedPipe:
|
||||||
mp.Source = clean(convertSlash(cfg.Source))
|
mp.Source = clean(convertSlash(cfg.Source))
|
||||||
if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
|
if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
|
||||||
mp.Propagation = cfg.BindOptions.Propagation
|
mp.Propagation = cfg.BindOptions.Propagation
|
||||||
|
|
|
@ -143,6 +143,7 @@ func TestParseMountRaw(t *testing.T) {
|
||||||
type testParseMountRaw struct {
|
type testParseMountRaw struct {
|
||||||
bind string
|
bind string
|
||||||
driver string
|
driver string
|
||||||
|
expType mount.Type
|
||||||
expDest string
|
expDest string
|
||||||
expSource string
|
expSource string
|
||||||
expName string
|
expName string
|
||||||
|
@ -155,28 +156,31 @@ func TestParseMountRawSplit(t *testing.T) {
|
||||||
var cases []testParseMountRaw
|
var cases []testParseMountRaw
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
cases = []testParseMountRaw{
|
cases = []testParseMountRaw{
|
||||||
{`c:\:d:`, "local", `d:`, `c:\`, ``, "", true, false},
|
{`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false},
|
||||||
{`c:\:d:\`, "local", `d:\`, `c:\`, ``, "", true, false},
|
{`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
|
||||||
{`c:\:d:\:ro`, "local", `d:\`, `c:\`, ``, "", false, false},
|
{`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false},
|
||||||
{`c:\:d:\:rw`, "local", `d:\`, `c:\`, ``, "", true, false},
|
{`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
|
||||||
{`c:\:d:\:foo`, "local", `d:\`, `c:\`, ``, "", false, true},
|
{`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true},
|
||||||
{`name:d::rw`, "local", `d:`, ``, `name`, "local", true, false},
|
{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false},
|
||||||
{`name:d:`, "local", `d:`, ``, `name`, "local", true, false},
|
{`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
||||||
{`name:d::ro`, "local", `d:`, ``, `name`, "local", false, false},
|
{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
||||||
{`name:c:`, "", ``, ``, ``, "", true, true},
|
{`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
|
||||||
{`driver/name:c:`, "", ``, ``, ``, "", true, true},
|
{`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
|
||||||
|
{`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false},
|
||||||
|
{`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
||||||
|
{`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cases = []testParseMountRaw{
|
cases = []testParseMountRaw{
|
||||||
{"/tmp:/tmp1", "", "/tmp1", "/tmp", "", "", true, false},
|
{"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
|
||||||
{"/tmp:/tmp2:ro", "", "/tmp2", "/tmp", "", "", false, false},
|
{"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
|
||||||
{"/tmp:/tmp3:rw", "", "/tmp3", "/tmp", "", "", true, false},
|
{"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
|
||||||
{"/tmp:/tmp4:foo", "", "", "", "", "", false, true},
|
{"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
|
||||||
{"name:/named1", "", "/named1", "", "name", "", true, false},
|
{"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
|
||||||
{"name:/named2", "external", "/named2", "", "name", "external", true, false},
|
{"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
|
||||||
{"name:/named3:ro", "local", "/named3", "", "name", "local", false, false},
|
{"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
|
||||||
{"local/name:/tmp:rw", "", "/tmp", "", "local/name", "", true, false},
|
{"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
|
||||||
{"/tmp:tmp", "", "", "", "", "", true, true},
|
{"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,8 +199,12 @@ func TestParseMountRawSplit(t *testing.T) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.Type != c.expType {
|
||||||
|
t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind)
|
||||||
|
}
|
||||||
|
|
||||||
if m.Destination != c.expDest {
|
if m.Destination != c.expDest {
|
||||||
t.Fatalf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
|
t.Fatalf("Expected destination '%s', was '%s', for spec '%s'", c.expDest, m.Destination, c.bind)
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.Source != c.expSource {
|
if m.Source != c.expSource {
|
||||||
|
|
|
@ -124,7 +124,12 @@ func validateCopyMode(mode bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertSlash(p string) string {
|
func convertSlash(p string) string {
|
||||||
return filepath.ToSlash(p)
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAbsPath reports whether the path is absolute.
|
||||||
|
func isAbsPath(p string) bool {
|
||||||
|
return filepath.IsAbs(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitRawSpec(raw string) ([]string, error) {
|
func splitRawSpec(raw string) ([]string, error) {
|
||||||
|
@ -139,6 +144,13 @@ func splitRawSpec(raw string) ([]string, error) {
|
||||||
return arr, nil
|
return arr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func detectMountType(p string) mounttypes.Type {
|
||||||
|
if filepath.IsAbs(p) {
|
||||||
|
return mounttypes.TypeBind
|
||||||
|
}
|
||||||
|
return mounttypes.TypeVolume
|
||||||
|
}
|
||||||
|
|
||||||
func clean(p string) string {
|
func clean(p string) string {
|
||||||
return filepath.Clean(p)
|
return filepath.Clean(p)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
mounttypes "github.com/docker/docker/api/types/mount"
|
||||||
)
|
)
|
||||||
|
|
||||||
// read-write modes
|
// read-write modes
|
||||||
|
@ -18,14 +20,7 @@ var roModes = map[string]bool{
|
||||||
"ro": true,
|
"ro": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var platformRawValidationOpts = []func(*validateOpts){
|
var platformRawValidationOpts = []func(*validateOpts){}
|
||||||
// filepath.IsAbs is weird on Windows:
|
|
||||||
// `c:` is not considered an absolute path
|
|
||||||
// `c:\` is considered an absolute path
|
|
||||||
// In any case, the regex matching below ensures absolute paths
|
|
||||||
// TODO: consider this a bug with filepath.IsAbs (?)
|
|
||||||
func(o *validateOpts) { o.skipAbsolutePathCheck = true },
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Spec should be in the format [source:]destination[:mode]
|
// Spec should be in the format [source:]destination[:mode]
|
||||||
|
@ -49,11 +44,13 @@ const (
|
||||||
RXHostDir = `[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\?)*`
|
RXHostDir = `[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\?)*`
|
||||||
// RXName is the second option of a source
|
// RXName is the second option of a source
|
||||||
RXName = `[^\\/:*?"<>|\r\n]+`
|
RXName = `[^\\/:*?"<>|\r\n]+`
|
||||||
|
// RXPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \)
|
||||||
|
RXPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
|
||||||
// RXReservedNames are reserved names not possible on Windows
|
// RXReservedNames are reserved names not possible on Windows
|
||||||
RXReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
|
RXReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
|
||||||
|
|
||||||
// RXSource is the combined possibilities for a source
|
// RXSource is the combined possibilities for a source
|
||||||
RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `))):)?`
|
RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `)|(` + RXPipe + `))):)?`
|
||||||
|
|
||||||
// Source. Can be either a host directory, a name, or omitted:
|
// Source. Can be either a host directory, a name, or omitted:
|
||||||
// HostDir:
|
// HostDir:
|
||||||
|
@ -69,8 +66,10 @@ const (
|
||||||
// - And then followed by a colon which is not in the capture group
|
// - And then followed by a colon which is not in the capture group
|
||||||
// - And can be optional
|
// - And can be optional
|
||||||
|
|
||||||
|
// RXDestinationDir is the file path option for the mount destination
|
||||||
|
RXDestinationDir = `([a-z]):((?:\\[^\\/:*?"<>\r\n]+)*\\?)`
|
||||||
// RXDestination is the regex expression for the mount destination
|
// RXDestination is the regex expression for the mount destination
|
||||||
RXDestination = `(?P<destination>([a-z]):((?:\\[^\\/:*?"<>\r\n]+)*\\?))`
|
RXDestination = `(?P<destination>(` + RXDestinationDir + `)|(` + RXPipe + `))`
|
||||||
// Destination (aka container path):
|
// Destination (aka container path):
|
||||||
// - Variation on hostdir but can be a drive followed by colon as well
|
// - Variation on hostdir but can be a drive followed by colon as well
|
||||||
// - If a path, must be absolute. Can include spaces
|
// - If a path, must be absolute. Can include spaces
|
||||||
|
@ -140,6 +139,15 @@ func splitRawSpec(raw string) ([]string, error) {
|
||||||
return split, nil
|
return split, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func detectMountType(p string) mounttypes.Type {
|
||||||
|
if strings.HasPrefix(filepath.FromSlash(p), `\\.\pipe\`) {
|
||||||
|
return mounttypes.TypeNamedPipe
|
||||||
|
} else if filepath.IsAbs(p) {
|
||||||
|
return mounttypes.TypeBind
|
||||||
|
}
|
||||||
|
return mounttypes.TypeVolume
|
||||||
|
}
|
||||||
|
|
||||||
// IsVolumeNameValid checks a volume name in a platform specific manner.
|
// IsVolumeNameValid checks a volume name in a platform specific manner.
|
||||||
func IsVolumeNameValid(name string) (bool, error) {
|
func IsVolumeNameValid(name string) (bool, error) {
|
||||||
nameExp := regexp.MustCompile(`^` + RXName + `$`)
|
nameExp := regexp.MustCompile(`^` + RXName + `$`)
|
||||||
|
@ -186,8 +194,19 @@ func convertSlash(p string) string {
|
||||||
return filepath.FromSlash(p)
|
return filepath.FromSlash(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isAbsPath returns whether a path is absolute for the purposes of mounting into a container
|
||||||
|
// (absolute paths, drive letter paths such as X:, and paths starting with `\\.\` to support named pipes).
|
||||||
|
func isAbsPath(p string) bool {
|
||||||
|
return filepath.IsAbs(p) ||
|
||||||
|
strings.HasPrefix(p, `\\.\`) ||
|
||||||
|
(len(p) == 2 && p[1] == ':' && ((p[0] >= 'a' && p[0] <= 'z') || (p[0] >= 'A' && p[0] <= 'Z')))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not clean plain drive letters or paths starting with `\\.\`.
|
||||||
|
var cleanRegexp = regexp.MustCompile(`^([a-z]:|[/\\]{2}\.[/\\].*)$`)
|
||||||
|
|
||||||
func clean(p string) string {
|
func clean(p string) string {
|
||||||
if match, _ := regexp.MatchString("^[a-z]:$", p); match {
|
if match := cleanRegexp.MatchString(p); match {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
return filepath.Clean(p)
|
return filepath.Clean(p)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue