mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-01 00:23:56 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			816 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			816 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package wclayer
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"encoding/binary"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"syscall"
 | |
| 
 | |
| 	"github.com/Microsoft/go-winio"
 | |
| 	"github.com/Microsoft/hcsshim/internal/longpath"
 | |
| 	"github.com/Microsoft/hcsshim/internal/safefile"
 | |
| )
 | |
| 
 | |
| var errorIterationCanceled = errors.New("")
 | |
| 
 | |
| var mutatedUtilityVMFiles = map[string]bool{
 | |
| 	`EFI\Microsoft\Boot\BCD`:      true,
 | |
| 	`EFI\Microsoft\Boot\BCD.LOG`:  true,
 | |
| 	`EFI\Microsoft\Boot\BCD.LOG1`: true,
 | |
| 	`EFI\Microsoft\Boot\BCD.LOG2`: true,
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	filesPath          = `Files`
 | |
| 	hivesPath          = `Hives`
 | |
| 	utilityVMPath      = `UtilityVM`
 | |
| 	utilityVMFilesPath = `UtilityVM\Files`
 | |
| )
 | |
| 
 | |
| func openFileOrDir(path string, mode uint32, createDisposition uint32) (file *os.File, err error) {
 | |
| 	return winio.OpenForBackup(path, mode, syscall.FILE_SHARE_READ, createDisposition)
 | |
| }
 | |
| 
 | |
| func hasPathPrefix(p, prefix string) bool {
 | |
| 	return strings.HasPrefix(p, prefix) && len(p) > len(prefix) && p[len(prefix)] == '\\'
 | |
| }
 | |
| 
 | |
| type fileEntry struct {
 | |
| 	path string
 | |
| 	fi   os.FileInfo
 | |
| 	err  error
 | |
| }
 | |
| 
 | |
| type legacyLayerReader struct {
 | |
| 	root         string
 | |
| 	result       chan *fileEntry
 | |
| 	proceed      chan bool
 | |
| 	currentFile  *os.File
 | |
| 	backupReader *winio.BackupFileReader
 | |
| }
 | |
| 
 | |
| // newLegacyLayerReader returns a new LayerReader that can read the Windows
 | |
| // container layer transport format from disk.
 | |
| func newLegacyLayerReader(root string) *legacyLayerReader {
 | |
| 	r := &legacyLayerReader{
 | |
| 		root:    root,
 | |
| 		result:  make(chan *fileEntry),
 | |
| 		proceed: make(chan bool),
 | |
| 	}
 | |
| 	go r.walk()
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| func readTombstones(path string) (map[string]([]string), error) {
 | |
| 	tf, err := os.Open(filepath.Join(path, "tombstones.txt"))
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer tf.Close()
 | |
| 	s := bufio.NewScanner(tf)
 | |
| 	if !s.Scan() || s.Text() != "\xef\xbb\xbfVersion 1.0" {
 | |
| 		return nil, errors.New("Invalid tombstones file")
 | |
| 	}
 | |
| 
 | |
| 	ts := make(map[string]([]string))
 | |
| 	for s.Scan() {
 | |
| 		t := filepath.Join(filesPath, s.Text()[1:]) // skip leading `\`
 | |
| 		dir := filepath.Dir(t)
 | |
| 		ts[dir] = append(ts[dir], t)
 | |
| 	}
 | |
| 	if err = s.Err(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return ts, nil
 | |
| }
 | |
| 
 | |
| func (r *legacyLayerReader) walkUntilCancelled() error {
 | |
| 	root, err := longpath.LongAbs(r.root)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	r.root = root
 | |
| 	ts, err := readTombstones(r.root)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	err = filepath.Walk(r.root, 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
 | |
| 		}
 | |
| 
 | |
| 		if path == r.root || path == filepath.Join(r.root, "tombstones.txt") || strings.HasSuffix(path, ".$wcidirs$") {
 | |
| 			return nil
 | |
| 		}
 | |
| 
 | |
| 		r.result <- &fileEntry{path, info, nil}
 | |
| 		if !<-r.proceed {
 | |
| 			return errorIterationCanceled
 | |
| 		}
 | |
| 
 | |
| 		// List all the tombstones.
 | |
| 		if info.IsDir() {
 | |
| 			relPath, err := filepath.Rel(r.root, path)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			if dts, ok := ts[relPath]; ok {
 | |
| 				for _, t := range dts {
 | |
| 					r.result <- &fileEntry{filepath.Join(r.root, t), nil, nil}
 | |
| 					if !<-r.proceed {
 | |
| 						return errorIterationCanceled
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return nil
 | |
| 	})
 | |
| 	if err == errorIterationCanceled {
 | |
| 		return nil
 | |
| 	}
 | |
| 	if err == nil {
 | |
| 		return io.EOF
 | |
| 	}
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (r *legacyLayerReader) 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 *legacyLayerReader) reset() {
 | |
| 	if r.backupReader != nil {
 | |
| 		r.backupReader.Close()
 | |
| 		r.backupReader = nil
 | |
| 	}
 | |
| 	if r.currentFile != nil {
 | |
| 		r.currentFile.Close()
 | |
| 		r.currentFile = nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func findBackupStreamSize(r io.Reader) (int64, error) {
 | |
| 	br := winio.NewBackupStreamReader(r)
 | |
| 	for {
 | |
| 		hdr, err := br.Next()
 | |
| 		if err != nil {
 | |
| 			if err == io.EOF {
 | |
| 				err = nil
 | |
| 			}
 | |
| 			return 0, err
 | |
| 		}
 | |
| 		if hdr.Id == winio.BackupData {
 | |
| 			return hdr.Size, nil
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (r *legacyLayerReader) Next() (path string, size int64, fileInfo *winio.FileBasicInfo, err error) {
 | |
| 	r.reset()
 | |
| 	r.proceed <- true
 | |
| 	fe := <-r.result
 | |
| 	if fe == nil {
 | |
| 		err = errors.New("LegacyLayerReader closed")
 | |
| 		return
 | |
| 	}
 | |
| 	if fe.err != nil {
 | |
| 		err = fe.err
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	path, err = filepath.Rel(r.root, fe.path)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if fe.fi == nil {
 | |
| 		// This is a tombstone. Return a nil fileInfo.
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if fe.fi.IsDir() && hasPathPrefix(path, filesPath) {
 | |
| 		fe.path += ".$wcidirs$"
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| 	}
 | |
| 
 | |
| 	if !hasPathPrefix(path, filesPath) {
 | |
| 		size = fe.fi.Size()
 | |
| 		r.backupReader = winio.NewBackupFileReader(f, false)
 | |
| 		if path == hivesPath || path == filesPath {
 | |
| 			// The Hives directory has a non-deterministic file time because of the
 | |
| 			// nature of the import process. Use the times from System_Delta.
 | |
| 			var g *os.File
 | |
| 			g, err = os.Open(filepath.Join(r.root, hivesPath, `System_Delta`))
 | |
| 			if err != nil {
 | |
| 				return
 | |
| 			}
 | |
| 			attr := fileInfo.FileAttributes
 | |
| 			fileInfo, err = winio.GetFileBasicInfo(g)
 | |
| 			g.Close()
 | |
| 			if err != nil {
 | |
| 				return
 | |
| 			}
 | |
| 			fileInfo.FileAttributes = attr
 | |
| 		}
 | |
| 
 | |
| 		// The creation time and access time get reset for files outside of the Files path.
 | |
| 		fileInfo.CreationTime = fileInfo.LastWriteTime
 | |
| 		fileInfo.LastAccessTime = fileInfo.LastWriteTime
 | |
| 
 | |
| 	} else {
 | |
| 		// The file attributes are written before the backup stream.
 | |
| 		var attr uint32
 | |
| 		err = binary.Read(f, binary.LittleEndian, &attr)
 | |
| 		if err != nil {
 | |
| 			return
 | |
| 		}
 | |
| 		fileInfo.FileAttributes = attr
 | |
| 		beginning := int64(4)
 | |
| 
 | |
| 		// Find the accurate file size.
 | |
| 		if !fe.fi.IsDir() {
 | |
| 			size, err = findBackupStreamSize(f)
 | |
| 			if err != nil {
 | |
| 				err = &os.PathError{Op: "findBackupStreamSize", Path: fe.path, Err: err}
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Return back to the beginning of the backup stream.
 | |
| 		_, err = f.Seek(beginning, 0)
 | |
| 		if err != nil {
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	r.currentFile = f
 | |
| 	f = nil
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (r *legacyLayerReader) 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 *legacyLayerReader) Seek(offset int64, whence int) (int64, error) {
 | |
| 	if r.backupReader == nil {
 | |
| 		if r.currentFile == nil {
 | |
| 			return 0, errors.New("no current file")
 | |
| 		}
 | |
| 		return r.currentFile.Seek(offset, whence)
 | |
| 	}
 | |
| 	return 0, errors.New("seek not supported on this stream")
 | |
| }
 | |
| 
 | |
| func (r *legacyLayerReader) Close() error {
 | |
| 	r.proceed <- false
 | |
| 	<-r.result
 | |
| 	r.reset()
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| type pendingLink struct {
 | |
| 	Path, Target string
 | |
| 	TargetRoot   *os.File
 | |
| }
 | |
| 
 | |
| type pendingDir struct {
 | |
| 	Path string
 | |
| 	Root *os.File
 | |
| }
 | |
| 
 | |
| type legacyLayerWriter struct {
 | |
| 	root            *os.File
 | |
| 	destRoot        *os.File
 | |
| 	parentRoots     []*os.File
 | |
| 	currentFile     *os.File
 | |
| 	bufWriter       *bufio.Writer
 | |
| 	currentFileName string
 | |
| 	currentFileRoot *os.File
 | |
| 	backupWriter    *winio.BackupFileWriter
 | |
| 	Tombstones      []string
 | |
| 	HasUtilityVM    bool
 | |
| 	uvmDi           []dirInfo
 | |
| 	addedFiles      map[string]bool
 | |
| 	PendingLinks    []pendingLink
 | |
| 	pendingDirs     []pendingDir
 | |
| 	currentIsDir    bool
 | |
| }
 | |
| 
 | |
| // newLegacyLayerWriter returns a LayerWriter that can write the contaler layer
 | |
| // transport format to disk.
 | |
| func newLegacyLayerWriter(root string, parentRoots []string, destRoot string) (w *legacyLayerWriter, err error) {
 | |
| 	w = &legacyLayerWriter{
 | |
| 		addedFiles: make(map[string]bool),
 | |
| 	}
 | |
| 	defer func() {
 | |
| 		if err != nil {
 | |
| 			w.CloseRoots()
 | |
| 			w = nil
 | |
| 		}
 | |
| 	}()
 | |
| 	w.root, err = safefile.OpenRoot(root)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	w.destRoot, err = safefile.OpenRoot(destRoot)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	for _, r := range parentRoots {
 | |
| 		f, err := safefile.OpenRoot(r)
 | |
| 		if err != nil {
 | |
| 			return w, err
 | |
| 		}
 | |
| 		w.parentRoots = append(w.parentRoots, f)
 | |
| 	}
 | |
| 	w.bufWriter = bufio.NewWriterSize(ioutil.Discard, 65536)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (w *legacyLayerWriter) CloseRoots() {
 | |
| 	if w.root != nil {
 | |
| 		w.root.Close()
 | |
| 		w.root = nil
 | |
| 	}
 | |
| 	if w.destRoot != nil {
 | |
| 		w.destRoot.Close()
 | |
| 		w.destRoot = nil
 | |
| 	}
 | |
| 	for i := range w.parentRoots {
 | |
| 		w.parentRoots[i].Close()
 | |
| 	}
 | |
| 	w.parentRoots = nil
 | |
| }
 | |
| 
 | |
| func (w *legacyLayerWriter) initUtilityVM() error {
 | |
| 	if !w.HasUtilityVM {
 | |
| 		err := safefile.MkdirRelative(utilityVMPath, w.destRoot)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		// Server 2016 does not support multiple layers for the utility VM, so
 | |
| 		// clone the utility VM from the parent layer into this layer. Use hard
 | |
| 		// links to avoid unnecessary copying, since most of the files are
 | |
| 		// immutable.
 | |
| 		err = cloneTree(w.parentRoots[0], w.destRoot, utilityVMFilesPath, mutatedUtilityVMFiles)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("cloning the parent utility VM image failed: %s", err)
 | |
| 		}
 | |
| 		w.HasUtilityVM = true
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (w *legacyLayerWriter) reset() error {
 | |
| 	err := w.bufWriter.Flush()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	w.bufWriter.Reset(ioutil.Discard)
 | |
| 	if w.currentIsDir {
 | |
| 		r := w.currentFile
 | |
| 		br := winio.NewBackupStreamReader(r)
 | |
| 		// Seek to the beginning of the backup stream, skipping the fileattrs
 | |
| 		if _, err := r.Seek(4, io.SeekStart); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		for {
 | |
| 			bhdr, err := br.Next()
 | |
| 			if err == io.EOF {
 | |
| 				// end of backupstream data
 | |
| 				break
 | |
| 			}
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			switch bhdr.Id {
 | |
| 			case winio.BackupReparseData:
 | |
| 				// The current file is a `.$wcidirs$` metadata file that
 | |
| 				// describes a directory reparse point. Delete the placeholder
 | |
| 				// directory to prevent future files being added into the
 | |
| 				// destination of the reparse point during the ImportLayer call
 | |
| 				if err := safefile.RemoveRelative(w.currentFileName, w.currentFileRoot); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				w.pendingDirs = append(w.pendingDirs, pendingDir{Path: w.currentFileName, Root: w.currentFileRoot})
 | |
| 			default:
 | |
| 				// ignore all other stream types, as we only care about directory reparse points
 | |
| 			}
 | |
| 		}
 | |
| 		w.currentIsDir = false
 | |
| 	}
 | |
| 	if w.backupWriter != nil {
 | |
| 		w.backupWriter.Close()
 | |
| 		w.backupWriter = nil
 | |
| 	}
 | |
| 	if w.currentFile != nil {
 | |
| 		w.currentFile.Close()
 | |
| 		w.currentFile = nil
 | |
| 		w.currentFileName = ""
 | |
| 		w.currentFileRoot = nil
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // copyFileWithMetadata copies a file using the backup/restore APIs in order to preserve metadata
 | |
| func copyFileWithMetadata(srcRoot, destRoot *os.File, subPath string, isDir bool) (fileInfo *winio.FileBasicInfo, err error) {
 | |
| 	src, err := safefile.OpenRelative(
 | |
| 		subPath,
 | |
| 		srcRoot,
 | |
| 		syscall.GENERIC_READ|winio.ACCESS_SYSTEM_SECURITY,
 | |
| 		syscall.FILE_SHARE_READ,
 | |
| 		safefile.FILE_OPEN,
 | |
| 		safefile.FILE_OPEN_REPARSE_POINT)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer src.Close()
 | |
| 	srcr := winio.NewBackupFileReader(src, true)
 | |
| 	defer srcr.Close()
 | |
| 
 | |
| 	fileInfo, err = winio.GetFileBasicInfo(src)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	extraFlags := uint32(0)
 | |
| 	if isDir {
 | |
| 		extraFlags |= safefile.FILE_DIRECTORY_FILE
 | |
| 	}
 | |
| 	dest, err := safefile.OpenRelative(
 | |
| 		subPath,
 | |
| 		destRoot,
 | |
| 		syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY,
 | |
| 		syscall.FILE_SHARE_READ,
 | |
| 		safefile.FILE_CREATE,
 | |
| 		extraFlags)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer dest.Close()
 | |
| 
 | |
| 	err = winio.SetFileBasicInfo(dest, fileInfo)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	destw := winio.NewBackupFileWriter(dest, true)
 | |
| 	defer func() {
 | |
| 		cerr := destw.Close()
 | |
| 		if err == nil {
 | |
| 			err = cerr
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	_, err = io.Copy(destw, srcr)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return fileInfo, nil
 | |
| }
 | |
| 
 | |
| // cloneTree clones a directory tree using hard links. It skips hard links for
 | |
| // the file names in the provided map and just copies those files.
 | |
| func cloneTree(srcRoot *os.File, destRoot *os.File, subPath string, mutatedFiles map[string]bool) error {
 | |
| 	var di []dirInfo
 | |
| 	err := safefile.EnsureNotReparsePointRelative(subPath, srcRoot)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	err = filepath.Walk(filepath.Join(srcRoot.Name(), subPath), func(srcFilePath string, info os.FileInfo, err error) error {
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		relPath, err := filepath.Rel(srcRoot.Name(), srcFilePath)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		fileAttributes := info.Sys().(*syscall.Win32FileAttributeData).FileAttributes
 | |
| 		// Directories, reparse points, and files that will be mutated during
 | |
| 		// utility VM import must be copied. All other files can be hard linked.
 | |
| 		isReparsePoint := fileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0
 | |
| 		// In go1.9, FileInfo.IsDir() returns false if the directory is also a symlink.
 | |
| 		// See: https://github.com/golang/go/commit/1989921aef60c83e6f9127a8448fb5ede10e9acc
 | |
| 		// Fixes the problem by checking syscall.FILE_ATTRIBUTE_DIRECTORY directly
 | |
| 		isDir := fileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0
 | |
| 
 | |
| 		if isDir || isReparsePoint || mutatedFiles[relPath] {
 | |
| 			fi, err := copyFileWithMetadata(srcRoot, destRoot, relPath, isDir)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			if isDir && !isReparsePoint {
 | |
| 				di = append(di, dirInfo{path: relPath, fileInfo: *fi})
 | |
| 			}
 | |
| 		} else {
 | |
| 			err = safefile.LinkRelative(relPath, srcRoot, relPath, destRoot)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return nil
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return reapplyDirectoryTimes(destRoot, di)
 | |
| }
 | |
| 
 | |
| func (w *legacyLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo) error {
 | |
| 	if err := w.reset(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if name == utilityVMPath {
 | |
| 		return w.initUtilityVM()
 | |
| 	}
 | |
| 
 | |
| 	name = filepath.Clean(name)
 | |
| 	if hasPathPrefix(name, utilityVMPath) {
 | |
| 		if !w.HasUtilityVM {
 | |
| 			return errors.New("missing UtilityVM directory")
 | |
| 		}
 | |
| 		if !hasPathPrefix(name, utilityVMFilesPath) && name != utilityVMFilesPath {
 | |
| 			return errors.New("invalid UtilityVM layer")
 | |
| 		}
 | |
| 		createDisposition := uint32(safefile.FILE_OPEN)
 | |
| 		if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
 | |
| 			st, err := safefile.LstatRelative(name, w.destRoot)
 | |
| 			if err != nil && !os.IsNotExist(err) {
 | |
| 				return err
 | |
| 			}
 | |
| 			if st != nil {
 | |
| 				// Delete the existing file/directory if it is not the same type as this directory.
 | |
| 				existingAttr := st.Sys().(*syscall.Win32FileAttributeData).FileAttributes
 | |
| 				if (uint32(fileInfo.FileAttributes)^existingAttr)&(syscall.FILE_ATTRIBUTE_DIRECTORY|syscall.FILE_ATTRIBUTE_REPARSE_POINT) != 0 {
 | |
| 					if err = safefile.RemoveAllRelative(name, w.destRoot); err != nil {
 | |
| 						return err
 | |
| 					}
 | |
| 					st = nil
 | |
| 				}
 | |
| 			}
 | |
| 			if st == nil {
 | |
| 				if err = safefile.MkdirRelative(name, w.destRoot); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 			if fileInfo.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 | |
| 				w.uvmDi = append(w.uvmDi, dirInfo{path: name, fileInfo: *fileInfo})
 | |
| 			}
 | |
| 		} else {
 | |
| 			// Overwrite any existing hard link.
 | |
| 			err := safefile.RemoveRelative(name, w.destRoot)
 | |
| 			if err != nil && !os.IsNotExist(err) {
 | |
| 				return err
 | |
| 			}
 | |
| 			createDisposition = safefile.FILE_CREATE
 | |
| 		}
 | |
| 
 | |
| 		f, err := safefile.OpenRelative(
 | |
| 			name,
 | |
| 			w.destRoot,
 | |
| 			syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY,
 | |
| 			syscall.FILE_SHARE_READ,
 | |
| 			createDisposition,
 | |
| 			safefile.FILE_OPEN_REPARSE_POINT,
 | |
| 		)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		defer func() {
 | |
| 			if f != nil {
 | |
| 				f.Close()
 | |
| 				safefile.RemoveRelative(name, w.destRoot)
 | |
| 			}
 | |
| 		}()
 | |
| 
 | |
| 		err = winio.SetFileBasicInfo(f, fileInfo)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		w.backupWriter = winio.NewBackupFileWriter(f, true)
 | |
| 		w.bufWriter.Reset(w.backupWriter)
 | |
| 		w.currentFile = f
 | |
| 		w.currentFileName = name
 | |
| 		w.currentFileRoot = w.destRoot
 | |
| 		w.addedFiles[name] = true
 | |
| 		f = nil
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	fname := name
 | |
| 	if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
 | |
| 		err := safefile.MkdirRelative(name, w.root)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		fname += ".$wcidirs$"
 | |
| 		w.currentIsDir = true
 | |
| 	}
 | |
| 
 | |
| 	f, err := safefile.OpenRelative(fname, w.root, syscall.GENERIC_READ|syscall.GENERIC_WRITE, syscall.FILE_SHARE_READ, safefile.FILE_CREATE, 0)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer func() {
 | |
| 		if f != nil {
 | |
| 			f.Close()
 | |
| 			safefile.RemoveRelative(fname, w.root)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	strippedFi := *fileInfo
 | |
| 	strippedFi.FileAttributes = 0
 | |
| 	err = winio.SetFileBasicInfo(f, &strippedFi)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if hasPathPrefix(name, hivesPath) {
 | |
| 		w.backupWriter = winio.NewBackupFileWriter(f, false)
 | |
| 		w.bufWriter.Reset(w.backupWriter)
 | |
| 	} else {
 | |
| 		w.bufWriter.Reset(f)
 | |
| 		// The file attributes are written before the stream.
 | |
| 		err = binary.Write(w.bufWriter, binary.LittleEndian, uint32(fileInfo.FileAttributes))
 | |
| 		if err != nil {
 | |
| 			w.bufWriter.Reset(ioutil.Discard)
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	w.currentFile = f
 | |
| 	w.currentFileName = name
 | |
| 	w.currentFileRoot = w.root
 | |
| 	w.addedFiles[name] = true
 | |
| 	f = nil
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (w *legacyLayerWriter) AddLink(name string, target string) error {
 | |
| 	if err := w.reset(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	target = filepath.Clean(target)
 | |
| 	var roots []*os.File
 | |
| 	if hasPathPrefix(target, filesPath) {
 | |
| 		// Look for cross-layer hard link targets in the parent layers, since
 | |
| 		// nothing is in the destination path yet.
 | |
| 		roots = w.parentRoots
 | |
| 	} else if hasPathPrefix(target, utilityVMFilesPath) {
 | |
| 		// Since the utility VM is fully cloned into the destination path
 | |
| 		// already, look for cross-layer hard link targets directly in the
 | |
| 		// destination path.
 | |
| 		roots = []*os.File{w.destRoot}
 | |
| 	}
 | |
| 
 | |
| 	if roots == nil || (!hasPathPrefix(name, filesPath) && !hasPathPrefix(name, utilityVMFilesPath)) {
 | |
| 		return errors.New("invalid hard link in layer")
 | |
| 	}
 | |
| 
 | |
| 	// Find to try the target of the link in a previously added file. If that
 | |
| 	// fails, search in parent layers.
 | |
| 	var selectedRoot *os.File
 | |
| 	if _, ok := w.addedFiles[target]; ok {
 | |
| 		selectedRoot = w.destRoot
 | |
| 	} else {
 | |
| 		for _, r := range roots {
 | |
| 			if _, err := safefile.LstatRelative(target, r); err != nil {
 | |
| 				if !os.IsNotExist(err) {
 | |
| 					return err
 | |
| 				}
 | |
| 			} else {
 | |
| 				selectedRoot = r
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if selectedRoot == nil {
 | |
| 			return fmt.Errorf("failed to find link target for '%s' -> '%s'", name, target)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// The link can't be written until after the ImportLayer call.
 | |
| 	w.PendingLinks = append(w.PendingLinks, pendingLink{
 | |
| 		Path:       name,
 | |
| 		Target:     target,
 | |
| 		TargetRoot: selectedRoot,
 | |
| 	})
 | |
| 	w.addedFiles[name] = true
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (w *legacyLayerWriter) Remove(name string) error {
 | |
| 	name = filepath.Clean(name)
 | |
| 	if hasPathPrefix(name, filesPath) {
 | |
| 		w.Tombstones = append(w.Tombstones, name)
 | |
| 	} else if hasPathPrefix(name, utilityVMFilesPath) {
 | |
| 		err := w.initUtilityVM()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		// Make sure the path exists; os.RemoveAll will not fail if the file is
 | |
| 		// already gone, and this needs to be a fatal error for diagnostics
 | |
| 		// purposes.
 | |
| 		if _, err := safefile.LstatRelative(name, w.destRoot); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		err = safefile.RemoveAllRelative(name, w.destRoot)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	} else {
 | |
| 		return fmt.Errorf("invalid tombstone %s", name)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (w *legacyLayerWriter) Write(b []byte) (int, error) {
 | |
| 	if w.backupWriter == nil && w.currentFile == nil {
 | |
| 		return 0, errors.New("closed")
 | |
| 	}
 | |
| 	return w.bufWriter.Write(b)
 | |
| }
 | |
| 
 | |
| func (w *legacyLayerWriter) Close() error {
 | |
| 	if err := w.reset(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := safefile.RemoveRelative("tombstones.txt", w.root); err != nil && !os.IsNotExist(err) {
 | |
| 		return err
 | |
| 	}
 | |
| 	for _, pd := range w.pendingDirs {
 | |
| 		err := safefile.MkdirRelative(pd.Path, pd.Root)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	if w.HasUtilityVM {
 | |
| 		err := reapplyDirectoryTimes(w.destRoot, w.uvmDi)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | 
