231 lines
9.0 KiB
Go
231 lines
9.0 KiB
Go
//go:build windows
|
|
|
|
package cim
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/Microsoft/go-winio"
|
|
"github.com/Microsoft/go-winio/vhd"
|
|
"github.com/Microsoft/hcsshim/computestorage"
|
|
"github.com/Microsoft/hcsshim/internal/memory"
|
|
"github.com/Microsoft/hcsshim/internal/security"
|
|
"github.com/Microsoft/hcsshim/internal/vhdx"
|
|
"github.com/Microsoft/hcsshim/internal/wclayer"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
const defaultVHDXBlockSizeInMB = 1
|
|
|
|
// processUtilityVMLayer is similar to createContainerBaseLayerVHDs but along with the scratch creation it
|
|
// also does some BCD modifications to allow the UVM to boot from the CIM. It expects that the UVM BCD file is
|
|
// present at layerPath/`wclayer.BcdFilePath` and a UVM SYSTEM hive is present at
|
|
// layerPath/UtilityVM/`wclayer.RegFilesPath`/SYSTEM. The scratch VHDs are created under the `layerPath`
|
|
// directory.
|
|
func processUtilityVMLayer(ctx context.Context, layerPath string) error {
|
|
// func createUtilityVMLayerVHDs(ctx context.Context, layerPath string) error {
|
|
baseVhdPath := filepath.Join(layerPath, wclayer.UtilityVMPath, wclayer.UtilityVMBaseVhd)
|
|
diffVhdPath := filepath.Join(layerPath, wclayer.UtilityVMPath, wclayer.UtilityVMScratchVhd)
|
|
defaultVhdSize := uint64(10)
|
|
|
|
// Just create the vhdx for utilityVM layer, no need to format it.
|
|
createParams := &vhd.CreateVirtualDiskParameters{
|
|
Version: 2,
|
|
Version2: vhd.CreateVersion2{
|
|
MaximumSize: defaultVhdSize * memory.GiB,
|
|
BlockSizeInBytes: defaultVHDXBlockSizeInMB * memory.MiB,
|
|
},
|
|
}
|
|
|
|
handle, err := vhd.CreateVirtualDisk(baseVhdPath, vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone, createParams)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create vhdx: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
os.RemoveAll(baseVhdPath)
|
|
os.RemoveAll(diffVhdPath)
|
|
}
|
|
}()
|
|
|
|
err = computestorage.FormatWritableLayerVhd(ctx, windows.Handle(handle))
|
|
closeErr := syscall.CloseHandle(handle)
|
|
if err != nil {
|
|
return err
|
|
} else if closeErr != nil {
|
|
return fmt.Errorf("failed to close vhdx handle: %w", closeErr)
|
|
}
|
|
|
|
partitionInfo, err := vhdx.GetScratchVhdPartitionInfo(ctx, baseVhdPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get base vhd layout info: %w", err)
|
|
}
|
|
// relativeCimPath needs to be the cim path relative to the snapshots directory. The snapshots
|
|
// directory is shared inside the UVM over VSMB, so during the UVM boot this relative path will be
|
|
// used to find the cim file under that VSMB share.
|
|
relativeCimPath := filepath.Join(filepath.Base(GetCimDirFromLayer(layerPath)), GetCimNameFromLayer(layerPath))
|
|
bcdPath := filepath.Join(layerPath, bcdFilePath)
|
|
if err = updateBcdStoreForBoot(bcdPath, relativeCimPath, partitionInfo.DiskID, partitionInfo.PartitionID); err != nil {
|
|
return fmt.Errorf("failed to update BCD: %w", err)
|
|
}
|
|
|
|
if err := enableCimBoot(filepath.Join(layerPath, wclayer.UtilityVMPath, wclayer.RegFilesPath, "SYSTEM")); err != nil {
|
|
return fmt.Errorf("failed to setup cim image for uvm boot: %w", err)
|
|
}
|
|
|
|
// Note: diff vhd creation and granting of vm group access must be done AFTER
|
|
// getting the partition info of the base VHD. Otherwise it causes the vhd parent
|
|
// chain to get corrupted.
|
|
// TODO(ambarve): figure out why this happens so that bcd update can be moved to a separate function
|
|
|
|
// Create the differencing disk that will be what's copied for the final rw layer
|
|
// for a container.
|
|
if err = vhd.CreateDiffVhd(diffVhdPath, baseVhdPath, defaultVHDXBlockSizeInMB); err != nil {
|
|
return fmt.Errorf("failed to create differencing disk: %w", err)
|
|
}
|
|
|
|
if err := security.GrantVmGroupAccess(baseVhdPath); err != nil {
|
|
return fmt.Errorf("failed to grant vm group access to %s: %w", baseVhdPath, err)
|
|
}
|
|
if err := security.GrantVmGroupAccess(diffVhdPath); err != nil {
|
|
return fmt.Errorf("failed to grant vm group access to %s: %w", diffVhdPath, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// processBaseLayerHives make the base layer specific modifications on the hives and emits equivalent the
|
|
// pendingCimOps that should be applied on the CIM. In base layer we need to create hard links from registry
|
|
// hives under Files/Windows/Sysetm32/config into Hives/*_BASE. This function creates these links outside so
|
|
// that the registry hives under Hives/ are available during children layers import. Then we write these hive
|
|
// files inside the cim and create links inside the cim.
|
|
func processBaseLayerHives(layerPath string) ([]pendingCimOp, error) {
|
|
pendingOps := []pendingCimOp{}
|
|
|
|
// make hives directory both outside and in the cim
|
|
if err := os.Mkdir(filepath.Join(layerPath, wclayer.HivesPath), 0755); err != nil {
|
|
return pendingOps, fmt.Errorf("hives directory creation: %w", err)
|
|
}
|
|
|
|
hivesDirInfo := &winio.FileBasicInfo{
|
|
CreationTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
LastAccessTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
LastWriteTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
ChangeTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
FileAttributes: windows.FILE_ATTRIBUTE_DIRECTORY,
|
|
}
|
|
pendingOps = append(pendingOps, &addOp{
|
|
pathInCim: wclayer.HivesPath,
|
|
hostPath: filepath.Join(layerPath, wclayer.HivesPath),
|
|
fileInfo: hivesDirInfo,
|
|
})
|
|
|
|
// add hard links from base hive files.
|
|
for _, hv := range hives {
|
|
oldHivePathRelative := filepath.Join(wclayer.RegFilesPath, hv.name)
|
|
newHivePathRelative := filepath.Join(wclayer.HivesPath, hv.base)
|
|
if err := os.Link(filepath.Join(layerPath, oldHivePathRelative), filepath.Join(layerPath, newHivePathRelative)); err != nil {
|
|
return pendingOps, fmt.Errorf("hive link creation: %w", err)
|
|
}
|
|
|
|
pendingOps = append(pendingOps, &linkOp{
|
|
oldPath: oldHivePathRelative,
|
|
newPath: newHivePathRelative,
|
|
})
|
|
}
|
|
return pendingOps, nil
|
|
}
|
|
|
|
// processLayoutFile creates a file named "layout" in the root of the base layer. This allows certain
|
|
// container startup related functions to understand that the hives are a part of the container rootfs.
|
|
func processLayoutFile(layerPath string) ([]pendingCimOp, error) {
|
|
fileContents := "vhd-with-hives\n"
|
|
if err := os.WriteFile(filepath.Join(layerPath, "layout"), []byte(fileContents), 0755); err != nil {
|
|
return []pendingCimOp{}, fmt.Errorf("write layout file: %w", err)
|
|
}
|
|
|
|
layoutFileInfo := &winio.FileBasicInfo{
|
|
CreationTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
LastAccessTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
LastWriteTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
ChangeTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
FileAttributes: windows.FILE_ATTRIBUTE_NORMAL,
|
|
}
|
|
|
|
op := &addOp{
|
|
pathInCim: "layout",
|
|
hostPath: filepath.Join(layerPath, "layout"),
|
|
fileInfo: layoutFileInfo,
|
|
}
|
|
return []pendingCimOp{op}, nil
|
|
}
|
|
|
|
// Some of the layer files that are generated during the processBaseLayer call must be added back
|
|
// inside the cim, some registry file links must be updated. This function takes care of all those
|
|
// steps. This function opens the cim file for writing and updates it.
|
|
func (cw *CimLayerWriter) processBaseLayer(ctx context.Context, processUtilityVM bool) (err error) {
|
|
if processUtilityVM {
|
|
if err = processUtilityVMLayer(ctx, cw.path); err != nil {
|
|
return fmt.Errorf("process utilityVM layer: %w", err)
|
|
}
|
|
}
|
|
|
|
ops, err := processBaseLayerHives(cw.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cw.pendingOps = append(cw.pendingOps, ops...)
|
|
|
|
ops, err = processLayoutFile(cw.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cw.pendingOps = append(cw.pendingOps, ops...)
|
|
return nil
|
|
}
|
|
|
|
// processNonBaseLayer takes care of the processing required for a non base layer. As of now
|
|
// the only processing required for non base layer is to merge the delta registry hives of the
|
|
// non-base layer with it's parent layer.
|
|
func (cw *CimLayerWriter) processNonBaseLayer(ctx context.Context, processUtilityVM bool) (err error) {
|
|
for _, hv := range hives {
|
|
baseHive := filepath.Join(wclayer.HivesPath, hv.base)
|
|
deltaHive := filepath.Join(wclayer.HivesPath, hv.delta)
|
|
_, err := os.Stat(filepath.Join(cw.path, deltaHive))
|
|
// merge with parent layer if delta exists.
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("stat delta hive %s: %w", filepath.Join(cw.path, deltaHive), err)
|
|
} else if err == nil {
|
|
// merge base hive of parent layer with the delta hive of this layer and write it as
|
|
// the base hive of this layer.
|
|
err = mergeHive(filepath.Join(cw.parentLayerPaths[0], baseHive), filepath.Join(cw.path, deltaHive), filepath.Join(cw.path, baseHive))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// the newly created merged file must be added to the cim
|
|
cw.pendingOps = append(cw.pendingOps, &addOp{
|
|
pathInCim: baseHive,
|
|
hostPath: filepath.Join(cw.path, baseHive),
|
|
fileInfo: &winio.FileBasicInfo{
|
|
CreationTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
LastAccessTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
LastWriteTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
ChangeTime: windows.NsecToFiletime(time.Now().UnixNano()),
|
|
FileAttributes: windows.FILE_ATTRIBUTE_NORMAL,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
if processUtilityVM {
|
|
return processUtilityVMLayer(ctx, cw.path)
|
|
}
|
|
return nil
|
|
}
|