Root.Path for a process-isolated WCOW container must be the Volume GUID

The actual divergence is due to differences in the snapshotter and
graphfilter mount behaviour on Windows, but the snapshotter behaviour is
better, so we deal with it here rather than changing the snapshotter
behaviour.

We're relying on the internals of containerd's Windows mount
implementation here. Unless this code flow is replaced, future work is
to move getBackingDeviceForContainerdMount into containerd's mount
implementation.

Signed-off-by: Paul "TBBle" Hampson <Paul.Hampson@Pobox.com>
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paul "TBBle" Hampson 2023-09-24 01:30:32 +09:00 committed by Paweł Gronowski
parent ec041193f9
commit e8f4bfb374
No known key found for this signature in database
GPG key ID: B85EFCFE26DEF92A

View file

@ -8,6 +8,7 @@ import (
"path/filepath"
"strings"
"github.com/Microsoft/hcsshim"
coci "github.com/containerd/containerd/oci"
"github.com/containerd/log"
containertypes "github.com/docker/docker/api/types/container"
@ -249,7 +250,24 @@ func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.S
return errors.New("createSpecWindowsFields: BaseFS of container " + c.ID + " is unexpectedly empty")
}
s.Root.Path = c.BaseFS // This is not set for Hyper-V containers
if daemon.UsesSnapshotter() {
// daemon.Mount() for the snapshotters actually mounts the filesystem to the host
// using containerd/mount.All and BaseFS is the directory where this is mounted.
// This is consistent with Linux-based graphdriver implementations.
// For the windowsfilter graphdriver, the underlying Get() call does not actually mount
// the filesystem to a path, and BaseFS is the Volume GUID of the prepared/activated
// filesystem.
// The spec for Root.Path for Windows specifies that for Process-isolated containers,
// it must be in the Volume GUID (\\?\\Volume{GUID} style), not a host-mounted directory.
backingDevicePath, err := getBackingDeviceForContainerdMount(c.BaseFS)
if err != nil {
return errors.Wrapf(err, "createSpecWindowsFields: Failed to get backing device of BaseFS of container %s", c.ID)
}
s.Root.Path = backingDevicePath
} else {
s.Root.Path = c.BaseFS // This is not set for Hyper-V containers
}
if !strings.HasSuffix(s.Root.Path, `\`) {
s.Root.Path = s.Root.Path + `\` // Ensure a correctly formatted volume GUID path \\?\Volume{GUID}\
}
@ -275,6 +293,48 @@ func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.S
return nil
}
// getBackingDeviceForContainerdMount extracts the backing device or directory mounted at mountPoint
// by containerd's mount.Mount implementation for Windows.
func getBackingDeviceForContainerdMount(mountPoint string) (string, error) {
// NOTE: This relies on details of the behaviour of containerd's mount implementation for Windows,
// and so is somewhat fragile.
// TODO: Upstream this into the mount package.
// The implementation would be the same, but it'll be better-encapsulated.
// See containerd/containerd/mount/mount_windows.go
// This is mostly just copied from mount.Unmount
const sourceStreamName = "containerd.io-source"
mountPoint = filepath.Clean(mountPoint)
adsFile := mountPoint + ":" + sourceStreamName
var layerPath string
if _, err := os.Lstat(adsFile); err == nil {
layerPathb, err := os.ReadFile(mountPoint + ":" + sourceStreamName)
if err != nil {
return "", fmt.Errorf("failed to retrieve layer source for mount %s: %w", mountPoint, err)
}
layerPath = string(layerPathb)
}
if layerPath == "" {
return "", fmt.Errorf("no layer source for mount %s", mountPoint)
}
home, layerID := filepath.Split(layerPath)
di := hcsshim.DriverInfo{
HomeDir: home,
}
backingDevice, err := hcsshim.GetLayerMountPath(di, layerID)
if err != nil {
return "", fmt.Errorf("failed to retrieve backing device for layer %s: %w", mountPoint, err)
}
return backingDevice, nil
}
var errInvalidCredentialSpecSecOpt = errdefs.InvalidParameter(fmt.Errorf("invalid credential spec security option - value must be prefixed by 'file://', 'registry://', or 'raw://' followed by a non-empty value"))
// setWindowsCredentialSpec sets the spec's `Windows.CredentialSpec`