package wclayer import ( "context" "fmt" "os" "path/filepath" "syscall" "github.com/Microsoft/hcsshim/internal/hcserror" "github.com/Microsoft/hcsshim/internal/longpath" "github.com/Microsoft/hcsshim/internal/oc" "github.com/Microsoft/hcsshim/internal/safefile" "github.com/Microsoft/hcsshim/internal/winapi" "github.com/pkg/errors" "go.opencensus.io/trace" "golang.org/x/sys/windows" ) var hiveNames = []string{"DEFAULT", "SAM", "SECURITY", "SOFTWARE", "SYSTEM"} // Ensure the given file exists as an ordinary file, and create a minimal hive file if not. func ensureHive(path string, root *os.File) (err error) { _, err = safefile.LstatRelative(path, root) if err != nil && !os.IsNotExist(err) { return fmt.Errorf("accessing %s: %w", path, err) } version := windows.RtlGetVersion() if version == nil { return fmt.Errorf("failed to get OS version") } var fullPath string fullPath, err = longpath.LongAbs(filepath.Join(root.Name(), path)) if err != nil { return fmt.Errorf("getting path: %w", err) } var key syscall.Handle err = winapi.ORCreateHive(&key) if err != nil { return fmt.Errorf("creating hive: %w", err) } defer func() { closeErr := winapi.ORCloseHive(key) if closeErr != nil && err == nil { err = fmt.Errorf("closing hive key: %w", closeErr) } }() err = winapi.ORSaveHive(key, fullPath, version.MajorVersion, version.MinorVersion) if err != nil { return fmt.Errorf("saving hive: %w", err) } return nil } func ensureBaseLayer(root *os.File) (hasUtilityVM bool, err error) { // The base layer registry hives will be copied from here const hiveSourcePath = "Files\\Windows\\System32\\config" if err = safefile.MkdirAllRelative(hiveSourcePath, root); err != nil { return } for _, hiveName := range hiveNames { hivePath := filepath.Join(hiveSourcePath, hiveName) if err = ensureHive(hivePath, root); err != nil { return } } stat, err := safefile.LstatRelative(utilityVMFilesPath, root) if os.IsNotExist(err) { return false, nil } if err != nil { return } if !stat.Mode().IsDir() { fullPath := filepath.Join(root.Name(), utilityVMFilesPath) return false, errors.Errorf("%s has unexpected file mode %s", fullPath, stat.Mode().String()) } const bcdRelativePath = "EFI\\Microsoft\\Boot\\BCD" // Just check that this exists as a regular file. If it exists but is not a valid registry hive, // ProcessUtilityVMImage will complain: // "The registry could not read in, or write out, or flush, one of the files that contain the system's image of the registry." bcdPath := filepath.Join(utilityVMFilesPath, bcdRelativePath) stat, err = safefile.LstatRelative(bcdPath, root) if err != nil { return false, errors.Wrapf(err, "UtilityVM must contain '%s'", bcdRelativePath) } if !stat.Mode().IsRegular() { fullPath := filepath.Join(root.Name(), bcdPath) return false, errors.Errorf("%s has unexpected file mode %s", fullPath, stat.Mode().String()) } return true, nil } func convertToBaseLayer(ctx context.Context, root *os.File) error { hasUtilityVM, err := ensureBaseLayer(root) if err != nil { return err } if err := ProcessBaseLayer(ctx, root.Name()); err != nil { return err } if !hasUtilityVM { return nil } err = safefile.EnsureNotReparsePointRelative(utilityVMPath, root) if err != nil { return err } utilityVMPath := filepath.Join(root.Name(), utilityVMPath) return ProcessUtilityVMImage(ctx, utilityVMPath) } // ConvertToBaseLayer processes a candidate base layer, i.e. a directory // containing the desired file content under Files/, and optionally the // desired file content for a UtilityVM under UtilityVM/Files/ func ConvertToBaseLayer(ctx context.Context, path string) (err error) { title := "hcsshim::ConvertToBaseLayer" ctx, span := trace.StartSpan(ctx, title) defer span.End() defer func() { oc.SetSpanStatus(span, err) }() span.AddAttributes(trace.StringAttribute("path", path)) root, err := safefile.OpenRoot(path) if err != nil { return hcserror.New(err, title+" - failed", "") } defer func() { if err2 := root.Close(); err == nil && err2 != nil { err = hcserror.New(err2, title+" - failed", "") } }() if err = convertToBaseLayer(ctx, root); err != nil { return hcserror.New(err, title+" - failed", "") } return nil }