summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason T. Greene <jason.greene@redhat.com>2022-03-24 22:07:55 -0500
committerJason T. Greene <jason.greene@redhat.com>2022-04-25 13:52:27 -0500
commitb0d36f63513ee64fa1c1eff4d1045a7633804f12 (patch)
tree3bb66bef188e12daae252a18234e544a9be1b145
parent3b6ffcd290978f5e0110e925c212d6396accee10 (diff)
downloadpodman-b0d36f63513ee64fa1c1eff4d1045a7633804f12.tar.gz
podman-b0d36f63513ee64fa1c1eff4d1045a7633804f12.tar.bz2
podman-b0d36f63513ee64fa1c1eff4d1045a7633804f12.zip
Implements Windows volume/mount support
Based on WSL2 9p support: remaps windows paths to /mnt/<drive> locations for both podman and Docker API clients. Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
-rw-r--r--cmd/podman/common/create_opts.go2
-rw-r--r--libpod/networking_linux.go2
-rw-r--r--pkg/machine/ignition.go20
-rw-r--r--pkg/machine/wsl/machine.go4
-rw-r--r--pkg/specgen/volumes.go27
-rw-r--r--pkg/specgen/winpath.go59
-rw-r--r--pkg/specgen/winpath_linux.go24
-rw-r--r--pkg/specgen/winpath_unsupported.go20
-rw-r--r--pkg/specgen/winpath_windows.go30
-rw-r--r--pkg/specgenutil/specgenutil_test.go77
-rw-r--r--pkg/specgenutil/volumes.go21
11 files changed, 273 insertions, 13 deletions
diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go
index 7b7626040..16f193b03 100644
--- a/cmd/podman/common/create_opts.go
+++ b/cmd/podman/common/create_opts.go
@@ -347,7 +347,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c
cliOpts.Volume = append(cliOpts.Volume, vol)
// Extract the destination so we don't add duplicate mounts in
// the volumes phase.
- splitVol := strings.SplitN(vol, ":", 3)
+ splitVol := specgen.SplitVolumeString(vol)
switch len(splitVol) {
case 1:
volDestinations[vol] = true
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index c168b7eca..a312f5a0c 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -63,7 +63,7 @@ const (
// This is need because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports.
// For machine the HostIP must only be used by gvproxy and never in the VM.
func (c *Container) convertPortMappings() []types.PortMapping {
- if !machine.IsPodmanMachine() || len(c.config.PortMappings) == 0 {
+ if !machine.IsGvProxyBased() || len(c.config.PortMappings) == 0 {
return c.config.PortMappings
}
// if we run in a machine VM we have to ignore the host IP part
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index fe47437e3..35a9a30cb 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -304,6 +304,8 @@ ExecStart=/usr/bin/sleep infinity
containers := `[containers]
netns="bridge"
`
+ // Set deprecated machine_enabled until podman package on fcos is
+ // current enough to no longer require it
rootContainers := `[engine]
machine_enabled=true
`
@@ -392,7 +394,7 @@ Delegate=memory pids cpu io
FileEmbedded1: FileEmbedded1{Mode: intToPtr(0644)},
})
- // Set machine_enabled to true to indicate we're in a VM
+ // Set deprecated machine_enabled to true to indicate we're in a VM
files = append(files, File{
Node: Node{
Group: getNodeGrp("root"),
@@ -408,6 +410,22 @@ Delegate=memory pids cpu io
},
})
+ // Set machine marker file to indicate podman is in a qemu based machine
+ files = append(files, File{
+ Node: Node{
+ Group: getNodeGrp("root"),
+ Path: "/etc/containers/podman-machine",
+ User: getNodeUsr("root"),
+ },
+ FileEmbedded1: FileEmbedded1{
+ Append: nil,
+ Contents: Resource{
+ Source: encodeDataURLPtr("qemu\n"),
+ },
+ Mode: intToPtr(0644),
+ },
+ })
+
// Issue #11489: make sure that we can inject a custom registries.conf
// file on the system level to force a single search registry.
// The remote client does not yet support prompting for short-name
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
index dff7bfef9..38bd4e96d 100644
--- a/pkg/machine/wsl/machine.go
+++ b/pkg/machine/wsl/machine.go
@@ -448,6 +448,10 @@ func configureSystem(v *MachineVM, dist string) error {
return errors.Wrap(err, "could not create containers.conf for guest OS")
}
+ if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", "echo wsl > /etc/containers/podman-machine"); err != nil {
+ return errors.Wrap(err, "could not create podman-machine file for guest OS")
+ }
+
return nil
}
diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go
index eca8c0c35..b26666df3 100644
--- a/pkg/specgen/volumes.go
+++ b/pkg/specgen/volumes.go
@@ -65,7 +65,7 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
err error
)
- splitVol := strings.Split(vol, ":")
+ splitVol := SplitVolumeString(vol)
if len(splitVol) > 3 {
return nil, nil, nil, errors.Wrapf(volumeFormatErr, vol)
}
@@ -93,7 +93,7 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
}
}
- if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") {
+ if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") || isHostWinPath(src) {
// This is not a named volume
overlayFlag := false
chownFlag := false
@@ -152,3 +152,26 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
return mounts, volumes, overlayVolumes, nil
}
+
+// Splits a volume string, accounting for Win drive paths
+// when running as a WSL linux guest or Windows client
+func SplitVolumeString(vol string) []string {
+ parts := strings.Split(vol, ":")
+ if !shouldResolveWinPaths() {
+ return parts
+ }
+
+ // Skip extended marker prefix if present
+ n := 0
+ if strings.HasPrefix(vol, `\\?\`) {
+ n = 4
+ }
+
+ if hasWinDriveScheme(vol, n) {
+ first := parts[0] + ":" + parts[1]
+ parts = parts[1:]
+ parts[0] = first
+ }
+
+ return parts
+}
diff --git a/pkg/specgen/winpath.go b/pkg/specgen/winpath.go
new file mode 100644
index 000000000..f4249fab1
--- /dev/null
+++ b/pkg/specgen/winpath.go
@@ -0,0 +1,59 @@
+package specgen
+
+import (
+ "fmt"
+ "strings"
+ "unicode"
+
+ "github.com/pkg/errors"
+)
+
+func isHostWinPath(path string) bool {
+ return shouldResolveWinPaths() && strings.HasPrefix(path, `\\`) || hasWinDriveScheme(path, 0) || winPathExists(path)
+}
+
+func hasWinDriveScheme(path string, start int) bool {
+ if len(path) < start+2 || path[start+1] != ':' {
+ return false
+ }
+
+ drive := rune(path[start])
+ return drive < unicode.MaxASCII && unicode.IsLetter(drive)
+}
+
+// Converts a Windows path to a WSL guest path if local env is a WSL linux guest or this is a Windows client.
+func ConvertWinMountPath(path string) (string, error) {
+ if !shouldResolveWinPaths() {
+ return path, nil
+ }
+
+ if strings.HasPrefix(path, "/") {
+ // Handle /[driveletter]/windows/path form (e.g. c:\Users\bar == /c/Users/bar)
+ if len(path) > 2 && path[2] == '/' && shouldResolveUnixWinVariant(path) {
+ drive := unicode.ToLower(rune(path[1]))
+ if unicode.IsLetter(drive) && drive <= unicode.MaxASCII {
+ return fmt.Sprintf("/mnt/%c/%s", drive, path[3:]), nil
+ }
+ }
+
+ // unix path - pass through
+ return path, nil
+ }
+
+ // Convert remote win client relative paths to absolute
+ path = resolveRelativeOnWindows(path)
+
+ // Strip extended marker prefix if present
+ path = strings.TrimPrefix(path, `\\?\`)
+
+ // Drive installed via wsl --mount
+ if strings.HasPrefix(path, `\\.\`) {
+ path = "/mnt/wsl/" + path[4:]
+ } else if len(path) > 1 && path[1] == ':' {
+ path = "/mnt/" + strings.ToLower(path[0:1]) + path[2:]
+ } else {
+ return path, errors.New("unsupported UNC path")
+ }
+
+ return strings.ReplaceAll(path, `\`, "/"), nil
+}
diff --git a/pkg/specgen/winpath_linux.go b/pkg/specgen/winpath_linux.go
new file mode 100644
index 000000000..f42ac7639
--- /dev/null
+++ b/pkg/specgen/winpath_linux.go
@@ -0,0 +1,24 @@
+package specgen
+
+import (
+ "os"
+
+ "github.com/containers/common/pkg/machine"
+)
+
+func shouldResolveWinPaths() bool {
+ return machine.MachineHostType() == "wsl"
+}
+
+func shouldResolveUnixWinVariant(path string) bool {
+ _, err := os.Stat(path)
+ return err != nil
+}
+
+func resolveRelativeOnWindows(path string) string {
+ return path
+}
+
+func winPathExists(path string) bool {
+ return false
+}
diff --git a/pkg/specgen/winpath_unsupported.go b/pkg/specgen/winpath_unsupported.go
new file mode 100644
index 000000000..4cd008fdd
--- /dev/null
+++ b/pkg/specgen/winpath_unsupported.go
@@ -0,0 +1,20 @@
+//go:build !linux && !windows
+// +build !linux,!windows
+
+package specgen
+
+func shouldResolveWinPaths() bool {
+ return false
+}
+
+func shouldResolveUnixWinVariant(path string) bool {
+ return false
+}
+
+func resolveRelativeOnWindows(path string) string {
+ return path
+}
+
+func winPathExists(path string) bool {
+ return false
+}
diff --git a/pkg/specgen/winpath_windows.go b/pkg/specgen/winpath_windows.go
new file mode 100644
index 000000000..c6aad314a
--- /dev/null
+++ b/pkg/specgen/winpath_windows.go
@@ -0,0 +1,30 @@
+package specgen
+
+import (
+ "github.com/sirupsen/logrus"
+ "os"
+ "path/filepath"
+)
+
+func shouldResolveUnixWinVariant(path string) bool {
+ return true
+}
+
+func shouldResolveWinPaths() bool {
+ return true
+}
+
+func resolveRelativeOnWindows(path string) string {
+ ret, err := filepath.Abs(path)
+ if err != nil {
+ logrus.Debugf("problem resolving possible relative path %q: %s", path, err.Error())
+ return path
+ }
+
+ return ret
+}
+
+func winPathExists(path string) bool {
+ _, err := os.Stat(path)
+ return err == nil
+}
diff --git a/pkg/specgenutil/specgenutil_test.go b/pkg/specgenutil/specgenutil_test.go
new file mode 100644
index 000000000..5867b0ae0
--- /dev/null
+++ b/pkg/specgenutil/specgenutil_test.go
@@ -0,0 +1,77 @@
+//go:build linux
+// +build linux
+
+package specgenutil
+
+import (
+ "testing"
+
+ "github.com/containers/common/pkg/machine"
+ "github.com/containers/podman/v4/pkg/domain/entities"
+ "github.com/containers/podman/v4/pkg/specgen"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestWinPath(t *testing.T) {
+ const (
+ fail = false
+ pass = true
+ )
+ tests := []struct {
+ vol string
+ source string
+ dest string
+ isN bool
+ outcome bool
+ mach string
+ }{
+ {`C:\Foo:/blah`, "/mnt/c/Foo", "/blah", false, pass, "wsl"},
+ {`C:\Foo:/blah`, "/mnt/c/Foo", "/blah", false, fail, ""},
+ {`\\?\C:\Foo:/blah`, "/mnt/c/Foo", "/blah", false, pass, "wsl"},
+ {`/c/bar:/blah`, "/mnt/c/bar", "/blah", false, pass, "wsl"},
+ {`/c/bar:/blah`, "/c/bar", "/blah", false, pass, ""},
+ {`/test/this:/blah`, "/test/this", "/blah", false, pass, "wsl"},
+ {`c:/bar/something:/other`, "/mnt/c/bar/something", "/other", false, pass, "wsl"},
+ {`c:/foo:ro`, "c", "/foo", true, pass, ""},
+ {`\\computer\loc:/dest`, "", "", false, fail, "wsl"},
+ {`\\.\drive\loc:/target`, "/mnt/wsl/drive/loc", "/target", false, pass, "wsl"},
+ }
+
+ f := func(vol string, mach string) (*specgen.SpecGenerator, error) {
+ machine := machine.GetMachineMarker()
+ oldEnable, oldType := machine.Enabled, machine.Type
+ machine.Enabled, machine.Type = len(mach) > 0, mach
+ sg := specgen.NewSpecGenerator("nothing", false)
+ err := FillOutSpecGen(sg, &entities.ContainerCreateOptions{
+ ImageVolume: "ignore",
+ Volume: []string{vol}}, []string{},
+ )
+ machine.Enabled, machine.Type = oldEnable, oldType
+ return sg, err
+ }
+
+ for _, test := range tests {
+ msg := "Checking: " + test.vol
+ sg, err := f(test.vol, test.mach)
+ if test.outcome == fail {
+ assert.NotNil(t, err, msg)
+ continue
+ }
+ if !assert.Nil(t, err, msg) {
+ continue
+ }
+ if test.isN {
+ if !assert.Equal(t, 1, len(sg.Volumes), msg) {
+ continue
+ }
+ assert.Equal(t, test.source, sg.Volumes[0].Name, msg)
+ assert.Equal(t, test.dest, sg.Volumes[0].Dest, msg)
+ } else {
+ if !assert.Equal(t, 1, len(sg.Mounts), msg) {
+ continue
+ }
+ assert.Equal(t, test.source, sg.Mounts[0].Source, msg)
+ assert.Equal(t, test.dest, sg.Mounts[0].Destination, msg)
+ }
+ }
+}
diff --git a/pkg/specgenutil/volumes.go b/pkg/specgenutil/volumes.go
index 95ce420f8..50d745380 100644
--- a/pkg/specgenutil/volumes.go
+++ b/pkg/specgenutil/volumes.go
@@ -3,7 +3,7 @@ package specgenutil
import (
"encoding/csv"
"fmt"
- "path/filepath"
+ "path"
"strings"
"github.com/containers/common/pkg/parse"
@@ -123,7 +123,7 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo
finalMounts := make([]spec.Mount, 0, len(unifiedMounts))
for _, mount := range unifiedMounts {
if mount.Type == define.TypeBind {
- absSrc, err := filepath.Abs(mount.Source)
+ absSrc, err := specgen.ConvertWinMountPath(mount.Source)
if err != nil {
return nil, nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
}
@@ -334,7 +334,7 @@ func getBindMount(args []string) (spec.Mount, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return newMount, err
}
- newMount.Destination = filepath.Clean(kv[1])
+ newMount.Destination = unixPathClean(kv[1])
setDest = true
case "relabel":
if setRelabel {
@@ -456,7 +456,7 @@ func getTmpfsMount(args []string) (spec.Mount, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return newMount, err
}
- newMount.Destination = filepath.Clean(kv[1])
+ newMount.Destination = unixPathClean(kv[1])
setDest = true
case "U", "chown":
if setOwnership {
@@ -507,7 +507,7 @@ func getDevptsMount(args []string) (spec.Mount, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return newMount, err
}
- newMount.Destination = filepath.Clean(kv[1])
+ newMount.Destination = unixPathClean(kv[1])
setDest = true
default:
return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
@@ -572,7 +572,7 @@ func getNamedVolume(args []string) (*specgen.NamedVolume, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return nil, err
}
- newVolume.Dest = filepath.Clean(kv[1])
+ newVolume.Dest = unixPathClean(kv[1])
setDest = true
case "U", "chown":
if setOwnership {
@@ -624,7 +624,7 @@ func getImageVolume(args []string) (*specgen.ImageVolume, error) {
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
return nil, err
}
- newVolume.Destination = filepath.Clean(kv[1])
+ newVolume.Destination = unixPathClean(kv[1])
case "rw", "readwrite":
switch kv[1] {
case "true":
@@ -670,7 +670,7 @@ func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) {
}
mount := spec.Mount{
- Destination: filepath.Clean(destPath),
+ Destination: unixPathClean(destPath),
Type: define.TypeTmpfs,
Options: options,
Source: define.TypeTmpfs,
@@ -700,3 +700,8 @@ func validChownFlag(flag string) (bool, error) {
return true, nil
}
+
+// Use path instead of filepath to preserve Unix style paths on Windows
+func unixPathClean(p string) string {
+ return path.Clean(p)
+}