mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
This tag of hcsshim brings in a couple welcome features/improvements. One being exposing a way to query for hns endpoint statistics (Packets received/sent etc.). This tag also contains some optimizations for querying whether a certain HCN feature is supported, which is a common workflow in kube-proxy on Windows. The first result from querying HCN is now cached so further calls can skip the hcn query as well as the version range parsing that was performed. This also gets rid of some redundant logs that used to hit everytime the version range parsing occurred. The Go-winio dep bump, and all of the ctrd deps are transitive only. Nothing new is needed/intended to be used. Signed-off-by: Daniel Canter <dcanter@microsoft.com>
812 lines
20 KiB
Go
812 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"
|
|
"github.com/Microsoft/hcsshim/internal/winapi"
|
|
)
|
|
|
|
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
|
|
changedDi []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,
|
|
winapi.FILE_OPEN,
|
|
winapi.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 |= winapi.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,
|
|
winapi.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 {
|
|
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()
|
|
}
|
|
|
|
if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
|
|
w.changedDi = append(w.changedDi, dirInfo{path: name, fileInfo: *fileInfo})
|
|
}
|
|
|
|
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(winapi.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
|
|
}
|
|
}
|
|
} else {
|
|
// Overwrite any existing hard link.
|
|
err := safefile.RemoveRelative(name, w.destRoot)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
createDisposition = winapi.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,
|
|
winapi.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, winapi.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
|
|
}
|
|
}
|
|
return nil
|
|
}
|