volume: Fix file subpath mount over existing image file

When a volume subpath pointing to a single file was mounted onto a
target path that already existed as a file in the container image, the
container creation failed with "open /var/lib/docker/tmp/safe-mountXXX:
not a directory".

Skip the volume population step when the volume mount path is a file,
since directory content copying is not applicable to single-file mounts.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski
2026-05-06 11:28:10 +02:00
parent 50b02f40b9
commit d3adafd853
2 changed files with 20 additions and 2 deletions

View File

@@ -46,6 +46,7 @@ func TestRunMountVolumeSubdir(t *testing.T) {
{name: "subdir link", opts: mount.VolumeOptions{Subpath: "hack/good"}, cmd: []string{"ls", "/volume"}, expected: "hello.txt"},
{name: "subdir with copy data", opts: mount.VolumeOptions{Subpath: "bin"}, volumeTarget: "/bin", cmd: []string{"ls", "/bin/busybox"}, expected: "/bin/busybox", skipPlatform: "windows:copy not supported on Windows"},
{name: "file", opts: mount.VolumeOptions{Subpath: "bar.txt"}, cmd: []string{"cat", "/volume"}, expected: "foo", skipPlatform: "windows:file bind mounts not supported on Windows"},
{name: "file over existing target", opts: mount.VolumeOptions{Subpath: "file"}, volumeTarget: "/etc/localtime", cmd: []string{"cat", "/etc/localtime"}, expected: "from-volume", skipPlatform: "windows:file bind mounts not supported on Windows"},
{name: "relative with backtracks", opts: mount.VolumeOptions{Subpath: "../../../../../../etc/passwd"}, cmd: []string{"cat", "/volume"}, createErr: "subpath must be a relative path within the volume"},
{name: "not existing", opts: mount.VolumeOptions{Subpath: "not-existing-path"}, cmd: []string{"cat", "/volume"}, startErr: (&safepath.ErrNotAccessible{}).Error()},
@@ -131,6 +132,7 @@ func TestRunMountImage(t *testing.T) {
name string
opts mount.ImageOptions
cmd []string
target string
createErr string
startErr string
expected string
@@ -143,6 +145,7 @@ func TestRunMountImage(t *testing.T) {
{name: "subdir link", opts: mount.ImageOptions{Subpath: "hack/good"}, cmd: []string{"ls", "/image"}, expected: "hello"},
{name: "subdir link outside context", opts: mount.ImageOptions{Subpath: "hack/bad"}, cmd: []string{"ls", "/image"}, startErr: (&safepath.ErrEscapesBase{}).Error()},
{name: "file", opts: mount.ImageOptions{Subpath: "subdir/hello"}, cmd: []string{"cat", "/image"}, expected: "world"},
{name: "file_over_existing_target", opts: mount.ImageOptions{Subpath: "subdir/hello"}, cmd: []string{"cat", "/etc/localtime"}, target: "/etc/localtime", expected: "world"},
{name: "relative with backtracks", opts: mount.ImageOptions{Subpath: "../../../../../../etc/passwd"}, cmd: []string{"cat", "/image"}, createErr: "subpath must be a relative path within the volume"},
{name: "not existing", opts: mount.ImageOptions{Subpath: "not-existing-path"}, cmd: []string{"cat", "/image"}, startErr: (&safepath.ErrNotAccessible{}).Error()},
@@ -163,12 +166,17 @@ func TestRunMountImage(t *testing.T) {
Cmd: tc.cmd,
}
target := tc.target
if target == "" {
target = "/image"
}
hostCfg := containertypes.HostConfig{
Mounts: []mount.Mount{
{
Type: mount.TypeImage,
Source: testImage,
Target: "/image",
Target: target,
ImageOptions: &tc.opts,
},
},
@@ -239,6 +247,7 @@ func TestRunMountImage(t *testing.T) {
// .
// |-- bar.txt (file with "foo")
// |-- bin (directory)
// |-- file (file with "from-volume")
// |-- subdir (directory)
// | |-- hello.txt (file with "world")
// |-- hack (directory)
@@ -267,6 +276,7 @@ func setupTestVolume(t *testing.T, apiClient client.APIClient) string {
}
initCmd := "echo foo > /volume/bar.txt && " +
"echo from-volume > /volume/file && " +
"mkdir /volume/bin && " +
"mkdir /volume/subdir && " +
"echo world > /volume/subdir/hello.txt && " +