fix: mounting issue with single character volume on windows

fixes https://github.com/containers/podman/issues/25218

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>
This commit is contained in:
axel7083
2025-03-26 17:57:52 +01:00
parent cb2466004f
commit ea5ed2a875
5 changed files with 204 additions and 1 deletions

View File

@ -102,6 +102,32 @@ var _ = Describe("run basic podman commands", func() {
Expect(build).To(Exit(0)) Expect(build).To(Exit(0))
}) })
It("Single character volume mount", func() {
// Get a tmp directory
tDir, err := filepath.Abs(GinkgoT().TempDir())
Expect(err).ToNot(HaveOccurred())
name := randomString()
i := new(initMachine).withImage(mb.imagePath).withNow()
// All other platforms have an implicit mount for the temp area
if isVmtype(define.QemuVirt) {
i.withVolume(tDir)
}
session, err := mb.setName(name).setCmd(i).run()
Expect(err).ToNot(HaveOccurred())
Expect(session).To(Exit(0))
bm := basicMachine{}
volumeCreate, err := mb.setCmd(bm.withPodmanCommand([]string{"volume", "create", "a"})).run()
Expect(err).ToNot(HaveOccurred())
Expect(volumeCreate).To(Exit(0))
run, err := mb.setCmd(bm.withPodmanCommand([]string{"run", "-v", "a:/test:Z", "quay.io/libpod/alpine_nginx", "true"})).run()
Expect(err).ToNot(HaveOccurred())
Expect(run).To(Exit(0))
})
It("Volume should be virtiofs", func() { It("Volume should be virtiofs", func() {
// In theory this could run on MacOS too, but we know virtiofs works for that now, // In theory this could run on MacOS too, but we know virtiofs works for that now,
// this is just testing linux // this is just testing linux

View File

@ -225,8 +225,9 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
return mounts, volumes, overlayVolumes, nil return mounts, volumes, overlayVolumes, nil
} }
// Splits a volume string, accounting for Win drive paths // SplitVolumeString Splits a volume string, accounting for Win drive paths
// when running as a WSL linux guest or Windows client // when running as a WSL linux guest or Windows client
// Format: [[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]
func SplitVolumeString(vol string) []string { func SplitVolumeString(vol string) []string {
parts := strings.Split(vol, ":") parts := strings.Split(vol, ":")
if !shouldResolveWinPaths() { if !shouldResolveWinPaths() {
@ -239,6 +240,20 @@ func SplitVolumeString(vol string) []string {
n = 4 n = 4
} }
// Determine if the last part is an absolute path (if true, it means we don't have any options such as ro, rw etc.)
lastPartIsPath := strings.HasPrefix(parts[len(parts)-1], "/")
// Case: Volume or relative host path (e.g., "vol-name:/container" or "./hello:/container")
if lastPartIsPath && len(parts) == 2 {
return parts
}
// Case: Volume or relative host path with options (e.g., "vol-name:/container:ro" or "./hello:/container:ro")
if !lastPartIsPath && len(parts) == 3 {
return parts
}
// Case: Windows absolute path (e.g., "C:/Users:/mnt:ro")
if hasWinDriveScheme(vol, n) { if hasWinDriveScheme(vol, n) {
first := parts[0] + ":" + parts[1] first := parts[0] + ":" + parts[1]
parts = parts[1:] parts = parts[1:]

View File

@ -0,0 +1,72 @@
package specgen
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSplitVolumeString(t *testing.T) {
tests := []struct {
name string
volume string
expect []string
}{
// relative host paths
{
name: "relative host path",
volume: "./hello:/container",
expect: []string{"./hello", "/container"},
},
{
name: "relative host path with options",
volume: "./hello:/container:ro",
expect: []string{"./hello", "/container", "ro"},
},
// absolute host path
{
name: "absolute host path",
volume: "/hello:/container",
expect: []string{"/hello", "/container"},
},
{
name: "absolute host path with option",
volume: "/hello:/container:ro",
expect: []string{"/hello", "/container", "ro"},
},
{
name: "absolute host path with option",
volume: "/hello:/container:ro",
expect: []string{"/hello", "/container", "ro"},
},
// volume source
{
name: "volume without option",
volume: "vol-name:/container",
expect: []string{"vol-name", "/container"},
},
{
name: "volume with option",
volume: "vol-name:/container:ro",
expect: []string{"vol-name", "/container", "ro"},
},
{
name: "single letter volume without option",
volume: "a:/container",
expect: []string{"a", "/container"},
},
{
name: "single letter volume with option",
volume: "a:/container:ro",
expect: []string{"a", "/container", "ro"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parts := SplitVolumeString(tt.volume)
assert.Equal(t, tt.expect, parts, tt.name)
})
}
}

View File

@ -0,0 +1,77 @@
package specgen
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSplitVolumeString(t *testing.T) {
tests := []struct {
name string
volume string
expect []string
}{
// relative host paths
{
name: "relative host path",
volume: "./hello:/container",
expect: []string{"./hello", "/container"},
},
{
name: "relative host path with options",
volume: "./hello:/container:ro",
expect: []string{"./hello", "/container", "ro"},
},
// absolute host path
{
name: "absolute host path",
volume: "C:\\hello:/container",
expect: []string{"C:\\hello", "/container"},
},
{
name: "absolute host path with option",
volume: "C:\\hello:/container:ro",
expect: []string{"C:\\hello", "/container", "ro"},
},
{
name: "absolute host path with option",
volume: "C:\\hello:/container:ro",
expect: []string{"C:\\hello", "/container", "ro"},
},
{
name: "absolute extended host path",
volume: `\\?\C:\hello:/container`,
expect: []string{`\\?\C:\hello`, "/container"},
},
// volume source
{
name: "volume without option",
volume: "vol-name:/container",
expect: []string{"vol-name", "/container"},
},
{
name: "volume with option",
volume: "vol-name:/container:ro",
expect: []string{"vol-name", "/container", "ro"},
},
{
name: "single letter volume without option",
volume: "a:/container",
expect: []string{"a", "/container"},
},
{
name: "single letter volume with option",
volume: "a:/container:ro",
expect: []string{"a", "/container", "ro"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parts := SplitVolumeString(tt.volume)
assert.Equal(t, tt.expect, parts, tt.name)
})
}
}

View File

@ -145,6 +145,19 @@ var _ = Describe("Podman run with volumes", func() {
Expect(session).To(ExitWithError(125, fmt.Sprintf("%s: duplicate mount destination", dest))) Expect(session).To(ExitWithError(125, fmt.Sprintf("%s: duplicate mount destination", dest)))
}) })
It("podman run with single character volume", func() {
// 1. create single character volume
session := podmanTest.Podman([]string{"volume", "create", "a"})
session.WaitWithDefaultTimeout()
volName := session.OutputToString()
Expect(session).Should(ExitCleanly())
// 2. create container with volume
session = podmanTest.Podman([]string{"run", "--volume", volName + ":/data", ALPINE, "sh", "-c", "echo hello world"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
})
It("podman run with conflict between image volume and user mount succeeds", func() { It("podman run with conflict between image volume and user mount succeeds", func() {
err = podmanTest.RestoreArtifact(REDIS_IMAGE) err = podmanTest.RestoreArtifact(REDIS_IMAGE)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())