From 58c65b81d660251c76f15af4f2a742972904a339 Mon Sep 17 00:00:00 2001 From: "Paul \"TBBle\" Hampson" Date: Thu, 3 Dec 2020 00:32:02 +1100 Subject: [PATCH 01/12] Simple baseLayerReader to export parentless layers This is the inverse of the baseLayerWriter: It walks Files/ and UtilityVM/Files/ (if present) and ignores the rest of the layer data, as it will be recreated when the layer is imported. Signed-off-by: Paul "TBBle" Hampson --- cmd/wclayer/export.go | 2 +- internal/wclayer/baselayerreader.go | 206 ++++++++++++++++++ .../{baselayer.go => baselayerwriter.go} | 0 internal/wclayer/exportlayer.go | 9 + 4 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 internal/wclayer/baselayerreader.go rename internal/wclayer/{baselayer.go => baselayerwriter.go} (100%) diff --git a/cmd/wclayer/export.go b/cmd/wclayer/export.go index d739e457de..f9cc08a309 100644 --- a/cmd/wclayer/export.go +++ b/cmd/wclayer/export.go @@ -40,7 +40,7 @@ var exportCommand = cli.Command{ return err } - layers, err := normalizeLayers(cliContext.StringSlice("layer"), true) + layers, err := normalizeLayers(cliContext.StringSlice("layer"), false) if err != nil { return err } diff --git a/internal/wclayer/baselayerreader.go b/internal/wclayer/baselayerreader.go new file mode 100644 index 0000000000..7a13c98949 --- /dev/null +++ b/internal/wclayer/baselayerreader.go @@ -0,0 +1,206 @@ +package wclayer + +import ( + "context" + "errors" + "io" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/Microsoft/go-winio" + "github.com/Microsoft/hcsshim/internal/longpath" + "github.com/Microsoft/hcsshim/internal/oc" + "go.opencensus.io/trace" +) + +type baseLayerReader struct { + ctx context.Context + s *trace.Span + root string + result chan *fileEntry + proceed chan bool + currentFile *os.File + backupReader *winio.BackupFileReader +} + +func newBaseLayerReader(ctx context.Context, root string, s *trace.Span) (r *baseLayerReader) { + r = &baseLayerReader{ + ctx: ctx, + s: s, + root: root, + result: make(chan *fileEntry), + proceed: make(chan bool), + } + go r.walk() + return r +} + +func (r *baseLayerReader) walkUntilCancelled() error { + root, err := longpath.LongAbs(r.root) + if err != nil { + return err + } + + r.root = root + + err = filepath.Walk(filepath.Join(r.root, filesPath), func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Indirect fix for https://github.com/moby/moby/issues/32838#issuecomment-343610048. + // Handle failure from what may be a golang bug in the conversion of + // UTF16 to UTF8 in files which are left in the recycle bin. Os.Lstat + // which is called by filepath.Walk will fail when a filename contains + // unicode characters. Skip the recycle bin regardless which is goodness. + if strings.EqualFold(path, filepath.Join(r.root, `Files\$Recycle.Bin`)) && info.IsDir() { + return filepath.SkipDir + } + + r.result <- &fileEntry{path, info, nil} + if !<-r.proceed { + return errorIterationCanceled + } + + return nil + }) + + if err == errorIterationCanceled { + return nil + } + + if err != nil { + return err + } + + utilityVMAbsPath := filepath.Join(r.root, utilityVMPath) + utilityVMFilesAbsPath := filepath.Join(r.root, utilityVMFilesPath) + + // Ignore a UtilityVM without Files, that's not _really_ a UtiltyVM + if _, err = os.Lstat(utilityVMFilesAbsPath); err != nil { + if os.IsNotExist(err) { + return io.EOF + } + return err + } + + err = filepath.Walk(utilityVMAbsPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if path != utilityVMAbsPath && path != utilityVMFilesAbsPath && !hasPathPrefix(path, utilityVMFilesAbsPath) { + if info.IsDir() { + return filepath.SkipDir + } + return nil + } + + r.result <- &fileEntry{path, info, nil} + if !<-r.proceed { + return errorIterationCanceled + } + + return nil + }) + + if err == errorIterationCanceled { + return nil + } + + if err != nil { + return err + } + + return io.EOF +} + +func (r *baseLayerReader) walk() { + defer close(r.result) + if !<-r.proceed { + return + } + + err := r.walkUntilCancelled() + if err != nil { + for { + r.result <- &fileEntry{err: err} + if !<-r.proceed { + return + } + } + } +} + +func (r *baseLayerReader) reset() { + if r.backupReader != nil { + r.backupReader.Close() + r.backupReader = nil + } + if r.currentFile != nil { + r.currentFile.Close() + r.currentFile = nil + } +} + +func (r *baseLayerReader) Next() (path string, size int64, fileInfo *winio.FileBasicInfo, err error) { + r.reset() + r.proceed <- true + fe := <-r.result + if fe == nil { + err = errors.New("BaseLayerReader closed") + return + } + if fe.err != nil { + err = fe.err + return + } + + path, err = filepath.Rel(r.root, fe.path) + if err != nil { + return + } + + f, err := openFileOrDir(fe.path, syscall.GENERIC_READ, syscall.OPEN_EXISTING) + if err != nil { + return + } + defer func() { + if f != nil { + f.Close() + } + }() + + fileInfo, err = winio.GetFileBasicInfo(f) + if err != nil { + return + } + + size = fe.fi.Size() + r.backupReader = winio.NewBackupFileReader(f, true) + + r.currentFile = f + f = nil + return +} + +func (r *baseLayerReader) Read(b []byte) (int, error) { + if r.backupReader == nil { + if r.currentFile == nil { + return 0, io.EOF + } + return r.currentFile.Read(b) + } + return r.backupReader.Read(b) +} + +func (r *baseLayerReader) Close() (err error) { + defer r.s.End() + defer func() { oc.SetSpanStatus(r.s, err) }() + r.proceed <- false + <-r.result + r.reset() + return nil +} diff --git a/internal/wclayer/baselayer.go b/internal/wclayer/baselayerwriter.go similarity index 100% rename from internal/wclayer/baselayer.go rename to internal/wclayer/baselayerwriter.go diff --git a/internal/wclayer/exportlayer.go b/internal/wclayer/exportlayer.go index 485af6849c..80db8f185d 100644 --- a/internal/wclayer/exportlayer.go +++ b/internal/wclayer/exportlayer.go @@ -67,7 +67,16 @@ func NewLayerReader(ctx context.Context, path string, parentLayerPaths []string) trace.StringAttribute("path", path), trace.StringAttribute("parentLayerPaths", strings.Join(parentLayerPaths, ", "))) +<<<<<<< HEAD exportPath, err := os.MkdirTemp("", "hcs") +======= + if len(parentLayerPaths) == 0 { + // This is a base layer. It gets exported differently. + return newBaseLayerReader(ctx, path, span), nil + } + + exportPath, err := ioutil.TempDir("", "hcs") +>>>>>>> e286e8ca (Simple baseLayerReader to export parentless layers) if err != nil { return nil, err } From 2de2a5c70adefdafe34c78c340786587f82f8734 Mon Sep 17 00:00:00 2001 From: "Paul \"TBBle\" Hampson" Date: Sun, 6 Dec 2020 21:09:40 +1100 Subject: [PATCH 02/12] Introduce hcsshim.ConvertToBaseLayer This API allows turning any collection of files into a WCOW base layer. It will create the necessary files in Files/ for hcsshim.ProcessBaseLayer to function, validate the necessary files for hcsshim.ProcessUtilityVMImage if UtilityVM/ exists, and then call those two APIs to complete the process. Calling this on a directory containing an untarred base layer OCI tarball, gives a very similar outcome to passing the tar stream through ociwclayer.ImportLayer. The new API is used in `TestSCSIAddRemoveWCOW` to create nearly-empty base layers for the scratch layers attached and removed from the utility VM. A wclayer command is also introduced: `makebaselayer` for testing and validation purposes. Signed-off-by: Paul "TBBle" Hampson --- cmd/wclayer/makebaselayer.go | 24 ++++ cmd/wclayer/wclayer.go | 1 + internal/wclayer/converttobaselayer.go | 139 ++++++++++++++++++++ internal/wclayer/exportlayer.go | 6 +- layer.go | 3 + test/functional/uvm_mem_backingtype_test.go | 3 +- test/functional/uvm_memory_test.go | 3 +- test/functional/uvm_properties_test.go | 3 +- test/functional/uvm_scsi_test.go | 5 +- test/functional/uvm_vsmb_test.go | 6 +- test/internal/scratch.go | 11 ++ 11 files changed, 193 insertions(+), 11 deletions(-) create mode 100644 cmd/wclayer/makebaselayer.go create mode 100644 internal/wclayer/converttobaselayer.go diff --git a/cmd/wclayer/makebaselayer.go b/cmd/wclayer/makebaselayer.go new file mode 100644 index 0000000000..bdd3b62444 --- /dev/null +++ b/cmd/wclayer/makebaselayer.go @@ -0,0 +1,24 @@ +package main + +import ( + "path/filepath" + + "github.com/Microsoft/hcsshim" + "github.com/Microsoft/hcsshim/internal/appargs" + "github.com/urfave/cli" +) + +var makeBaseLayerCommand = cli.Command{ + Name: "makebaselayer", + Usage: "converts a directory containing 'Files/' into a base layer", + ArgsUsage: "", + Before: appargs.Validate(appargs.NonEmptyString), + Action: func(context *cli.Context) error { + path, err := filepath.Abs(context.Args().First()) + if err != nil { + return err + } + + return hcsshim.ConvertToBaseLayer(path) + }, +} diff --git a/cmd/wclayer/wclayer.go b/cmd/wclayer/wclayer.go index e891a446ed..17b001b0ad 100644 --- a/cmd/wclayer/wclayer.go +++ b/cmd/wclayer/wclayer.go @@ -31,6 +31,7 @@ func main() { createCommand, exportCommand, importCommand, + makeBaseLayerCommand, mountCommand, removeCommand, unmountCommand, diff --git a/internal/wclayer/converttobaselayer.go b/internal/wclayer/converttobaselayer.go new file mode 100644 index 0000000000..65baf6d29e --- /dev/null +++ b/internal/wclayer/converttobaselayer.go @@ -0,0 +1,139 @@ +package wclayer + +import ( + "context" + "os" + "path/filepath" + "syscall" + + "github.com/Microsoft/hcsshim/internal/hcserror" + "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" +) + +var hiveNames = []string{"DEFAULT", "SAM", "SECURITY", "SOFTWARE", "SYSTEM"} + +// Ensure the given file exists as an ordinary file, and create a zero-length file if not. +func ensureFile(path string, root *os.File) error { + stat, err := safefile.LstatRelative(path, root) + if err != nil && os.IsNotExist(err) { + newFile, err := safefile.OpenRelative(path, root, 0, syscall.FILE_SHARE_WRITE, winapi.FILE_CREATE, 0) + if err != nil { + return err + } + return newFile.Close() + } + + if err != nil { + return err + } + + if !stat.Mode().IsRegular() { + fullPath := filepath.Join(root.Name(), path) + return errors.Errorf("%s has unexpected file mode %s", fullPath, stat.Mode().String()) + } + + 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 = ensureFile(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 +} diff --git a/internal/wclayer/exportlayer.go b/internal/wclayer/exportlayer.go index 80db8f185d..33020ba4ae 100644 --- a/internal/wclayer/exportlayer.go +++ b/internal/wclayer/exportlayer.go @@ -67,16 +67,12 @@ func NewLayerReader(ctx context.Context, path string, parentLayerPaths []string) trace.StringAttribute("path", path), trace.StringAttribute("parentLayerPaths", strings.Join(parentLayerPaths, ", "))) -<<<<<<< HEAD - exportPath, err := os.MkdirTemp("", "hcs") -======= if len(parentLayerPaths) == 0 { // This is a base layer. It gets exported differently. return newBaseLayerReader(ctx, path, span), nil } - exportPath, err := ioutil.TempDir("", "hcs") ->>>>>>> e286e8ca (Simple baseLayerReader to export parentless layers) + exportPath, err := os.MkdirTemp("", "hcs") if err != nil { return nil, err } diff --git a/layer.go b/layer.go index e323c8308d..afd1ddd0ae 100644 --- a/layer.go +++ b/layer.go @@ -70,6 +70,9 @@ func ProcessUtilityVMImage(path string) error { func UnprepareLayer(info DriverInfo, layerId string) error { return wclayer.UnprepareLayer(context.Background(), layerPath(&info, layerId)) } +func ConvertToBaseLayer(path string) error { + return wclayer.ConvertToBaseLayer(context.Background(), path) +} type DriverInfo struct { Flavour int diff --git a/test/functional/uvm_mem_backingtype_test.go b/test/functional/uvm_mem_backingtype_test.go index da6f0ec563..f097640056 100644 --- a/test/functional/uvm_mem_backingtype_test.go +++ b/test/functional/uvm_mem_backingtype_test.go @@ -27,7 +27,8 @@ func runMemStartLCOWTest(t *testing.T, opts *uvm.OptionsLCOW) { //nolint:unused // unused since tests are skipped func runMemStartWCOWTest(t *testing.T, opts *uvm.OptionsWCOW) { t.Helper() - u, _, _ := tuvm.CreateWCOWUVMFromOptsWithImage(context.Background(), t, opts, "microsoft/nanoserver") + u, _, scratchDir := tuvm.CreateWCOWUVMFromOptsWithImage(context.Background(), t, opts, "microsoft/nanoserver") + defer os.RemoveAll(scratchDir) u.Close() } diff --git a/test/functional/uvm_memory_test.go b/test/functional/uvm_memory_test.go index 748ddde8f6..4398a7f51d 100644 --- a/test/functional/uvm_memory_test.go +++ b/test/functional/uvm_memory_test.go @@ -55,7 +55,8 @@ func TestUVMMemoryUpdateWCOW(t *testing.T) { opts := uvm.NewDefaultOptionsWCOW(t.Name(), "") opts.MemorySizeInMB = 1024 * 2 - u, _, _ := tuvm.CreateWCOWUVMFromOptsWithImage(ctx, t, opts, "mcr.microsoft.com/windows/nanoserver:1909") + u, _, uvmScratchDir := tuvm.CreateWCOWUVMFromOptsWithImage(ctx, t, opts, "mcr.microsoft.com/windows/nanoserver:1909") + defer os.RemoveAll(uvmScratchDir) defer u.Close() newMemoryInBytes := uint64(opts.MemorySizeInMB/2) * memory.MiB diff --git a/test/functional/uvm_properties_test.go b/test/functional/uvm_properties_test.go index 5bf784ddc5..d114d15983 100644 --- a/test/functional/uvm_properties_test.go +++ b/test/functional/uvm_properties_test.go @@ -36,7 +36,8 @@ func TestPropertiesGuestConnection_WCOW(t *testing.T) { require.Build(t, osversion.RS5) requireFeatures(t, featureWCOW) - uvm, _, _ := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver") + uvm, _, uvmScratchDir := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver") + defer os.RemoveAll(uvmScratchDir) defer uvm.Close() p, gc := uvm.Capabilities() diff --git a/test/functional/uvm_scsi_test.go b/test/functional/uvm_scsi_test.go index 246aa53844..2af0b419bf 100644 --- a/test/functional/uvm_scsi_test.go +++ b/test/functional/uvm_scsi_test.go @@ -48,8 +48,11 @@ func TestSCSIAddRemoveWCOW(t *testing.T) { requireFeatures(t, featureWCOW, featureSCSI) // TODO make the image configurable to the build we're testing on - u, layers, _ := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "mcr.microsoft.com/windows/nanoserver:1903") + u, layers, uvmScratchDir := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "mcr.microsoft.com/windows/nanoserver:1903") + defer os.RemoveAll(uvmScratchDir) defer u.Close() + layers := testutilities.CreateWCOWBlankBaseLayer(context.Background(), t) + defer os.RemoveAll(layers[0]) testSCSIAddRemoveSingle(t, u, `c:\`, "windows", layers) } diff --git a/test/functional/uvm_vsmb_test.go b/test/functional/uvm_vsmb_test.go index 56da116286..19e7931463 100644 --- a/test/functional/uvm_vsmb_test.go +++ b/test/functional/uvm_vsmb_test.go @@ -24,7 +24,8 @@ func TestVSMB(t *testing.T) { require.Build(t, osversion.RS5) requireFeatures(t, featureWCOW, featureVSMB) - uvm, _, _ := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver") + uvm, _, uvmScratchDir := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver") + defer os.RemoveAll(uvmScratchDir) defer uvm.Close() dir := t.TempDir() @@ -55,7 +56,8 @@ func TestVSMB_Writable(t *testing.T) { opts := uvm.NewDefaultOptionsWCOW(t.Name(), "") opts.NoWritableFileShares = true - vm, _, _ := tuvm.CreateWCOWUVMFromOptsWithImage(context.Background(), t, opts, "microsoft/nanoserver") + vm, _, uvmScratchDir := tuvm.CreateWCOWUVMFromOptsWithImage(context.Background(), t, opts, "microsoft/nanoserver") + defer os.RemoveAll(uvmScratchDir) defer vm.Close() dir := t.TempDir() diff --git a/test/internal/scratch.go b/test/internal/scratch.go index 6c2a8b9970..d5bf566610 100644 --- a/test/internal/scratch.go +++ b/test/internal/scratch.go @@ -28,6 +28,17 @@ func init() { } } +// CreateWCOWBlankBaseLayer creates an as-blank-as-possible base WCOW layer, which +// can be used as the base of a WCOW RW layer when it's not going to be the container's +// scratch mount. +func CreateWCOWBlankBaseLayer(ctx context.Context, t *testing.T) []string { + tempDir := CreateTempDir(t) + if err := wclayer.ConvertToBaseLayer(context.Background(), tempDir); err != nil { + t.Fatalf("Failed ConvertToBaseLayer: %s", err) + } + return []string{tempDir} +} + // CreateWCOWBlankRWLayer uses HCS to create a temp test directory containing a // read-write layer containing a disk that can be used as a containers scratch // space. The VHD is created with VM group access From a24bcbcdac7e9079205d3a9ad773dc77d5e43602 Mon Sep 17 00:00:00 2001 From: "Paul \"TBBle\" Hampson" Date: Sat, 12 Dec 2020 23:33:11 +1100 Subject: [PATCH 03/12] Include hard-linked files as hard-links in the tarstream Signed-off-by: Paul "TBBle" Hampson --- internal/wclayer/baselayerreader.go | 12 ++++++++++++ internal/wclayer/exportlayer.go | 2 ++ internal/wclayer/legacy.go | 12 ++++++++++++ pkg/ociwclayer/export.go | 23 +++++++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/internal/wclayer/baselayerreader.go b/internal/wclayer/baselayerreader.go index 7a13c98949..f5c8a57b35 100644 --- a/internal/wclayer/baselayerreader.go +++ b/internal/wclayer/baselayerreader.go @@ -186,6 +186,18 @@ func (r *baseLayerReader) Next() (path string, size int64, fileInfo *winio.FileB return } +func (r *baseLayerReader) LinkInfo() (uint32, *winio.FileIDInfo, error) { + fileStandardInfo, err := winio.GetFileStandardInfo(r.currentFile) + if err != nil { + return 0, nil, err + } + fileIDInfo, err := winio.GetFileID(r.currentFile) + if err != nil { + return 0, nil, err + } + return fileStandardInfo.NumberOfLinks, fileIDInfo, nil +} + func (r *baseLayerReader) Read(b []byte) (int, error) { if r.backupReader == nil { if r.currentFile == nil { diff --git a/internal/wclayer/exportlayer.go b/internal/wclayer/exportlayer.go index 33020ba4ae..bf8b0cfcdb 100644 --- a/internal/wclayer/exportlayer.go +++ b/internal/wclayer/exportlayer.go @@ -45,6 +45,8 @@ func ExportLayer(ctx context.Context, path string, exportFolderPath string, pare type LayerReader interface { // Next advances to the next file and returns the name, size, and file info Next() (string, int64, *winio.FileBasicInfo, error) + // LinkInfo returns the number of links and the file identifier for the current file. + LinkInfo() (uint32, *winio.FileIDInfo, error) // Read reads data from the current file, in the format of a Win32 backup stream, and // returns the number of bytes read. Read(b []byte) (int, error) diff --git a/internal/wclayer/legacy.go b/internal/wclayer/legacy.go index 6feb205e4f..ee8da5df9c 100644 --- a/internal/wclayer/legacy.go +++ b/internal/wclayer/legacy.go @@ -294,6 +294,18 @@ func (r *legacyLayerReader) Next() (path string, size int64, fileInfo *winio.Fil return } +func (r *legacyLayerReader) LinkInfo() (uint32, *winio.FileIDInfo, error) { + fileStandardInfo, err := winio.GetFileStandardInfo(r.currentFile) + if err != nil { + return 0, nil, err + } + fileIDInfo, err := winio.GetFileID(r.currentFile) + if err != nil { + return 0, nil, err + } + return fileStandardInfo.NumberOfLinks, fileIDInfo, nil +} + func (r *legacyLayerReader) Read(b []byte) (int, error) { if r.backupReader == nil { if r.currentFile == nil { diff --git a/pkg/ociwclayer/export.go b/pkg/ociwclayer/export.go index baa2dff3ee..1c2c82c701 100644 --- a/pkg/ociwclayer/export.go +++ b/pkg/ociwclayer/export.go @@ -51,6 +51,8 @@ func ExportLayerToTar(ctx context.Context, w io.Writer, path string, parentLayer } func writeTarFromLayer(ctx context.Context, r wclayer.LayerReader, w io.Writer) error { + linkRecords := make(map[[16]byte]string) + t := tar.NewWriter(w) for { select { @@ -76,6 +78,27 @@ func writeTarFromLayer(ctx context.Context, r wclayer.LayerReader, w io.Writer) return err } } else { + numberOfLinks, fileIDInfo, err := r.LinkInfo() + if err != nil { + return err + } + if numberOfLinks > 1 { + if linkName, ok := linkRecords[fileIDInfo.FileID]; ok { + // We've seen this file before, by another name, so put a hardlink in the tar stream. + hdr := backuptar.BasicInfoHeader(name, 0, fileInfo) + hdr.Mode = 0644 + hdr.Typeflag = tar.TypeLink + hdr.Linkname = linkName + if err := t.WriteHeader(hdr); err != nil { + return err + } + continue + } + + // All subsequent names for this file will be hard-linked to this name + linkRecords[fileIDInfo.FileID] = filepath.ToSlash(name) + } + err = backuptar.WriteTarFileFromBackupStream(t, r, name, size, fileInfo) if err != nil { return err From 9e4cbf360f70a9510a16c0b33526a2ce3c19f0c5 Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Fri, 30 Sep 2022 15:47:47 +0300 Subject: [PATCH 04/12] Use offline registry library to generate min hive This change adds functions to generate valid, empty hives. Signed-off-by: Gabriel Adrian Samfira --- internal/wclayer/converttobaselayer.go | 42 +++++++++++++++++--------- internal/winapi/ofregistry.go | 4 +++ internal/winapi/zsyscall_windows.go | 19 ++++++++++++ 3 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 internal/winapi/ofregistry.go diff --git a/internal/wclayer/converttobaselayer.go b/internal/wclayer/converttobaselayer.go index 65baf6d29e..655d766c05 100644 --- a/internal/wclayer/converttobaselayer.go +++ b/internal/wclayer/converttobaselayer.go @@ -2,38 +2,52 @@ 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 zero-length file if not. -func ensureFile(path string, root *os.File) error { - stat, err := safefile.LstatRelative(path, root) - if err != nil && os.IsNotExist(err) { - newFile, err := safefile.OpenRelative(path, root, 0, syscall.FILE_SHARE_WRITE, winapi.FILE_CREATE, 0) - if err != nil { - return err - } - return newFile.Close() +// Ensure the given file exists as an ordinary file, and create a minimal hive file if not. +func ensureHive(path string, root *os.File) 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") } + fullPath, err := longpath.LongAbs(filepath.Join(root.Name(), path)) if err != nil { - return err + return fmt.Errorf("getting path: %w", err) } - if !stat.Mode().IsRegular() { - fullPath := filepath.Join(root.Name(), path) - return errors.Errorf("%s has unexpected file mode %s", fullPath, stat.Mode().String()) + var key syscall.Handle + if err := winapi.ORCreateHive(&key); err != nil { + return fmt.Errorf("creating hive: %w", err) + } + + hivePath, err := syscall.UTF16PtrFromString(fullPath) + if err != nil { + return fmt.Errorf("getting path: %w", err) + } + + if err := winapi.ORSaveHive(key, hivePath, version.MajorVersion, version.MinorVersion); err != nil { + return fmt.Errorf("saving hive: %w", err) } return nil @@ -48,7 +62,7 @@ func ensureBaseLayer(root *os.File) (hasUtilityVM bool, err error) { for _, hiveName := range hiveNames { hivePath := filepath.Join(hiveSourcePath, hiveName) - if err = ensureFile(hivePath, root); err != nil { + if err = ensureHive(hivePath, root); err != nil { return } } diff --git a/internal/winapi/ofregistry.go b/internal/winapi/ofregistry.go new file mode 100644 index 0000000000..9f41f34bc4 --- /dev/null +++ b/internal/winapi/ofregistry.go @@ -0,0 +1,4 @@ +package winapi + +//sys ORCreateHive(key *syscall.Handle) (regerrno error) = offreg.ORCreateHive +//sys ORSaveHive(key syscall.Handle, file *uint16, OsMajorVersion uint32, OsMinorVersion uint32) (regerrno error) = offreg.ORSaveHive diff --git a/internal/winapi/zsyscall_windows.go b/internal/winapi/zsyscall_windows.go index 47d1209f67..9f573ab4b2 100644 --- a/internal/winapi/zsyscall_windows.go +++ b/internal/winapi/zsyscall_windows.go @@ -47,6 +47,7 @@ var ( modkernel32 = windows.NewLazySystemDLL("kernel32.dll") modnetapi32 = windows.NewLazySystemDLL("netapi32.dll") modntdll = windows.NewLazySystemDLL("ntdll.dll") + modoffreg = windows.NewLazySystemDLL("offreg.dll") procLogonUserW = modadvapi32.NewProc("LogonUserW") procBfSetupFilter = modbindfltapi.NewProc("BfSetupFilter") @@ -82,6 +83,8 @@ var ( procNtQuerySystemInformation = modntdll.NewProc("NtQuerySystemInformation") procNtSetInformationFile = modntdll.NewProc("NtSetInformationFile") procRtlNtStatusToDosError = modntdll.NewProc("RtlNtStatusToDosError") + procORCreateHive = modoffreg.NewProc("ORCreateHive") + procORSaveHive = modoffreg.NewProc("ORSaveHive") ) func LogonUser(username *uint16, domain *uint16, password *uint16, logonType uint32, logonProvider uint32, token *windows.Token) (err error) { @@ -376,3 +379,19 @@ func RtlNtStatusToDosError(status uint32) (winerr error) { } return } + +func ORCreateHive(key *syscall.Handle) (regerrno error) { + r0, _, _ := syscall.Syscall(procORCreateHive.Addr(), 1, uintptr(unsafe.Pointer(key)), 0, 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func ORSaveHive(key syscall.Handle, file *uint16, OsMajorVersion uint32, OsMinorVersion uint32) (regerrno error) { + r0, _, _ := syscall.Syscall6(procORSaveHive.Addr(), 4, uintptr(key), uintptr(unsafe.Pointer(file)), uintptr(OsMajorVersion), uintptr(OsMinorVersion), 0, 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} From 9edb4c9ae5476f20e2c1a04c5f5ef3be74c7a396 Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Mon, 21 Nov 2022 17:21:29 +0200 Subject: [PATCH 05/12] Rename ofreg.go and close key Signed-off-by: Gabriel Adrian Samfira --- internal/wclayer/converttobaselayer.go | 23 +++++++++++++++------ internal/winapi/{ofregistry.go => ofreg.go} | 1 + internal/winapi/zsyscall_windows.go | 9 ++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) rename internal/winapi/{ofregistry.go => ofreg.go} (74%) diff --git a/internal/wclayer/converttobaselayer.go b/internal/wclayer/converttobaselayer.go index 655d766c05..86e68e0789 100644 --- a/internal/wclayer/converttobaselayer.go +++ b/internal/wclayer/converttobaselayer.go @@ -20,8 +20,8 @@ import ( 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) error { - _, err := safefile.LstatRelative(path, root) +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) } @@ -31,22 +31,33 @@ func ensureHive(path string, root *os.File) error { return fmt.Errorf("failed to get OS version") } - fullPath, err := longpath.LongAbs(filepath.Join(root.Name(), path)) + 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 - if err := winapi.ORCreateHive(&key); err != nil { + err = winapi.ORCreateHive(&key) + if err != nil { return fmt.Errorf("creating hive: %w", err) } - hivePath, err := syscall.UTF16PtrFromString(fullPath) + defer func() { + closeErr := winapi.ORCloseHive(&key) + if closeErr != nil { + err = fmt.Errorf("closing hive key: %w", closeErr) + } + }() + + var hivePath *uint16 + hivePath, err = syscall.UTF16PtrFromString(fullPath) if err != nil { return fmt.Errorf("getting path: %w", err) } - if err := winapi.ORSaveHive(key, hivePath, version.MajorVersion, version.MinorVersion); err != nil { + err = winapi.ORSaveHive(key, hivePath, version.MajorVersion, version.MinorVersion) + if err != nil { return fmt.Errorf("saving hive: %w", err) } diff --git a/internal/winapi/ofregistry.go b/internal/winapi/ofreg.go similarity index 74% rename from internal/winapi/ofregistry.go rename to internal/winapi/ofreg.go index 9f41f34bc4..9f7cd3537e 100644 --- a/internal/winapi/ofregistry.go +++ b/internal/winapi/ofreg.go @@ -2,3 +2,4 @@ package winapi //sys ORCreateHive(key *syscall.Handle) (regerrno error) = offreg.ORCreateHive //sys ORSaveHive(key syscall.Handle, file *uint16, OsMajorVersion uint32, OsMinorVersion uint32) (regerrno error) = offreg.ORSaveHive +//sys ORCloseHive(key *syscall.Handle) (regerrno error) = offreg.ORCloseHive diff --git a/internal/winapi/zsyscall_windows.go b/internal/winapi/zsyscall_windows.go index 9f573ab4b2..9fe21209ca 100644 --- a/internal/winapi/zsyscall_windows.go +++ b/internal/winapi/zsyscall_windows.go @@ -85,6 +85,7 @@ var ( procRtlNtStatusToDosError = modntdll.NewProc("RtlNtStatusToDosError") procORCreateHive = modoffreg.NewProc("ORCreateHive") procORSaveHive = modoffreg.NewProc("ORSaveHive") + procORCloseHive = modoffreg.NewProc("ORCloseHive") ) func LogonUser(username *uint16, domain *uint16, password *uint16, logonType uint32, logonProvider uint32, token *windows.Token) (err error) { @@ -395,3 +396,11 @@ func ORSaveHive(key syscall.Handle, file *uint16, OsMajorVersion uint32, OsMinor } return } + +func ORCloseHive(key *syscall.Handle) (regerrno error) { + r0, _, _ := syscall.Syscall(procORCloseHive.Addr(), 1, uintptr(unsafe.Pointer(key)), 0, 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} From 5181c44de483a85062331e9156ee465176732cd9 Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Tue, 31 Jan 2023 08:09:52 -0800 Subject: [PATCH 06/12] Fix temp dir creation Signed-off-by: Gabriel Adrian Samfira --- internal/winapi/zsyscall_windows.go | 14 +++++++------- test/functional/uvm_mem_backingtype_test.go | 1 + test/functional/uvm_memory_test.go | 1 + test/functional/uvm_properties_test.go | 1 + test/functional/uvm_scsi_test.go | 1 - test/functional/uvm_vsmb_test.go | 1 + test/internal/scratch.go | 3 ++- 7 files changed, 13 insertions(+), 9 deletions(-) diff --git a/internal/winapi/zsyscall_windows.go b/internal/winapi/zsyscall_windows.go index 9fe21209ca..dc19142087 100644 --- a/internal/winapi/zsyscall_windows.go +++ b/internal/winapi/zsyscall_windows.go @@ -83,9 +83,9 @@ var ( procNtQuerySystemInformation = modntdll.NewProc("NtQuerySystemInformation") procNtSetInformationFile = modntdll.NewProc("NtSetInformationFile") procRtlNtStatusToDosError = modntdll.NewProc("RtlNtStatusToDosError") + procORCloseHive = modoffreg.NewProc("ORCloseHive") procORCreateHive = modoffreg.NewProc("ORCreateHive") procORSaveHive = modoffreg.NewProc("ORSaveHive") - procORCloseHive = modoffreg.NewProc("ORCloseHive") ) func LogonUser(username *uint16, domain *uint16, password *uint16, logonType uint32, logonProvider uint32, token *windows.Token) (err error) { @@ -381,24 +381,24 @@ func RtlNtStatusToDosError(status uint32) (winerr error) { return } -func ORCreateHive(key *syscall.Handle) (regerrno error) { - r0, _, _ := syscall.Syscall(procORCreateHive.Addr(), 1, uintptr(unsafe.Pointer(key)), 0, 0) +func ORCloseHive(key *syscall.Handle) (regerrno error) { + r0, _, _ := syscall.Syscall(procORCloseHive.Addr(), 1, uintptr(unsafe.Pointer(key)), 0, 0) if r0 != 0 { regerrno = syscall.Errno(r0) } return } -func ORSaveHive(key syscall.Handle, file *uint16, OsMajorVersion uint32, OsMinorVersion uint32) (regerrno error) { - r0, _, _ := syscall.Syscall6(procORSaveHive.Addr(), 4, uintptr(key), uintptr(unsafe.Pointer(file)), uintptr(OsMajorVersion), uintptr(OsMinorVersion), 0, 0) +func ORCreateHive(key *syscall.Handle) (regerrno error) { + r0, _, _ := syscall.Syscall(procORCreateHive.Addr(), 1, uintptr(unsafe.Pointer(key)), 0, 0) if r0 != 0 { regerrno = syscall.Errno(r0) } return } -func ORCloseHive(key *syscall.Handle) (regerrno error) { - r0, _, _ := syscall.Syscall(procORCloseHive.Addr(), 1, uintptr(unsafe.Pointer(key)), 0, 0) +func ORSaveHive(key syscall.Handle, file *uint16, OsMajorVersion uint32, OsMinorVersion uint32) (regerrno error) { + r0, _, _ := syscall.Syscall6(procORSaveHive.Addr(), 4, uintptr(key), uintptr(unsafe.Pointer(file)), uintptr(OsMajorVersion), uintptr(OsMinorVersion), 0, 0) if r0 != 0 { regerrno = syscall.Errno(r0) } diff --git a/test/functional/uvm_mem_backingtype_test.go b/test/functional/uvm_mem_backingtype_test.go index f097640056..50eab781c1 100644 --- a/test/functional/uvm_mem_backingtype_test.go +++ b/test/functional/uvm_mem_backingtype_test.go @@ -7,6 +7,7 @@ package functional import ( "context" "io" + "os" "testing" "github.com/Microsoft/hcsshim/internal/uvm" diff --git a/test/functional/uvm_memory_test.go b/test/functional/uvm_memory_test.go index 4398a7f51d..cee55348ae 100644 --- a/test/functional/uvm_memory_test.go +++ b/test/functional/uvm_memory_test.go @@ -4,6 +4,7 @@ package functional import ( "context" + "os" "testing" "time" diff --git a/test/functional/uvm_properties_test.go b/test/functional/uvm_properties_test.go index d114d15983..aabd6ab508 100644 --- a/test/functional/uvm_properties_test.go +++ b/test/functional/uvm_properties_test.go @@ -6,6 +6,7 @@ package functional import ( "context" + "os" "testing" "github.com/Microsoft/hcsshim/osversion" diff --git a/test/functional/uvm_scsi_test.go b/test/functional/uvm_scsi_test.go index 2af0b419bf..b9dae75e1c 100644 --- a/test/functional/uvm_scsi_test.go +++ b/test/functional/uvm_scsi_test.go @@ -51,7 +51,6 @@ func TestSCSIAddRemoveWCOW(t *testing.T) { u, layers, uvmScratchDir := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "mcr.microsoft.com/windows/nanoserver:1903") defer os.RemoveAll(uvmScratchDir) defer u.Close() - layers := testutilities.CreateWCOWBlankBaseLayer(context.Background(), t) defer os.RemoveAll(layers[0]) testSCSIAddRemoveSingle(t, u, `c:\`, "windows", layers) diff --git a/test/functional/uvm_vsmb_test.go b/test/functional/uvm_vsmb_test.go index 19e7931463..b51a40a8a6 100644 --- a/test/functional/uvm_vsmb_test.go +++ b/test/functional/uvm_vsmb_test.go @@ -7,6 +7,7 @@ package functional import ( "context" "errors" + "os" "testing" "github.com/Microsoft/hcsshim/internal/hcs" diff --git a/test/internal/scratch.go b/test/internal/scratch.go index d5bf566610..3694182af9 100644 --- a/test/internal/scratch.go +++ b/test/internal/scratch.go @@ -32,7 +32,8 @@ func init() { // can be used as the base of a WCOW RW layer when it's not going to be the container's // scratch mount. func CreateWCOWBlankBaseLayer(ctx context.Context, t *testing.T) []string { - tempDir := CreateTempDir(t) + t.Helper() + tempDir := t.TempDir() if err := wclayer.ConvertToBaseLayer(context.Background(), tempDir); err != nil { t.Fatalf("Failed ConvertToBaseLayer: %s", err) } From 99c91464b0f87cbf7aa13dc0e03cdcf07fb2b0ea Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Tue, 31 Jan 2023 09:39:32 -0800 Subject: [PATCH 07/12] Cleanup tests Signed-off-by: Gabriel Adrian Samfira --- test/functional/uvm_mem_backingtype_test.go | 4 +--- test/functional/uvm_memory_test.go | 4 +--- test/functional/uvm_properties_test.go | 4 +--- test/functional/uvm_scsi_test.go | 4 +--- test/functional/uvm_vsmb_test.go | 7 ++----- test/internal/scratch.go | 2 +- 6 files changed, 7 insertions(+), 18 deletions(-) diff --git a/test/functional/uvm_mem_backingtype_test.go b/test/functional/uvm_mem_backingtype_test.go index 50eab781c1..da6f0ec563 100644 --- a/test/functional/uvm_mem_backingtype_test.go +++ b/test/functional/uvm_mem_backingtype_test.go @@ -7,7 +7,6 @@ package functional import ( "context" "io" - "os" "testing" "github.com/Microsoft/hcsshim/internal/uvm" @@ -28,8 +27,7 @@ func runMemStartLCOWTest(t *testing.T, opts *uvm.OptionsLCOW) { //nolint:unused // unused since tests are skipped func runMemStartWCOWTest(t *testing.T, opts *uvm.OptionsWCOW) { t.Helper() - u, _, scratchDir := tuvm.CreateWCOWUVMFromOptsWithImage(context.Background(), t, opts, "microsoft/nanoserver") - defer os.RemoveAll(scratchDir) + u, _, _ := tuvm.CreateWCOWUVMFromOptsWithImage(context.Background(), t, opts, "microsoft/nanoserver") u.Close() } diff --git a/test/functional/uvm_memory_test.go b/test/functional/uvm_memory_test.go index cee55348ae..748ddde8f6 100644 --- a/test/functional/uvm_memory_test.go +++ b/test/functional/uvm_memory_test.go @@ -4,7 +4,6 @@ package functional import ( "context" - "os" "testing" "time" @@ -56,8 +55,7 @@ func TestUVMMemoryUpdateWCOW(t *testing.T) { opts := uvm.NewDefaultOptionsWCOW(t.Name(), "") opts.MemorySizeInMB = 1024 * 2 - u, _, uvmScratchDir := tuvm.CreateWCOWUVMFromOptsWithImage(ctx, t, opts, "mcr.microsoft.com/windows/nanoserver:1909") - defer os.RemoveAll(uvmScratchDir) + u, _, _ := tuvm.CreateWCOWUVMFromOptsWithImage(ctx, t, opts, "mcr.microsoft.com/windows/nanoserver:1909") defer u.Close() newMemoryInBytes := uint64(opts.MemorySizeInMB/2) * memory.MiB diff --git a/test/functional/uvm_properties_test.go b/test/functional/uvm_properties_test.go index aabd6ab508..5bf784ddc5 100644 --- a/test/functional/uvm_properties_test.go +++ b/test/functional/uvm_properties_test.go @@ -6,7 +6,6 @@ package functional import ( "context" - "os" "testing" "github.com/Microsoft/hcsshim/osversion" @@ -37,8 +36,7 @@ func TestPropertiesGuestConnection_WCOW(t *testing.T) { require.Build(t, osversion.RS5) requireFeatures(t, featureWCOW) - uvm, _, uvmScratchDir := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver") - defer os.RemoveAll(uvmScratchDir) + uvm, _, _ := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver") defer uvm.Close() p, gc := uvm.Capabilities() diff --git a/test/functional/uvm_scsi_test.go b/test/functional/uvm_scsi_test.go index b9dae75e1c..246aa53844 100644 --- a/test/functional/uvm_scsi_test.go +++ b/test/functional/uvm_scsi_test.go @@ -48,10 +48,8 @@ func TestSCSIAddRemoveWCOW(t *testing.T) { requireFeatures(t, featureWCOW, featureSCSI) // TODO make the image configurable to the build we're testing on - u, layers, uvmScratchDir := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "mcr.microsoft.com/windows/nanoserver:1903") - defer os.RemoveAll(uvmScratchDir) + u, layers, _ := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "mcr.microsoft.com/windows/nanoserver:1903") defer u.Close() - defer os.RemoveAll(layers[0]) testSCSIAddRemoveSingle(t, u, `c:\`, "windows", layers) } diff --git a/test/functional/uvm_vsmb_test.go b/test/functional/uvm_vsmb_test.go index b51a40a8a6..56da116286 100644 --- a/test/functional/uvm_vsmb_test.go +++ b/test/functional/uvm_vsmb_test.go @@ -7,7 +7,6 @@ package functional import ( "context" "errors" - "os" "testing" "github.com/Microsoft/hcsshim/internal/hcs" @@ -25,8 +24,7 @@ func TestVSMB(t *testing.T) { require.Build(t, osversion.RS5) requireFeatures(t, featureWCOW, featureVSMB) - uvm, _, uvmScratchDir := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver") - defer os.RemoveAll(uvmScratchDir) + uvm, _, _ := tuvm.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver") defer uvm.Close() dir := t.TempDir() @@ -57,8 +55,7 @@ func TestVSMB_Writable(t *testing.T) { opts := uvm.NewDefaultOptionsWCOW(t.Name(), "") opts.NoWritableFileShares = true - vm, _, uvmScratchDir := tuvm.CreateWCOWUVMFromOptsWithImage(context.Background(), t, opts, "microsoft/nanoserver") - defer os.RemoveAll(uvmScratchDir) + vm, _, _ := tuvm.CreateWCOWUVMFromOptsWithImage(context.Background(), t, opts, "microsoft/nanoserver") defer vm.Close() dir := t.TempDir() diff --git a/test/internal/scratch.go b/test/internal/scratch.go index 3694182af9..cee87bec42 100644 --- a/test/internal/scratch.go +++ b/test/internal/scratch.go @@ -34,7 +34,7 @@ func init() { func CreateWCOWBlankBaseLayer(ctx context.Context, t *testing.T) []string { t.Helper() tempDir := t.TempDir() - if err := wclayer.ConvertToBaseLayer(context.Background(), tempDir); err != nil { + if err := wclayer.ConvertToBaseLayer(ctx, tempDir); err != nil { t.Fatalf("Failed ConvertToBaseLayer: %s", err) } return []string{tempDir} From 9bfb46a9c70333f6b40b6dbce719d60f95d275b3 Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Fri, 3 Feb 2023 09:40:19 -0800 Subject: [PATCH 08/12] Fix ORCloseHive definition Signed-off-by: Gabriel Adrian Samfira --- internal/wclayer/converttobaselayer.go | 2 +- internal/winapi/ofreg.go | 2 +- internal/winapi/zsyscall_windows.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/wclayer/converttobaselayer.go b/internal/wclayer/converttobaselayer.go index 86e68e0789..0728636fd5 100644 --- a/internal/wclayer/converttobaselayer.go +++ b/internal/wclayer/converttobaselayer.go @@ -44,7 +44,7 @@ func ensureHive(path string, root *os.File) (err error) { } defer func() { - closeErr := winapi.ORCloseHive(&key) + closeErr := winapi.ORCloseHive(key) if closeErr != nil { err = fmt.Errorf("closing hive key: %w", closeErr) } diff --git a/internal/winapi/ofreg.go b/internal/winapi/ofreg.go index 9f7cd3537e..c7e8a82971 100644 --- a/internal/winapi/ofreg.go +++ b/internal/winapi/ofreg.go @@ -2,4 +2,4 @@ package winapi //sys ORCreateHive(key *syscall.Handle) (regerrno error) = offreg.ORCreateHive //sys ORSaveHive(key syscall.Handle, file *uint16, OsMajorVersion uint32, OsMinorVersion uint32) (regerrno error) = offreg.ORSaveHive -//sys ORCloseHive(key *syscall.Handle) (regerrno error) = offreg.ORCloseHive +//sys ORCloseHive(key syscall.Handle) (regerrno error) = offreg.ORCloseHive diff --git a/internal/winapi/zsyscall_windows.go b/internal/winapi/zsyscall_windows.go index dc19142087..904f6b1fc8 100644 --- a/internal/winapi/zsyscall_windows.go +++ b/internal/winapi/zsyscall_windows.go @@ -381,8 +381,8 @@ func RtlNtStatusToDosError(status uint32) (winerr error) { return } -func ORCloseHive(key *syscall.Handle) (regerrno error) { - r0, _, _ := syscall.Syscall(procORCloseHive.Addr(), 1, uintptr(unsafe.Pointer(key)), 0, 0) +func ORCloseHive(key syscall.Handle) (regerrno error) { + r0, _, _ := syscall.Syscall(procORCloseHive.Addr(), 1, uintptr(key), 0, 0) if r0 != 0 { regerrno = syscall.Errno(r0) } From 077be0f6c4800110732768355073472d34da6092 Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Wed, 8 Feb 2023 03:16:27 -0800 Subject: [PATCH 09/12] Remove unused ctx from baseLayerReader Signed-off-by: Gabriel Adrian Samfira --- internal/wclayer/baselayerreader.go | 5 +---- internal/wclayer/exportlayer.go | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/wclayer/baselayerreader.go b/internal/wclayer/baselayerreader.go index f5c8a57b35..3e8ad7e45d 100644 --- a/internal/wclayer/baselayerreader.go +++ b/internal/wclayer/baselayerreader.go @@ -1,7 +1,6 @@ package wclayer import ( - "context" "errors" "io" "os" @@ -16,7 +15,6 @@ import ( ) type baseLayerReader struct { - ctx context.Context s *trace.Span root string result chan *fileEntry @@ -25,9 +23,8 @@ type baseLayerReader struct { backupReader *winio.BackupFileReader } -func newBaseLayerReader(ctx context.Context, root string, s *trace.Span) (r *baseLayerReader) { +func newBaseLayerReader(root string, s *trace.Span) (r *baseLayerReader) { r = &baseLayerReader{ - ctx: ctx, s: s, root: root, result: make(chan *fileEntry), diff --git a/internal/wclayer/exportlayer.go b/internal/wclayer/exportlayer.go index bf8b0cfcdb..d4c677aabf 100644 --- a/internal/wclayer/exportlayer.go +++ b/internal/wclayer/exportlayer.go @@ -71,7 +71,7 @@ func NewLayerReader(ctx context.Context, path string, parentLayerPaths []string) if len(parentLayerPaths) == 0 { // This is a base layer. It gets exported differently. - return newBaseLayerReader(ctx, path, span), nil + return newBaseLayerReader(path, span), nil } exportPath, err := os.MkdirTemp("", "hcs") From e79beb3e1e58d1cad94af17b83670d26dd7afcba Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Mon, 27 Feb 2023 01:30:59 -0800 Subject: [PATCH 10/12] Use string in sys definition and check for err * We can use string instead of *uint16 in the //sys definition and allow mksyscall to generate the proper boilerplate. * do not shadow err if it's not nil Signed-off-by: Gabriel Adrian Samfira --- internal/wclayer/converttobaselayer.go | 10 ++-------- internal/winapi/ofreg.go | 2 +- internal/winapi/zsyscall_windows.go | 11 ++++++++++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/internal/wclayer/converttobaselayer.go b/internal/wclayer/converttobaselayer.go index 0728636fd5..ceb3b50835 100644 --- a/internal/wclayer/converttobaselayer.go +++ b/internal/wclayer/converttobaselayer.go @@ -45,18 +45,12 @@ func ensureHive(path string, root *os.File) (err error) { defer func() { closeErr := winapi.ORCloseHive(key) - if closeErr != nil { + if closeErr != nil && err == nil { err = fmt.Errorf("closing hive key: %w", closeErr) } }() - var hivePath *uint16 - hivePath, err = syscall.UTF16PtrFromString(fullPath) - if err != nil { - return fmt.Errorf("getting path: %w", err) - } - - err = winapi.ORSaveHive(key, hivePath, version.MajorVersion, version.MinorVersion) + err = winapi.ORSaveHive(key, fullPath, version.MajorVersion, version.MinorVersion) if err != nil { return fmt.Errorf("saving hive: %w", err) } diff --git a/internal/winapi/ofreg.go b/internal/winapi/ofreg.go index c7e8a82971..d8f7afe8a4 100644 --- a/internal/winapi/ofreg.go +++ b/internal/winapi/ofreg.go @@ -1,5 +1,5 @@ package winapi //sys ORCreateHive(key *syscall.Handle) (regerrno error) = offreg.ORCreateHive -//sys ORSaveHive(key syscall.Handle, file *uint16, OsMajorVersion uint32, OsMinorVersion uint32) (regerrno error) = offreg.ORSaveHive +//sys ORSaveHive(key syscall.Handle, file string, OsMajorVersion uint32, OsMinorVersion uint32) (regerrno error) = offreg.ORSaveHive //sys ORCloseHive(key syscall.Handle) (regerrno error) = offreg.ORCloseHive diff --git a/internal/winapi/zsyscall_windows.go b/internal/winapi/zsyscall_windows.go index 904f6b1fc8..c607245eb3 100644 --- a/internal/winapi/zsyscall_windows.go +++ b/internal/winapi/zsyscall_windows.go @@ -397,7 +397,16 @@ func ORCreateHive(key *syscall.Handle) (regerrno error) { return } -func ORSaveHive(key syscall.Handle, file *uint16, OsMajorVersion uint32, OsMinorVersion uint32) (regerrno error) { +func ORSaveHive(key syscall.Handle, file string, OsMajorVersion uint32, OsMinorVersion uint32) (regerrno error) { + var _p0 *uint16 + _p0, regerrno = syscall.UTF16PtrFromString(file) + if regerrno != nil { + return + } + return _ORSaveHive(key, _p0, OsMajorVersion, OsMinorVersion) +} + +func _ORSaveHive(key syscall.Handle, file *uint16, OsMajorVersion uint32, OsMinorVersion uint32) (regerrno error) { r0, _, _ := syscall.Syscall6(procORSaveHive.Addr(), 4, uintptr(key), uintptr(unsafe.Pointer(file)), uintptr(OsMajorVersion), uintptr(OsMinorVersion), 0, 0) if r0 != 0 { regerrno = syscall.Errno(r0) From b2acd4e27309f4d516ac7c859b2f04fe75baeeca Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Mon, 27 Feb 2023 01:34:23 -0800 Subject: [PATCH 11/12] Close the r.proceed channel Signed-off-by: Gabriel Adrian Samfira --- internal/wclayer/baselayerreader.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/wclayer/baselayerreader.go b/internal/wclayer/baselayerreader.go index 3e8ad7e45d..b45772e82f 100644 --- a/internal/wclayer/baselayerreader.go +++ b/internal/wclayer/baselayerreader.go @@ -207,8 +207,12 @@ func (r *baseLayerReader) Read(b []byte) (int, error) { func (r *baseLayerReader) Close() (err error) { defer r.s.End() - defer func() { oc.SetSpanStatus(r.s, err) }() + defer func() { + oc.SetSpanStatus(r.s, err) + close(r.proceed) + }() r.proceed <- false + // The r.result channel will be closed once walk() returns <-r.result r.reset() return nil From ee3a0edc70516944e4224c6b0b7a518b397836d7 Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Mon, 27 Feb 2023 22:08:10 -0800 Subject: [PATCH 12/12] Return if backup reader is nil Signed-off-by: Gabriel Adrian Samfira --- internal/wclayer/baselayerreader.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/wclayer/baselayerreader.go b/internal/wclayer/baselayerreader.go index b45772e82f..ec4423effe 100644 --- a/internal/wclayer/baselayerreader.go +++ b/internal/wclayer/baselayerreader.go @@ -197,10 +197,7 @@ func (r *baseLayerReader) LinkInfo() (uint32, *winio.FileIDInfo, error) { func (r *baseLayerReader) Read(b []byte) (int, error) { if r.backupReader == nil { - if r.currentFile == nil { - return 0, io.EOF - } - return r.currentFile.Read(b) + return 0, io.EOF } return r.backupReader.Read(b) }