724 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			724 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package hcsshim
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"encoding/binary"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"syscall"
 | 
						|
 | 
						|
	"github.com/Microsoft/go-winio"
 | 
						|
)
 | 
						|
 | 
						|
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,
 | 
						|
}
 | 
						|
 | 
						|
func openFileOrDir(path string, mode uint32, createDisposition uint32) (file *os.File, err error) {
 | 
						|
	return winio.OpenForBackup(path, mode, syscall.FILE_SHARE_READ, createDisposition)
 | 
						|
}
 | 
						|
 | 
						|
func makeLongAbsPath(path string) (string, error) {
 | 
						|
	if strings.HasPrefix(path, `\\?\`) || strings.HasPrefix(path, `\\.\`) {
 | 
						|
		return path, nil
 | 
						|
	}
 | 
						|
	if !filepath.IsAbs(path) {
 | 
						|
		absPath, err := filepath.Abs(path)
 | 
						|
		if err != nil {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
		path = absPath
 | 
						|
	}
 | 
						|
	if strings.HasPrefix(path, `\\`) {
 | 
						|
		return `\\?\UNC\` + path[2:], nil
 | 
						|
	}
 | 
						|
	return `\\?\` + path, nil
 | 
						|
}
 | 
						|
 | 
						|
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("Files", 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 := makeLongAbsPath(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
 | 
						|
		}
 | 
						|
		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() && strings.HasPrefix(path, `Files\`) {
 | 
						|
		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 !strings.HasPrefix(path, `Files\`) {
 | 
						|
		size = fe.fi.Size()
 | 
						|
		r.backupReader = winio.NewBackupFileReader(f, false)
 | 
						|
		if path == "Hives" || path == "Files" {
 | 
						|
			// 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, `Hives\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 = uintptr(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) Close() error {
 | 
						|
	r.proceed <- false
 | 
						|
	<-r.result
 | 
						|
	r.reset()
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
type pendingLink struct {
 | 
						|
	Path, Target string
 | 
						|
}
 | 
						|
 | 
						|
type legacyLayerWriter struct {
 | 
						|
	root         string
 | 
						|
	parentRoots  []string
 | 
						|
	destRoot     string
 | 
						|
	currentFile  *os.File
 | 
						|
	backupWriter *winio.BackupFileWriter
 | 
						|
	tombstones   []string
 | 
						|
	pathFixed    bool
 | 
						|
	HasUtilityVM bool
 | 
						|
	uvmDi        []dirInfo
 | 
						|
	addedFiles   map[string]bool
 | 
						|
	PendingLinks []pendingLink
 | 
						|
}
 | 
						|
 | 
						|
// newLegacyLayerWriter returns a LayerWriter that can write the contaler layer
 | 
						|
// transport format to disk.
 | 
						|
func newLegacyLayerWriter(root string, parentRoots []string, destRoot string) *legacyLayerWriter {
 | 
						|
	return &legacyLayerWriter{
 | 
						|
		root:        root,
 | 
						|
		parentRoots: parentRoots,
 | 
						|
		destRoot:    destRoot,
 | 
						|
		addedFiles:  make(map[string]bool),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (w *legacyLayerWriter) init() error {
 | 
						|
	if !w.pathFixed {
 | 
						|
		path, err := makeLongAbsPath(w.root)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		for i, p := range w.parentRoots {
 | 
						|
			w.parentRoots[i], err = makeLongAbsPath(p)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		destPath, err := makeLongAbsPath(w.destRoot)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		w.root = path
 | 
						|
		w.destRoot = destPath
 | 
						|
		w.pathFixed = true
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (w *legacyLayerWriter) initUtilityVM() error {
 | 
						|
	if !w.HasUtilityVM {
 | 
						|
		err := os.Mkdir(filepath.Join(w.destRoot, `UtilityVM`), 0)
 | 
						|
		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(filepath.Join(w.parentRoots[0], `UtilityVM\Files`), filepath.Join(w.destRoot, `UtilityVM\Files`), 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() {
 | 
						|
	if w.backupWriter != nil {
 | 
						|
		w.backupWriter.Close()
 | 
						|
		w.backupWriter = nil
 | 
						|
	}
 | 
						|
	if w.currentFile != nil {
 | 
						|
		w.currentFile.Close()
 | 
						|
		w.currentFile = nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// copyFileWithMetadata copies a file using the backup/restore APIs in order to preserve metadata
 | 
						|
func copyFileWithMetadata(srcPath, destPath string, isDir bool) (fileInfo *winio.FileBasicInfo, err error) {
 | 
						|
	createDisposition := uint32(syscall.CREATE_NEW)
 | 
						|
	if isDir {
 | 
						|
		err = os.Mkdir(destPath, 0)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		createDisposition = syscall.OPEN_EXISTING
 | 
						|
	}
 | 
						|
 | 
						|
	src, err := openFileOrDir(srcPath, syscall.GENERIC_READ|winio.ACCESS_SYSTEM_SECURITY, syscall.OPEN_EXISTING)
 | 
						|
	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
 | 
						|
	}
 | 
						|
 | 
						|
	dest, err := openFileOrDir(destPath, syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY, createDisposition)
 | 
						|
	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(srcPath, destPath string, mutatedFiles map[string]bool) error {
 | 
						|
	var di []dirInfo
 | 
						|
	err := filepath.Walk(srcPath, func(srcFilePath string, info os.FileInfo, err error) error {
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		relPath, err := filepath.Rel(srcPath, srcFilePath)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		destFilePath := filepath.Join(destPath, relPath)
 | 
						|
 | 
						|
		// Directories, reparse points, and files that will be mutated during
 | 
						|
		// utility VM import must be copied. All other files can be hard linked.
 | 
						|
		isReparsePoint := info.Sys().(*syscall.Win32FileAttributeData).FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0
 | 
						|
		if info.IsDir() || isReparsePoint || mutatedFiles[relPath] {
 | 
						|
			fi, err := copyFileWithMetadata(srcFilePath, destFilePath, info.IsDir())
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if info.IsDir() && !isReparsePoint {
 | 
						|
				di = append(di, dirInfo{path: destFilePath, fileInfo: *fi})
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			err = os.Link(srcFilePath, destFilePath)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Don't recurse on reparse points.
 | 
						|
		if info.IsDir() && isReparsePoint {
 | 
						|
			return filepath.SkipDir
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return reapplyDirectoryTimes(di)
 | 
						|
}
 | 
						|
 | 
						|
func (w *legacyLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo) error {
 | 
						|
	w.reset()
 | 
						|
	err := w.init()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if name == `UtilityVM` {
 | 
						|
		return w.initUtilityVM()
 | 
						|
	}
 | 
						|
 | 
						|
	if strings.HasPrefix(name, `UtilityVM\`) {
 | 
						|
		if !w.HasUtilityVM {
 | 
						|
			return errors.New("missing UtilityVM directory")
 | 
						|
		}
 | 
						|
		if !strings.HasPrefix(name, `UtilityVM\Files\`) && name != `UtilityVM\Files` {
 | 
						|
			return errors.New("invalid UtilityVM layer")
 | 
						|
		}
 | 
						|
		path := filepath.Join(w.destRoot, name)
 | 
						|
		createDisposition := uint32(syscall.OPEN_EXISTING)
 | 
						|
		if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
 | 
						|
			st, err := os.Lstat(path)
 | 
						|
			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 = os.RemoveAll(path); err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
					st = nil
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if st == nil {
 | 
						|
				if err = os.Mkdir(path, 0); err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if fileInfo.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 | 
						|
				w.uvmDi = append(w.uvmDi, dirInfo{path: path, fileInfo: *fileInfo})
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			// Overwrite any existing hard link.
 | 
						|
			err = os.Remove(path)
 | 
						|
			if err != nil && !os.IsNotExist(err) {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			createDisposition = syscall.CREATE_NEW
 | 
						|
		}
 | 
						|
 | 
						|
		f, err := openFileOrDir(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY, createDisposition)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		defer func() {
 | 
						|
			if f != nil {
 | 
						|
				f.Close()
 | 
						|
				os.Remove(path)
 | 
						|
			}
 | 
						|
		}()
 | 
						|
 | 
						|
		err = winio.SetFileBasicInfo(f, fileInfo)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		w.backupWriter = winio.NewBackupFileWriter(f, true)
 | 
						|
		w.currentFile = f
 | 
						|
		w.addedFiles[name] = true
 | 
						|
		f = nil
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	path := filepath.Join(w.root, name)
 | 
						|
	if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
 | 
						|
		err := os.Mkdir(path, 0)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		path += ".$wcidirs$"
 | 
						|
	}
 | 
						|
 | 
						|
	f, err := openFileOrDir(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, syscall.CREATE_NEW)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	defer func() {
 | 
						|
		if f != nil {
 | 
						|
			f.Close()
 | 
						|
			os.Remove(path)
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	strippedFi := *fileInfo
 | 
						|
	strippedFi.FileAttributes = 0
 | 
						|
	err = winio.SetFileBasicInfo(f, &strippedFi)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if strings.HasPrefix(name, `Hives\`) {
 | 
						|
		w.backupWriter = winio.NewBackupFileWriter(f, false)
 | 
						|
	} else {
 | 
						|
		// The file attributes are written before the stream.
 | 
						|
		err = binary.Write(f, binary.LittleEndian, uint32(fileInfo.FileAttributes))
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	w.currentFile = f
 | 
						|
	w.addedFiles[name] = true
 | 
						|
	f = nil
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (w *legacyLayerWriter) AddLink(name string, target string) error {
 | 
						|
	w.reset()
 | 
						|
	err := w.init()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	var requiredPrefix string
 | 
						|
	var roots []string
 | 
						|
	if prefix := `Files\`; strings.HasPrefix(name, prefix) {
 | 
						|
		requiredPrefix = prefix
 | 
						|
		// Look for cross-layer hard link targets in the parent layers, since
 | 
						|
		// nothing is in the destination path yet.
 | 
						|
		roots = w.parentRoots
 | 
						|
	} else if prefix := `UtilityVM\Files\`; strings.HasPrefix(name, prefix) {
 | 
						|
		requiredPrefix = prefix
 | 
						|
		// 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 = []string{w.destRoot}
 | 
						|
	}
 | 
						|
 | 
						|
	if requiredPrefix == "" || !strings.HasPrefix(target, requiredPrefix) {
 | 
						|
		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 string
 | 
						|
	if _, ok := w.addedFiles[target]; ok {
 | 
						|
		selectedRoot = w.destRoot
 | 
						|
	} else {
 | 
						|
		for _, r := range roots {
 | 
						|
			if _, err = os.Lstat(filepath.Join(r, target)); err != nil {
 | 
						|
				if !os.IsNotExist(err) {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				selectedRoot = r
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if selectedRoot == "" {
 | 
						|
			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:   filepath.Join(w.destRoot, name),
 | 
						|
		Target: filepath.Join(selectedRoot, target),
 | 
						|
	})
 | 
						|
	w.addedFiles[name] = true
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (w *legacyLayerWriter) Remove(name string) error {
 | 
						|
	if strings.HasPrefix(name, `Files\`) {
 | 
						|
		w.tombstones = append(w.tombstones, name[len(`Files\`):])
 | 
						|
	} else if strings.HasPrefix(name, `UtilityVM\Files\`) {
 | 
						|
		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.
 | 
						|
		path := filepath.Join(w.destRoot, name)
 | 
						|
		if _, err := os.Lstat(path); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = os.RemoveAll(path)
 | 
						|
		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 {
 | 
						|
		if w.currentFile == nil {
 | 
						|
			return 0, errors.New("closed")
 | 
						|
		}
 | 
						|
		return w.currentFile.Write(b)
 | 
						|
	}
 | 
						|
	return w.backupWriter.Write(b)
 | 
						|
}
 | 
						|
 | 
						|
func (w *legacyLayerWriter) Close() error {
 | 
						|
	w.reset()
 | 
						|
	err := w.init()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	tf, err := os.Create(filepath.Join(w.root, "tombstones.txt"))
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	defer tf.Close()
 | 
						|
	_, err = tf.Write([]byte("\xef\xbb\xbfVersion 1.0\n"))
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	for _, t := range w.tombstones {
 | 
						|
		_, err = tf.Write([]byte(filepath.Join(`\`, t) + "\n"))
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if w.HasUtilityVM {
 | 
						|
		err = reapplyDirectoryTimes(w.uvmDi)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 |