
Details about CimFs project are discussed in #8346 Signed-off-by: Amit Barve <ambarve@microsoft.com>
290 lines
9.6 KiB
Go
290 lines
9.6 KiB
Go
//go:build windows
|
|
|
|
package cim
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Microsoft/go-winio"
|
|
"github.com/Microsoft/hcsshim/internal/log"
|
|
"github.com/Microsoft/hcsshim/internal/oc"
|
|
"github.com/Microsoft/hcsshim/internal/wclayer"
|
|
"github.com/Microsoft/hcsshim/osversion"
|
|
"github.com/Microsoft/hcsshim/pkg/cimfs"
|
|
"go.opencensus.io/trace"
|
|
)
|
|
|
|
// A CimLayerWriter implements the wclayer.LayerWriter interface to allow writing container
|
|
// image layers in the cim format.
|
|
// A cim layer consist of cim files (which are usually stored in the `cim-layers` directory and
|
|
// some other files which are stored in the directory of that layer (i.e the `path` directory).
|
|
type CimLayerWriter struct {
|
|
ctx context.Context
|
|
s *trace.Span
|
|
// path to the layer (i.e layer's directory) as provided by the caller.
|
|
// Even if a layer is stored as a cim in the cim directory, some files associated
|
|
// with a layer are still stored in this path.
|
|
path string
|
|
// parent layer paths
|
|
parentLayerPaths []string
|
|
// Handle to the layer cim - writes to the cim file
|
|
cimWriter *cimfs.CimFsWriter
|
|
// Handle to the writer for writing files in the local filesystem
|
|
stdFileWriter *stdFileWriter
|
|
// reference to currently active writer either cimWriter or stdFileWriter
|
|
activeWriter io.Writer
|
|
// denotes if this layer has the UtilityVM directory
|
|
hasUtilityVM bool
|
|
// some files are written outside the cim during initial import (via stdFileWriter) because we need to
|
|
// make some modifications to these files before writing them to the cim. The pendingOps slice
|
|
// maintains a list of such delayed modifications to the layer cim. These modifications are applied at
|
|
// the very end of layer import process.
|
|
pendingOps []pendingCimOp
|
|
}
|
|
|
|
type hive struct {
|
|
name string
|
|
base string
|
|
delta string
|
|
}
|
|
|
|
var (
|
|
hives = []hive{
|
|
{"SYSTEM", "SYSTEM_BASE", "SYSTEM_DELTA"},
|
|
{"SOFTWARE", "SOFTWARE_BASE", "SOFTWARE_DELTA"},
|
|
{"SAM", "SAM_BASE", "SAM_DELTA"},
|
|
{"SECURITY", "SECURITY_BASE", "SECURITY_DELTA"},
|
|
{"DEFAULT", "DEFAULTUSER_BASE", "DEFAULTUSER_DELTA"},
|
|
}
|
|
)
|
|
|
|
func isDeltaOrBaseHive(path string) bool {
|
|
for _, hv := range hives {
|
|
if strings.EqualFold(path, filepath.Join(wclayer.HivesPath, hv.delta)) ||
|
|
strings.EqualFold(path, filepath.Join(wclayer.RegFilesPath, hv.name)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// checks if this particular file should be written with a stdFileWriter instead of
|
|
// using the cimWriter.
|
|
func isStdFile(path string) bool {
|
|
return (isDeltaOrBaseHive(path) ||
|
|
path == filepath.Join(wclayer.UtilityVMPath, wclayer.RegFilesPath, "SYSTEM") ||
|
|
path == filepath.Join(wclayer.UtilityVMPath, wclayer.RegFilesPath, "SOFTWARE") ||
|
|
path == wclayer.BcdFilePath || path == wclayer.BootMgrFilePath)
|
|
}
|
|
|
|
// Add adds a file to the layer with given metadata.
|
|
func (cw *CimLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo, fileSize int64, securityDescriptor []byte, extendedAttributes []byte, reparseData []byte) error {
|
|
if name == wclayer.UtilityVMPath {
|
|
cw.hasUtilityVM = true
|
|
}
|
|
if isStdFile(name) {
|
|
// create a pending op for this file
|
|
cw.pendingOps = append(cw.pendingOps, &addOp{
|
|
pathInCim: name,
|
|
hostPath: filepath.Join(cw.path, name),
|
|
fileInfo: fileInfo,
|
|
securityDescriptor: securityDescriptor,
|
|
extendedAttributes: extendedAttributes,
|
|
reparseData: reparseData,
|
|
})
|
|
if err := cw.stdFileWriter.Add(name); err != nil {
|
|
return err
|
|
}
|
|
cw.activeWriter = cw.stdFileWriter
|
|
} else {
|
|
if err := cw.cimWriter.AddFile(name, fileInfo, fileSize, securityDescriptor, extendedAttributes, reparseData); err != nil {
|
|
return err
|
|
}
|
|
cw.activeWriter = cw.cimWriter
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddLink adds a hard link to the layer. The target must already have been added.
|
|
func (cw *CimLayerWriter) AddLink(name string, target string) error {
|
|
// set active write to nil so that we panic if layer tar is incorrectly formatted.
|
|
cw.activeWriter = nil
|
|
if isStdFile(target) {
|
|
// If this is a link to a std file it will have to be added later once the
|
|
// std file is written to the CIM. Create a pending op for this
|
|
cw.pendingOps = append(cw.pendingOps, &linkOp{
|
|
oldPath: target,
|
|
newPath: name,
|
|
})
|
|
return nil
|
|
} else if isStdFile(name) {
|
|
// None of the predefined std files are links. If they show up as links this is unexpected
|
|
// behavior. Error out.
|
|
return fmt.Errorf("unexpected link %s in layer", name)
|
|
} else {
|
|
return cw.cimWriter.AddLink(target, name)
|
|
}
|
|
}
|
|
|
|
// AddAlternateStream creates another alternate stream at the given
|
|
// path. Any writes made after this call will go to that stream.
|
|
func (cw *CimLayerWriter) AddAlternateStream(name string, size uint64) error {
|
|
if isStdFile(name) {
|
|
// As of now there is no known case of std file having multiple data streams.
|
|
// If such a file is encountered our assumptions are wrong. Error out.
|
|
return fmt.Errorf("unexpected alternate stream %s in layer", name)
|
|
}
|
|
|
|
if err := cw.cimWriter.CreateAlternateStream(name, size); err != nil {
|
|
return err
|
|
}
|
|
cw.activeWriter = cw.cimWriter
|
|
return nil
|
|
}
|
|
|
|
// Remove removes a file that was present in a parent layer from the layer.
|
|
func (cw *CimLayerWriter) Remove(name string) error {
|
|
// set active write to nil so that we panic if layer tar is incorrectly formatted.
|
|
cw.activeWriter = nil
|
|
return cw.cimWriter.Unlink(name)
|
|
}
|
|
|
|
// Write writes data to the current file. The data must be in the format of a Win32
|
|
// backup stream.
|
|
func (cw *CimLayerWriter) Write(b []byte) (int, error) {
|
|
return cw.activeWriter.Write(b)
|
|
}
|
|
|
|
// Close finishes the layer writing process and releases any resources.
|
|
func (cw *CimLayerWriter) Close(ctx context.Context) (retErr error) {
|
|
if err := cw.stdFileWriter.Close(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
// cimWriter must be closed even if there are errors.
|
|
defer func() {
|
|
if err := cw.cimWriter.Close(); retErr == nil {
|
|
retErr = err
|
|
}
|
|
}()
|
|
|
|
// Find out the osversion of this layer, both base & non-base layers can have UtilityVM layer.
|
|
processUtilityVM := false
|
|
if cw.hasUtilityVM {
|
|
uvmSoftwareHivePath := filepath.Join(cw.path, wclayer.UtilityVMPath, wclayer.RegFilesPath, "SOFTWARE")
|
|
osvStr, err := getOsBuildNumberFromRegistry(uvmSoftwareHivePath)
|
|
if err != nil {
|
|
return fmt.Errorf("read os version string from UtilityVM SOFTWARE hive: %w", err)
|
|
}
|
|
|
|
osv, err := strconv.ParseUint(osvStr, 10, 16)
|
|
if err != nil {
|
|
return fmt.Errorf("parse os version string (%s): %w", osvStr, err)
|
|
}
|
|
|
|
// write this version to a file for future reference by the shim process
|
|
if err = wclayer.WriteLayerUvmBuildFile(cw.path, uint16(osv)); err != nil {
|
|
return fmt.Errorf("write uvm build version: %w", err)
|
|
}
|
|
|
|
// CIMFS for hyperV isolated is only supported after 20348, processing UtilityVM layer on 2048
|
|
// & lower will cause failures since those images won't have CIMFS specific UVM files (mostly
|
|
// BCD entries required for CIMFS)
|
|
processUtilityVM = (osv > osversion.LTSC2022)
|
|
log.G(ctx).Debugf("import image os version %d, processing UtilityVM layer: %t\n", osv, processUtilityVM)
|
|
}
|
|
|
|
if len(cw.parentLayerPaths) == 0 {
|
|
if err := cw.processBaseLayer(ctx, processUtilityVM); err != nil {
|
|
return fmt.Errorf("process base layer: %w", err)
|
|
}
|
|
} else {
|
|
if err := cw.processNonBaseLayer(ctx, processUtilityVM); err != nil {
|
|
return fmt.Errorf("process non base layer: %w", err)
|
|
}
|
|
}
|
|
|
|
for _, op := range cw.pendingOps {
|
|
if err := op.apply(cw.cimWriter); err != nil {
|
|
return fmt.Errorf("apply pending operations: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func NewCimLayerWriter(ctx context.Context, path string, parentLayerPaths []string) (_ *CimLayerWriter, err error) {
|
|
if !cimfs.IsCimFSSupported() {
|
|
return nil, fmt.Errorf("CimFs not supported on this build")
|
|
}
|
|
|
|
ctx, span := trace.StartSpan(ctx, "hcsshim::NewCimLayerWriter")
|
|
defer func() {
|
|
if err != nil {
|
|
oc.SetSpanStatus(span, err)
|
|
span.End()
|
|
}
|
|
}()
|
|
span.AddAttributes(
|
|
trace.StringAttribute("path", path),
|
|
trace.StringAttribute("parentLayerPaths", strings.Join(parentLayerPaths, ", ")))
|
|
|
|
parentCim := ""
|
|
cimDirPath := GetCimDirFromLayer(path)
|
|
if _, err = os.Stat(cimDirPath); os.IsNotExist(err) {
|
|
// create cim directory
|
|
if err = os.Mkdir(cimDirPath, 0755); err != nil {
|
|
return nil, fmt.Errorf("failed while creating cim layers directory: %w", err)
|
|
}
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("unable to access cim layers directory: %w", err)
|
|
|
|
}
|
|
|
|
if len(parentLayerPaths) > 0 {
|
|
parentCim = GetCimNameFromLayer(parentLayerPaths[0])
|
|
}
|
|
|
|
cim, err := cimfs.Create(cimDirPath, parentCim, GetCimNameFromLayer(path))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error in creating a new cim: %w", err)
|
|
}
|
|
|
|
sfw, err := newStdFileWriter(path, parentLayerPaths)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error in creating new standard file writer: %w", err)
|
|
}
|
|
return &CimLayerWriter{
|
|
ctx: ctx,
|
|
s: span,
|
|
path: path,
|
|
parentLayerPaths: parentLayerPaths,
|
|
cimWriter: cim,
|
|
stdFileWriter: sfw,
|
|
}, nil
|
|
}
|
|
|
|
// DestroyCimLayer destroys a cim layer i.e it removes all the cimfs files for the given layer as well as
|
|
// all of the other files that are stored in the layer directory (at path `layerPath`).
|
|
// If this is not a cimfs layer (i.e a cim file for the given layer does not exist) then nothing is done.
|
|
func DestroyCimLayer(ctx context.Context, layerPath string) error {
|
|
cimPath := GetCimPathFromLayer(layerPath)
|
|
|
|
// verify that such a cim exists first, sometimes containerd tries to call
|
|
// this with the root snapshot directory as the layer path. We don't want to
|
|
// destroy everything inside the snapshots directory.
|
|
if _, err := os.Stat(cimPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
return cimfs.DestroyCim(ctx, cimPath)
|
|
}
|