Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions cmd/containerd-shim-runhcs-v1/task_hcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -988,10 +988,11 @@ func isMountTypeSupported(hostPath, mountType string) bool {
hcsoci.MountTypeVirtualDisk, hcsoci.MountTypeExtensibleVirtualDisk:
return false
default:
// Ensure that host path is not sandbox://, hugepages://
// Ensure that host path is not sandbox://, hugepages://, \\.\pipe, uvm://
if strings.HasPrefix(hostPath, guestpath.SandboxMountPrefix) ||
strings.HasPrefix(hostPath, guestpath.HugePagesMountPrefix) ||
strings.HasPrefix(hostPath, guestpath.PipePrefix) {
strings.HasPrefix(hostPath, guestpath.PipePrefix) ||
strings.HasPrefix(hostPath, guestpath.UVMMountPrefix) {
return false
} else {
// hcsshim treats mountType == "" as a normal directory mount
Expand Down
3 changes: 3 additions & 0 deletions internal/guestpath/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const (
// BlockDevMountPrefix is mount prefix used in container spec to mark a
// block-device mount.
BlockDevMountPrefix = "blockdev://"
// UVMMountPrefix is mount prefix used in container spec to mark a UVM mount
// into container.
UVMMountPrefix = "uvm://"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UVM prefix seems very generic and not related to using pipes. We already have SandboxMountPrefix to convey the same. Could we have overloaded SandboxMountPrefix for namedpipe use case and used pipe regex and SandboxMountPrefix to determine that this is the case for sharing namedpipes across containers?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anmaxvl was there another use case for the UVM prefix?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, from my understanding Singularity also needs this for LCOW.

// PipePrefix is the mount prefix used in container spec to mark a named pipe
PipePrefix = `\\.\pipe`
// LCOWMountPathPrefixFmt is the path format in the LCOW UVM where
Expand Down
10 changes: 8 additions & 2 deletions internal/hcsoci/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,15 @@ type createOptionsInternal struct {
actualID string // Identifier for the container
actualOwner string // Owner for the container
actualNetworkNamespace string
ccgState *hcsschema.ContainerCredentialGuardState // Container Credential Guard information to be attached to HCS container document
// ccgState is Container Credential Guard information to be attached to HCS container document
ccgState *hcsschema.ContainerCredentialGuardState

windowsAdditionalMounts []hcsschema.MappedDirectory // Holds additional mounts based on added devices (such as SCSI). Only used for Windows v2 schema containers.
// windowsAdditionalMounts holds additional mounts based on added devices (such as SCSI).
// Only used for Windows v2 schema containers.
windowsAdditionalMounts []hcsschema.MappedDirectory

// namedPipeMounts holds named pipe mount information.
namedPipeMounts []uvm.NamedPipe

mountedWCOWLayers *layers.MountedWCOWLayers
}
Expand Down
79 changes: 41 additions & 38 deletions internal/hcsoci/hcsdoc_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,50 +42,53 @@ type mountsConfig struct {

func createMountsConfig(ctx context.Context, coi *createOptionsInternal) (*mountsConfig, error) {
// Add the mounts as mapped directories or mapped pipes
// TODO: Mapped pipes to add in v2 schema.
var config mountsConfig
for _, mount := range coi.Spec.Mounts {
// mapped pipes will be added outside the loop
if uvm.IsPipe(mount.Source) {
src, dst := uvm.GetContainerPipeMapping(coi.HostingSystem, mount)
config.mpsv1 = append(config.mpsv1, schema1.MappedPipe{HostPath: src, ContainerPipeName: dst})
config.mpsv2 = append(config.mpsv2, hcsschema.MappedPipe{HostPath: src, ContainerPipeName: dst})
} else {
readOnly := false
for _, o := range mount.Options {
if strings.ToLower(o) == "ro" {
readOnly = true
}
continue
}

readOnly := false
for _, o := range mount.Options {
if strings.ToLower(o) == "ro" {
readOnly = true
}
mdv1 := schema1.MappedDir{HostPath: mount.Source, ContainerPath: mount.Destination, ReadOnly: readOnly}
mdv2 := hcsschema.MappedDirectory{ContainerPath: mount.Destination, ReadOnly: readOnly}
if coi.HostingSystem == nil {
// HCS has a bug where it does not correctly resolve file (not dir) paths
// if the path includes a symlink. Therefore, we resolve the path here before
// passing it in. The issue does not occur with VSMB, so don't need to worry
// about the isolated case.
src, err := fs.ResolvePath(mount.Source)
if err != nil {
return nil, fmt.Errorf("failed to resolve path for mount source %q: %w", mount.Source, err)
}
mdv2.HostPath = src
} else if mount.Type == MountTypeVirtualDisk || mount.Type == MountTypePhysicalDisk || mount.Type == MountTypeExtensibleVirtualDisk {
// For v2 schema containers, any disk mounts will be part of coi.additionalMounts.
// For v1 schema containers, we don't even get here, since there is no HostingSystem.
continue
} else if strings.HasPrefix(mount.Source, guestpath.SandboxMountPrefix) {
// Convert to the path in the guest that was asked for.
mdv2.HostPath = convertToWCOWSandboxMountPath(mount.Source)
} else {
// vsmb mount
uvmPath, err := coi.HostingSystem.GetVSMBUvmPath(ctx, mount.Source, readOnly)
if err != nil {
return nil, err
}
mdv2.HostPath = uvmPath
}
mdv1 := schema1.MappedDir{HostPath: mount.Source, ContainerPath: mount.Destination, ReadOnly: readOnly}
mdv2 := hcsschema.MappedDirectory{ContainerPath: mount.Destination, ReadOnly: readOnly}
if coi.HostingSystem == nil {
// HCS has a bug where it does not correctly resolve file (not dir) paths
// if the path includes a symlink. Therefore, we resolve the path here before
// passing it in. The issue does not occur with VSMB, so don't need to worry
// about the isolated case.
src, err := fs.ResolvePath(mount.Source)
if err != nil {
return nil, fmt.Errorf("failed to resolve path for mount source %q: %w", mount.Source, err)
}
config.mdsv1 = append(config.mdsv1, mdv1)
config.mdsv2 = append(config.mdsv2, mdv2)
mdv2.HostPath = src
} else if mount.Type == MountTypeVirtualDisk || mount.Type == MountTypePhysicalDisk || mount.Type == MountTypeExtensibleVirtualDisk {
// For v2 schema containers, any disk mounts will be part of coi.windowsAdditionalMounts.
// For v1 schema containers, we don't even get here, since there is no HostingSystem.
continue
} else if strings.HasPrefix(mount.Source, guestpath.SandboxMountPrefix) {
// Convert to the path in the guest that was asked for.
mdv2.HostPath = convertToWCOWSandboxMountPath(mount.Source)
} else {
// vsmb mount
uvmPath, err := coi.HostingSystem.GetVSMBUvmPath(ctx, mount.Source, readOnly)
if err != nil {
return nil, err
}
mdv2.HostPath = uvmPath
}
config.mdsv1 = append(config.mdsv1, mdv1)
config.mdsv2 = append(config.mdsv2, mdv2)
}
// adding mapped pipes
for _, np := range coi.namedPipeMounts {
config.mpsv1 = append(config.mpsv1, schema1.MappedPipe{HostPath: np.HostPath, ContainerPipeName: np.ContainerPath})
config.mpsv2 = append(config.mpsv2, hcsschema.MappedPipe{HostPath: np.HostPath, ContainerPipeName: np.ContainerPath})
}
config.mdsv2 = append(config.mdsv2, coi.windowsAdditionalMounts...)
return &config, nil
Expand Down
203 changes: 107 additions & 96 deletions internal/hcsoci/resources_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,21 @@ func allocateWindowsResources(ctx context.Context, coi *createOptionsInternal, r
// request.
func setupMounts(ctx context.Context, coi *createOptionsInternal, r *resources.Resources) error {
// Validate each of the mounts. If this is a V2 Xenon, we have to add them as
// VSMB shares to the utility VM. For V1 Xenon and Argons, there's nothing for
// us to do as it's done by HCS.
// VSMB shares to the utility VM. For V1 Xenon and Argons, only process named pipe
// mounts.
for _, mount := range coi.Spec.Mounts {
if mount.Destination == "" || mount.Source == "" {
return fmt.Errorf("invalid OCI spec - a mount must have both source and a destination: %+v", mount)
}

// check if this is a named pipe for process isolated containers and add it to `namedPipeMounts`
if coi.HostingSystem == nil || !schemaversion.IsV21(coi.actualSchemaVersion) {
if np, ok := uvm.ParseNamedPipe(coi.HostingSystem, mount); ok {
coi.namedPipeMounts = append(coi.namedPipeMounts, np)
}
continue
}

switch mount.Type {
case "":
case MountTypePhysicalDisk:
Expand All @@ -122,105 +131,107 @@ func setupMounts(ctx context.Context, coi *createOptionsInternal, r *resources.R
return fmt.Errorf("invalid OCI spec - Type '%s' not supported", mount.Type)
}

if coi.HostingSystem != nil && schemaversion.IsV21(coi.actualSchemaVersion) {
readOnly := false
for _, o := range mount.Options {
if strings.ToLower(o) == "ro" {
readOnly = true
break
}
readOnly := false
for _, o := range mount.Options {
if strings.ToLower(o) == "ro" {
readOnly = true
break
}
l := log.G(ctx).WithField("mount", fmt.Sprintf("%+v", mount))
if mount.Type == MountTypePhysicalDisk || mount.Type == MountTypeVirtualDisk || mount.Type == MountTypeExtensibleVirtualDisk {
var (
scsiMount *scsi.Mount
err error
}
l := log.G(ctx).WithField("mount", fmt.Sprintf("%+v", mount))
if mount.Type == MountTypePhysicalDisk || mount.Type == MountTypeVirtualDisk || mount.Type == MountTypeExtensibleVirtualDisk {
var (
scsiMount *scsi.Mount
err error
)
switch mount.Type {
case MountTypePhysicalDisk:
l.Debug("hcsshim::allocateWindowsResources Hot-adding SCSI physical disk for OCI mount")
scsiMount, err = coi.HostingSystem.SCSIManager.AddPhysicalDisk(
ctx,
mount.Source,
readOnly,
coi.HostingSystem.ID(),
"",
&scsi.MountConfig{},
)
switch mount.Type {
case MountTypePhysicalDisk:
l.Debug("hcsshim::allocateWindowsResources Hot-adding SCSI physical disk for OCI mount")
scsiMount, err = coi.HostingSystem.SCSIManager.AddPhysicalDisk(
ctx,
mount.Source,
readOnly,
coi.HostingSystem.ID(),
"",
&scsi.MountConfig{},
)
case MountTypeVirtualDisk:
l.Debug("hcsshim::allocateWindowsResources Hot-adding SCSI virtual disk for OCI mount")
scsiMount, err = coi.HostingSystem.SCSIManager.AddVirtualDisk(
ctx,
mount.Source,
readOnly,
coi.HostingSystem.ID(),
"",
&scsi.MountConfig{},
)
case MountTypeExtensibleVirtualDisk:
l.Debug("hcsshim::allocateWindowsResource Hot-adding ExtensibleVirtualDisk")
scsiMount, err = coi.HostingSystem.SCSIManager.AddExtensibleVirtualDisk(
ctx,
mount.Source,
readOnly,
"",
&scsi.MountConfig{},
)
}
if err != nil {
return fmt.Errorf("adding SCSI mount %+v: %w", mount, err)
}
r.Add(scsiMount)
// Compute guest mounts now, and store them, so they can be added to the container doc later.
// We do this now because we have access to the guest path through the returned mount object.
coi.windowsAdditionalMounts = append(coi.windowsAdditionalMounts, hcsschema.MappedDirectory{
HostPath: scsiMount.GuestPath(),
ContainerPath: mount.Destination,
ReadOnly: readOnly,
})
} else if strings.HasPrefix(mount.Source, guestpath.SandboxMountPrefix) {
// Mounts that map to a path in the UVM are specified with a 'sandbox://' prefix.
//
// Example: sandbox:///a/dirInUvm destination:C:\\dirInContainer.
//
// so first convert to a path in the sandboxmounts path itself.
sandboxPath := convertToWCOWSandboxMountPath(mount.Source)

// Now we need to exec a process in the vm that will make these directories as theres
// no functionality in the Windows gcs to create an arbitrary directory.
//
// Create the directory, but also run dir afterwards regardless of if mkdir succeeded to handle the case where the directory already exists
// e.g. from a previous container specifying the same mount (and thus creating the same directory).
b := &bytes.Buffer{}
stderr, err := cmd.CreatePipeAndListen(b, false)
if err != nil {
return err
}
req := &cmd.CmdProcessRequest{
Args: []string{"cmd", "/c", "mkdir", sandboxPath, "&", "dir", sandboxPath},
Stderr: stderr,
}
exitCode, err := cmd.ExecInUvm(ctx, coi.HostingSystem, req)
case MountTypeVirtualDisk:
l.Debug("hcsshim::allocateWindowsResources Hot-adding SCSI virtual disk for OCI mount")
scsiMount, err = coi.HostingSystem.SCSIManager.AddVirtualDisk(
ctx,
mount.Source,
readOnly,
coi.HostingSystem.ID(),
"",
&scsi.MountConfig{},
)
case MountTypeExtensibleVirtualDisk:
l.Debug("hcsshim::allocateWindowsResource Hot-adding ExtensibleVirtualDisk")
scsiMount, err = coi.HostingSystem.SCSIManager.AddExtensibleVirtualDisk(
ctx,
mount.Source,
readOnly,
"",
&scsi.MountConfig{},
)
}
if err != nil {
return fmt.Errorf("adding SCSI mount %+v: %w", mount, err)
}
r.Add(scsiMount)
// Compute guest mounts now, and store them, so they can be added to the container doc later.
// We do this now because we have access to the guest path through the returned mount object.
coi.windowsAdditionalMounts = append(coi.windowsAdditionalMounts, hcsschema.MappedDirectory{
HostPath: scsiMount.GuestPath(),
ContainerPath: mount.Destination,
ReadOnly: readOnly,
})
} else if strings.HasPrefix(mount.Source, guestpath.SandboxMountPrefix) {
// Mounts that map to a path in the UVM are specified with a 'sandbox://' prefix.
//
// Example:
// - sandbox:///a/dirInUvm destination:C:\\dirInContainer.
//
// so first convert to a path in the sandboxmounts path itself.
sandboxPath := convertToWCOWSandboxMountPath(mount.Source)

// Now we need to exec a process in the vm that will make these directories as theres
// no functionality in the Windows gcs to create an arbitrary directory.
//
// Create the directory, but also run dir afterwards regardless of if mkdir succeeded to handle the case where the directory already exists
// e.g. from a previous container specifying the same mount (and thus creating the same directory).
b := &bytes.Buffer{}
stderr, err := cmd.CreatePipeAndListen(b, false)
if err != nil {
return err
}
req := &cmd.CmdProcessRequest{
Args: []string{"cmd", "/c", "mkdir", sandboxPath, "&", "dir", sandboxPath},
Stderr: stderr,
}
exitCode, err := cmd.ExecInUvm(ctx, coi.HostingSystem, req)
if err != nil {
return errors.Wrapf(err, "failed to create sandbox mount directory in utility VM with exit code %d %q", exitCode, b.String())
}
} else if np, ok := uvm.ParseNamedPipe(coi.HostingSystem, mount); ok {
if !np.UVMPipe {
pipe, err := coi.HostingSystem.AddPipe(ctx, mount.Source)
if err != nil {
return errors.Wrapf(err, "failed to create sandbox mount directory in utility VM with exit code %d %q", exitCode, b.String())
}
} else {
if uvm.IsPipe(mount.Source) {
pipe, err := coi.HostingSystem.AddPipe(ctx, mount.Source)
if err != nil {
return errors.Wrap(err, "failed to add named pipe to UVM")
}
r.Add(pipe)
} else {
l.Debug("hcsshim::allocateWindowsResources Hot-adding VSMB share for OCI mount")
options := coi.HostingSystem.DefaultVSMBOptions(readOnly)
share, err := coi.HostingSystem.AddVSMB(ctx, mount.Source, options)
if err != nil {
return errors.Wrapf(err, "failed to add VSMB share to utility VM for mount %+v", mount)
}
r.Add(share)
return errors.Wrap(err, "failed to add named pipe to UVM")
}
r.Add(pipe)
}
coi.namedPipeMounts = append(coi.namedPipeMounts, np)
} else if strings.HasPrefix(mount.Source, guestpath.UVMMountPrefix) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would this case arise? Is this based on some incorrect user input?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's the intent. since named pipes are checked first, any other uvm:// prefixed mount will trigger this.

return fmt.Errorf("unsupported UVM mount source %s", mount.Source)
} else {
l.Debug("hcsshim::allocateWindowsResources Hot-adding VSMB share for OCI mount")
options := coi.HostingSystem.DefaultVSMBOptions(readOnly)
share, err := coi.HostingSystem.AddVSMB(ctx, mount.Source, options)
if err != nil {
return errors.Wrapf(err, "failed to add VSMB share to utility VM for mount %+v", mount)
}
r.Add(share)
}
}

Expand Down
Loading
Loading