...

Source file src/github.com/Microsoft/hcsshim/internal/wclayer/legacy.go

Documentation: github.com/Microsoft/hcsshim/internal/wclayer

     1  //go:build windows
     2  
     3  package wclayer
     4  
     5  import (
     6  	"bufio"
     7  	"encoding/binary"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"syscall"
    15  
    16  	"github.com/Microsoft/go-winio"
    17  	"github.com/Microsoft/hcsshim/internal/longpath"
    18  	"github.com/Microsoft/hcsshim/internal/safefile"
    19  	"github.com/Microsoft/hcsshim/internal/winapi"
    20  )
    21  
    22  var errorIterationCanceled = errors.New("")
    23  
    24  var mutatedUtilityVMFiles = map[string]bool{
    25  	`EFI\Microsoft\Boot\BCD`:      true,
    26  	`EFI\Microsoft\Boot\BCD.LOG`:  true,
    27  	`EFI\Microsoft\Boot\BCD.LOG1`: true,
    28  	`EFI\Microsoft\Boot\BCD.LOG2`: true,
    29  }
    30  
    31  const (
    32  	filesPath          = `Files`
    33  	hivesPath          = `Hives`
    34  	utilityVMPath      = `UtilityVM`
    35  	utilityVMFilesPath = `UtilityVM\Files`
    36  )
    37  
    38  func openFileOrDir(path string, mode uint32, createDisposition uint32) (file *os.File, err error) {
    39  	return winio.OpenForBackup(path, mode, syscall.FILE_SHARE_READ, createDisposition)
    40  }
    41  
    42  func hasPathPrefix(p, prefix string) bool {
    43  	return strings.HasPrefix(p, prefix) && len(p) > len(prefix) && p[len(prefix)] == '\\'
    44  }
    45  
    46  type fileEntry struct {
    47  	path string
    48  	fi   os.FileInfo
    49  	err  error
    50  }
    51  
    52  type legacyLayerReader struct {
    53  	root         string
    54  	result       chan *fileEntry
    55  	proceed      chan bool
    56  	currentFile  *os.File
    57  	backupReader *winio.BackupFileReader
    58  }
    59  
    60  // newLegacyLayerReader returns a new LayerReader that can read the Windows
    61  // container layer transport format from disk.
    62  func newLegacyLayerReader(root string) *legacyLayerReader {
    63  	r := &legacyLayerReader{
    64  		root:    root,
    65  		result:  make(chan *fileEntry),
    66  		proceed: make(chan bool),
    67  	}
    68  	go r.walk()
    69  	return r
    70  }
    71  
    72  func readTombstones(path string) (map[string]([]string), error) {
    73  	tf, err := os.Open(filepath.Join(path, "tombstones.txt"))
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	defer tf.Close()
    78  	s := bufio.NewScanner(tf)
    79  	if !s.Scan() || s.Text() != "\xef\xbb\xbfVersion 1.0" {
    80  		return nil, errors.New("invalid tombstones file")
    81  	}
    82  
    83  	ts := make(map[string]([]string))
    84  	for s.Scan() {
    85  		t := filepath.Join(filesPath, s.Text()[1:]) // skip leading `\`
    86  		dir := filepath.Dir(t)
    87  		ts[dir] = append(ts[dir], t)
    88  	}
    89  	if err = s.Err(); err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	return ts, nil
    94  }
    95  
    96  func (r *legacyLayerReader) walkUntilCancelled() error {
    97  	root, err := longpath.LongAbs(r.root)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	r.root = root
   103  	ts, err := readTombstones(r.root)
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	err = filepath.Walk(r.root, func(path string, info os.FileInfo, err error) error {
   109  		if err != nil {
   110  			return err
   111  		}
   112  
   113  		// Indirect fix for https://github.com/moby/moby/issues/32838#issuecomment-343610048.
   114  		// Handle failure from what may be a golang bug in the conversion of
   115  		// UTF16 to UTF8 in files which are left in the recycle bin. Os.Lstat
   116  		// which is called by filepath.Walk will fail when a filename contains
   117  		// unicode characters. Skip the recycle bin regardless which is goodness.
   118  		if strings.EqualFold(path, filepath.Join(r.root, `Files\$Recycle.Bin`)) && info.IsDir() {
   119  			return filepath.SkipDir
   120  		}
   121  
   122  		if path == r.root || path == filepath.Join(r.root, "tombstones.txt") || strings.HasSuffix(path, ".$wcidirs$") {
   123  			return nil
   124  		}
   125  
   126  		r.result <- &fileEntry{path, info, nil}
   127  		if !<-r.proceed {
   128  			return errorIterationCanceled
   129  		}
   130  
   131  		// List all the tombstones.
   132  		if info.IsDir() {
   133  			relPath, err := filepath.Rel(r.root, path)
   134  			if err != nil {
   135  				return err
   136  			}
   137  			if dts, ok := ts[relPath]; ok {
   138  				for _, t := range dts {
   139  					r.result <- &fileEntry{filepath.Join(r.root, t), nil, nil}
   140  					if !<-r.proceed {
   141  						return errorIterationCanceled
   142  					}
   143  				}
   144  			}
   145  		}
   146  		return nil
   147  	})
   148  	if err == errorIterationCanceled {
   149  		return nil
   150  	}
   151  	if err == nil {
   152  		return io.EOF
   153  	}
   154  	return err
   155  }
   156  
   157  func (r *legacyLayerReader) walk() {
   158  	defer close(r.result)
   159  	if !<-r.proceed {
   160  		return
   161  	}
   162  
   163  	err := r.walkUntilCancelled()
   164  	if err != nil {
   165  		for {
   166  			r.result <- &fileEntry{err: err}
   167  			if !<-r.proceed {
   168  				return
   169  			}
   170  		}
   171  	}
   172  }
   173  
   174  func (r *legacyLayerReader) reset() {
   175  	if r.backupReader != nil {
   176  		r.backupReader.Close()
   177  		r.backupReader = nil
   178  	}
   179  	if r.currentFile != nil {
   180  		r.currentFile.Close()
   181  		r.currentFile = nil
   182  	}
   183  }
   184  
   185  func findBackupStreamSize(r io.Reader) (int64, error) {
   186  	br := winio.NewBackupStreamReader(r)
   187  	for {
   188  		hdr, err := br.Next()
   189  		if err != nil {
   190  			if err == io.EOF {
   191  				err = nil
   192  			}
   193  			return 0, err
   194  		}
   195  		if hdr.Id == winio.BackupData {
   196  			return hdr.Size, nil
   197  		}
   198  	}
   199  }
   200  
   201  func (r *legacyLayerReader) Next() (path string, size int64, fileInfo *winio.FileBasicInfo, err error) {
   202  	r.reset()
   203  	r.proceed <- true
   204  	fe := <-r.result
   205  	if fe == nil {
   206  		err = errors.New("LegacyLayerReader closed")
   207  		return
   208  	}
   209  	if fe.err != nil {
   210  		err = fe.err
   211  		return
   212  	}
   213  
   214  	path, err = filepath.Rel(r.root, fe.path)
   215  	if err != nil {
   216  		return
   217  	}
   218  
   219  	if fe.fi == nil {
   220  		// This is a tombstone. Return a nil fileInfo.
   221  		return
   222  	}
   223  
   224  	if fe.fi.IsDir() && hasPathPrefix(path, filesPath) {
   225  		fe.path += ".$wcidirs$"
   226  	}
   227  
   228  	f, err := openFileOrDir(fe.path, syscall.GENERIC_READ, syscall.OPEN_EXISTING)
   229  	if err != nil {
   230  		return
   231  	}
   232  	defer func() {
   233  		if f != nil {
   234  			f.Close()
   235  		}
   236  	}()
   237  
   238  	fileInfo, err = winio.GetFileBasicInfo(f)
   239  	if err != nil {
   240  		return
   241  	}
   242  
   243  	if !hasPathPrefix(path, filesPath) {
   244  		size = fe.fi.Size()
   245  		r.backupReader = winio.NewBackupFileReader(f, false)
   246  		if path == hivesPath || path == filesPath {
   247  			// The Hives directory has a non-deterministic file time because of the
   248  			// nature of the import process. Use the times from System_Delta.
   249  			var g *os.File
   250  			g, err = os.Open(filepath.Join(r.root, hivesPath, `System_Delta`))
   251  			if err != nil {
   252  				return
   253  			}
   254  			attr := fileInfo.FileAttributes
   255  			fileInfo, err = winio.GetFileBasicInfo(g)
   256  			g.Close()
   257  			if err != nil {
   258  				return
   259  			}
   260  			fileInfo.FileAttributes = attr
   261  		}
   262  
   263  		// The creation time and access time get reset for files outside of the Files path.
   264  		fileInfo.CreationTime = fileInfo.LastWriteTime
   265  		fileInfo.LastAccessTime = fileInfo.LastWriteTime
   266  	} else {
   267  		// The file attributes are written before the backup stream.
   268  		var attr uint32
   269  		err = binary.Read(f, binary.LittleEndian, &attr)
   270  		if err != nil {
   271  			return
   272  		}
   273  		fileInfo.FileAttributes = attr
   274  		beginning := int64(4)
   275  
   276  		// Find the accurate file size.
   277  		if !fe.fi.IsDir() {
   278  			size, err = findBackupStreamSize(f)
   279  			if err != nil {
   280  				err = &os.PathError{Op: "findBackupStreamSize", Path: fe.path, Err: err}
   281  				return
   282  			}
   283  		}
   284  
   285  		// Return back to the beginning of the backup stream.
   286  		_, err = f.Seek(beginning, 0)
   287  		if err != nil {
   288  			return
   289  		}
   290  	}
   291  
   292  	r.currentFile = f
   293  	f = nil
   294  	return
   295  }
   296  
   297  func (r *legacyLayerReader) LinkInfo() (uint32, *winio.FileIDInfo, error) {
   298  	fileStandardInfo, err := winio.GetFileStandardInfo(r.currentFile)
   299  	if err != nil {
   300  		return 0, nil, err
   301  	}
   302  	fileIDInfo, err := winio.GetFileID(r.currentFile)
   303  	if err != nil {
   304  		return 0, nil, err
   305  	}
   306  	return fileStandardInfo.NumberOfLinks, fileIDInfo, nil
   307  }
   308  
   309  func (r *legacyLayerReader) Read(b []byte) (int, error) {
   310  	if r.backupReader == nil {
   311  		if r.currentFile == nil {
   312  			return 0, io.EOF
   313  		}
   314  		return r.currentFile.Read(b)
   315  	}
   316  	return r.backupReader.Read(b)
   317  }
   318  
   319  func (r *legacyLayerReader) Seek(offset int64, whence int) (int64, error) {
   320  	if r.backupReader == nil {
   321  		if r.currentFile == nil {
   322  			return 0, errors.New("no current file")
   323  		}
   324  		return r.currentFile.Seek(offset, whence)
   325  	}
   326  	return 0, errors.New("seek not supported on this stream")
   327  }
   328  
   329  func (r *legacyLayerReader) Close() error {
   330  	r.proceed <- false
   331  	<-r.result
   332  	r.reset()
   333  	return nil
   334  }
   335  
   336  type pendingLink struct {
   337  	Path, Target string
   338  	TargetRoot   *os.File
   339  }
   340  
   341  type pendingDir struct {
   342  	Path string
   343  	Root *os.File
   344  }
   345  
   346  type legacyLayerWriter struct {
   347  	root            *os.File
   348  	destRoot        *os.File
   349  	parentRoots     []*os.File
   350  	currentFile     *os.File
   351  	bufWriter       *bufio.Writer
   352  	currentFileName string
   353  	currentFileRoot *os.File
   354  	backupWriter    *winio.BackupFileWriter
   355  	Tombstones      []string
   356  	HasUtilityVM    bool
   357  	changedDi       []dirInfo
   358  	addedFiles      map[string]bool
   359  	PendingLinks    []pendingLink
   360  	pendingDirs     []pendingDir
   361  	currentIsDir    bool
   362  }
   363  
   364  // newLegacyLayerWriter returns a LayerWriter that can write the container layer
   365  // transport format to disk.
   366  func newLegacyLayerWriter(root string, parentRoots []string, destRoot string) (w *legacyLayerWriter, err error) {
   367  	w = &legacyLayerWriter{
   368  		addedFiles: make(map[string]bool),
   369  	}
   370  	defer func() {
   371  		if err != nil {
   372  			w.CloseRoots()
   373  			w = nil
   374  		}
   375  	}()
   376  	w.root, err = safefile.OpenRoot(root)
   377  	if err != nil {
   378  		return
   379  	}
   380  	w.destRoot, err = safefile.OpenRoot(destRoot)
   381  	if err != nil {
   382  		return
   383  	}
   384  	for _, r := range parentRoots {
   385  		f, err := safefile.OpenRoot(r)
   386  		if err != nil {
   387  			return w, err
   388  		}
   389  		w.parentRoots = append(w.parentRoots, f)
   390  	}
   391  	w.bufWriter = bufio.NewWriterSize(io.Discard, 65536)
   392  	return
   393  }
   394  
   395  func (w *legacyLayerWriter) CloseRoots() {
   396  	if w.root != nil {
   397  		w.root.Close()
   398  		w.root = nil
   399  	}
   400  	if w.destRoot != nil {
   401  		w.destRoot.Close()
   402  		w.destRoot = nil
   403  	}
   404  	for i := range w.parentRoots {
   405  		_ = w.parentRoots[i].Close()
   406  	}
   407  	w.parentRoots = nil
   408  }
   409  
   410  func (w *legacyLayerWriter) initUtilityVM() error {
   411  	if !w.HasUtilityVM {
   412  		err := safefile.MkdirRelative(utilityVMPath, w.destRoot)
   413  		if err != nil {
   414  			return err
   415  		}
   416  		// Server 2016 does not support multiple layers for the utility VM, so
   417  		// clone the utility VM from the parent layer into this layer. Use hard
   418  		// links to avoid unnecessary copying, since most of the files are
   419  		// immutable.
   420  		err = cloneTree(w.parentRoots[0], w.destRoot, utilityVMFilesPath, mutatedUtilityVMFiles)
   421  		if err != nil {
   422  			return fmt.Errorf("cloning the parent utility VM image failed: %s", err)
   423  		}
   424  		w.HasUtilityVM = true
   425  	}
   426  	return nil
   427  }
   428  
   429  func (w *legacyLayerWriter) reset() error {
   430  	err := w.bufWriter.Flush()
   431  	if err != nil {
   432  		return err
   433  	}
   434  	w.bufWriter.Reset(io.Discard)
   435  	if w.currentIsDir {
   436  		r := w.currentFile
   437  		br := winio.NewBackupStreamReader(r)
   438  		// Seek to the beginning of the backup stream, skipping the fileattrs
   439  		if _, err := r.Seek(4, io.SeekStart); err != nil {
   440  			return err
   441  		}
   442  
   443  		for {
   444  			bhdr, err := br.Next()
   445  			if err == io.EOF {
   446  				// end of backupstream data
   447  				break
   448  			}
   449  			if err != nil {
   450  				return err
   451  			}
   452  			switch bhdr.Id {
   453  			case winio.BackupReparseData:
   454  				// The current file is a `.$wcidirs$` metadata file that
   455  				// describes a directory reparse point. Delete the placeholder
   456  				// directory to prevent future files being added into the
   457  				// destination of the reparse point during the ImportLayer call
   458  				if err := safefile.RemoveRelative(w.currentFileName, w.currentFileRoot); err != nil {
   459  					return err
   460  				}
   461  				w.pendingDirs = append(w.pendingDirs, pendingDir{Path: w.currentFileName, Root: w.currentFileRoot})
   462  			default:
   463  				// ignore all other stream types, as we only care about directory reparse points
   464  			}
   465  		}
   466  		w.currentIsDir = false
   467  	}
   468  	if w.backupWriter != nil {
   469  		w.backupWriter.Close()
   470  		w.backupWriter = nil
   471  	}
   472  	if w.currentFile != nil {
   473  		w.currentFile.Close()
   474  		w.currentFile = nil
   475  		w.currentFileName = ""
   476  		w.currentFileRoot = nil
   477  	}
   478  	return nil
   479  }
   480  
   481  // copyFileWithMetadata copies a file using the backup/restore APIs in order to preserve metadata
   482  func copyFileWithMetadata(srcRoot, destRoot *os.File, subPath string, isDir bool) (fileInfo *winio.FileBasicInfo, err error) {
   483  	src, err := safefile.OpenRelative(
   484  		subPath,
   485  		srcRoot,
   486  		syscall.GENERIC_READ|winio.ACCESS_SYSTEM_SECURITY,
   487  		syscall.FILE_SHARE_READ,
   488  		winapi.FILE_OPEN,
   489  		winapi.FILE_OPEN_REPARSE_POINT)
   490  	if err != nil {
   491  		return nil, err
   492  	}
   493  	defer src.Close()
   494  	srcr := winio.NewBackupFileReader(src, true)
   495  	defer srcr.Close()
   496  
   497  	fileInfo, err = winio.GetFileBasicInfo(src)
   498  	if err != nil {
   499  		return nil, err
   500  	}
   501  
   502  	extraFlags := uint32(0)
   503  	if isDir {
   504  		extraFlags |= winapi.FILE_DIRECTORY_FILE
   505  	}
   506  	dest, err := safefile.OpenRelative(
   507  		subPath,
   508  		destRoot,
   509  		syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY,
   510  		syscall.FILE_SHARE_READ,
   511  		winapi.FILE_CREATE,
   512  		extraFlags)
   513  	if err != nil {
   514  		return nil, err
   515  	}
   516  	defer dest.Close()
   517  
   518  	err = winio.SetFileBasicInfo(dest, fileInfo)
   519  	if err != nil {
   520  		return nil, err
   521  	}
   522  
   523  	destw := winio.NewBackupFileWriter(dest, true)
   524  	defer func() {
   525  		cerr := destw.Close()
   526  		if err == nil {
   527  			err = cerr
   528  		}
   529  	}()
   530  
   531  	_, err = io.Copy(destw, srcr)
   532  	if err != nil {
   533  		return nil, err
   534  	}
   535  
   536  	return fileInfo, nil
   537  }
   538  
   539  // cloneTree clones a directory tree using hard links. It skips hard links for
   540  // the file names in the provided map and just copies those files.
   541  func cloneTree(srcRoot *os.File, destRoot *os.File, subPath string, mutatedFiles map[string]bool) error {
   542  	var di []dirInfo
   543  	err := safefile.EnsureNotReparsePointRelative(subPath, srcRoot)
   544  	if err != nil {
   545  		return err
   546  	}
   547  	err = filepath.Walk(filepath.Join(srcRoot.Name(), subPath), func(srcFilePath string, info os.FileInfo, err error) error {
   548  		if err != nil {
   549  			return err
   550  		}
   551  
   552  		relPath, err := filepath.Rel(srcRoot.Name(), srcFilePath)
   553  		if err != nil {
   554  			return err
   555  		}
   556  
   557  		fileAttributes := info.Sys().(*syscall.Win32FileAttributeData).FileAttributes
   558  		// Directories, reparse points, and files that will be mutated during
   559  		// utility VM import must be copied. All other files can be hard linked.
   560  		isReparsePoint := fileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0
   561  		// In go1.9, FileInfo.IsDir() returns false if the directory is also a symlink.
   562  		// See: https://github.com/golang/go/commit/1989921aef60c83e6f9127a8448fb5ede10e9acc
   563  		// Fixes the problem by checking syscall.FILE_ATTRIBUTE_DIRECTORY directly
   564  		isDir := fileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0
   565  
   566  		if isDir || isReparsePoint || mutatedFiles[relPath] {
   567  			fi, err := copyFileWithMetadata(srcRoot, destRoot, relPath, isDir)
   568  			if err != nil {
   569  				return err
   570  			}
   571  			if isDir {
   572  				di = append(di, dirInfo{path: relPath, fileInfo: *fi})
   573  			}
   574  		} else {
   575  			err = safefile.LinkRelative(relPath, srcRoot, relPath, destRoot)
   576  			if err != nil {
   577  				return err
   578  			}
   579  		}
   580  
   581  		return nil
   582  	})
   583  	if err != nil {
   584  		return err
   585  	}
   586  
   587  	return reapplyDirectoryTimes(destRoot, di)
   588  }
   589  
   590  func (w *legacyLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo) error {
   591  	if err := w.reset(); err != nil {
   592  		return err
   593  	}
   594  
   595  	if name == utilityVMPath {
   596  		return w.initUtilityVM()
   597  	}
   598  
   599  	if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
   600  		w.changedDi = append(w.changedDi, dirInfo{path: name, fileInfo: *fileInfo})
   601  	}
   602  
   603  	name = filepath.Clean(name)
   604  	if hasPathPrefix(name, utilityVMPath) {
   605  		if !w.HasUtilityVM {
   606  			return errors.New("missing UtilityVM directory")
   607  		}
   608  		if !hasPathPrefix(name, utilityVMFilesPath) && name != utilityVMFilesPath {
   609  			return errors.New("invalid UtilityVM layer")
   610  		}
   611  		createDisposition := uint32(winapi.FILE_OPEN)
   612  		if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
   613  			st, err := safefile.LstatRelative(name, w.destRoot)
   614  			if err != nil && !os.IsNotExist(err) {
   615  				return err
   616  			}
   617  			if st != nil {
   618  				// Delete the existing file/directory if it is not the same type as this directory.
   619  				existingAttr := st.Sys().(*syscall.Win32FileAttributeData).FileAttributes
   620  				if (uint32(fileInfo.FileAttributes)^existingAttr)&(syscall.FILE_ATTRIBUTE_DIRECTORY|syscall.FILE_ATTRIBUTE_REPARSE_POINT) != 0 {
   621  					if err = safefile.RemoveAllRelative(name, w.destRoot); err != nil {
   622  						return err
   623  					}
   624  					st = nil
   625  				}
   626  			}
   627  			if st == nil {
   628  				if err = safefile.MkdirRelative(name, w.destRoot); err != nil {
   629  					return err
   630  				}
   631  			}
   632  		} else {
   633  			// Overwrite any existing hard link.
   634  			err := safefile.RemoveRelative(name, w.destRoot)
   635  			if err != nil && !os.IsNotExist(err) {
   636  				return err
   637  			}
   638  			createDisposition = winapi.FILE_CREATE
   639  		}
   640  
   641  		f, err := safefile.OpenRelative(
   642  			name,
   643  			w.destRoot,
   644  			syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY,
   645  			syscall.FILE_SHARE_READ,
   646  			createDisposition,
   647  			winapi.FILE_OPEN_REPARSE_POINT,
   648  		)
   649  		if err != nil {
   650  			return err
   651  		}
   652  		defer func() {
   653  			if f != nil {
   654  				f.Close()
   655  				_ = safefile.RemoveRelative(name, w.destRoot)
   656  			}
   657  		}()
   658  
   659  		err = winio.SetFileBasicInfo(f, fileInfo)
   660  		if err != nil {
   661  			return err
   662  		}
   663  
   664  		w.backupWriter = winio.NewBackupFileWriter(f, true)
   665  		w.bufWriter.Reset(w.backupWriter)
   666  		w.currentFile = f
   667  		w.currentFileName = name
   668  		w.currentFileRoot = w.destRoot
   669  		w.addedFiles[name] = true
   670  		f = nil
   671  		return nil
   672  	}
   673  
   674  	fname := name
   675  	if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
   676  		err := safefile.MkdirRelative(name, w.root)
   677  		if err != nil {
   678  			return err
   679  		}
   680  		fname += ".$wcidirs$"
   681  		w.currentIsDir = true
   682  	}
   683  
   684  	f, err := safefile.OpenRelative(fname, w.root, syscall.GENERIC_READ|syscall.GENERIC_WRITE, syscall.FILE_SHARE_READ, winapi.FILE_CREATE, 0)
   685  	if err != nil {
   686  		return err
   687  	}
   688  	defer func() {
   689  		if f != nil {
   690  			f.Close()
   691  			_ = safefile.RemoveRelative(fname, w.root)
   692  		}
   693  	}()
   694  
   695  	strippedFi := *fileInfo
   696  	strippedFi.FileAttributes = 0
   697  	err = winio.SetFileBasicInfo(f, &strippedFi)
   698  	if err != nil {
   699  		return err
   700  	}
   701  
   702  	if hasPathPrefix(name, hivesPath) {
   703  		w.backupWriter = winio.NewBackupFileWriter(f, false)
   704  		w.bufWriter.Reset(w.backupWriter)
   705  	} else {
   706  		w.bufWriter.Reset(f)
   707  		// The file attributes are written before the stream.
   708  		err = binary.Write(w.bufWriter, binary.LittleEndian, uint32(fileInfo.FileAttributes))
   709  		if err != nil {
   710  			w.bufWriter.Reset(io.Discard)
   711  			return err
   712  		}
   713  	}
   714  
   715  	w.currentFile = f
   716  	w.currentFileName = name
   717  	w.currentFileRoot = w.root
   718  	w.addedFiles[name] = true
   719  	f = nil
   720  	return nil
   721  }
   722  
   723  func (w *legacyLayerWriter) AddLink(name string, target string) error {
   724  	if err := w.reset(); err != nil {
   725  		return err
   726  	}
   727  
   728  	target = filepath.Clean(target)
   729  	var roots []*os.File
   730  	if hasPathPrefix(target, filesPath) {
   731  		// Look for cross-layer hard link targets in the parent layers, since
   732  		// nothing is in the destination path yet.
   733  		roots = w.parentRoots
   734  	} else if hasPathPrefix(target, utilityVMFilesPath) {
   735  		// Since the utility VM is fully cloned into the destination path
   736  		// already, look for cross-layer hard link targets directly in the
   737  		// destination path.
   738  		roots = []*os.File{w.destRoot}
   739  	}
   740  
   741  	if roots == nil || (!hasPathPrefix(name, filesPath) && !hasPathPrefix(name, utilityVMFilesPath)) {
   742  		return errors.New("invalid hard link in layer")
   743  	}
   744  
   745  	// Try to find the target of the link in a previously added file. If that
   746  	// fails, search in parent layers.
   747  	var selectedRoot *os.File
   748  	if _, ok := w.addedFiles[target]; ok {
   749  		selectedRoot = w.destRoot
   750  	} else {
   751  		for _, r := range roots {
   752  			if _, err := safefile.LstatRelative(target, r); err != nil {
   753  				if !os.IsNotExist(err) {
   754  					return err
   755  				}
   756  			} else {
   757  				selectedRoot = r
   758  				break
   759  			}
   760  		}
   761  		if selectedRoot == nil {
   762  			return fmt.Errorf("failed to find link target for '%s' -> '%s'", name, target)
   763  		}
   764  	}
   765  
   766  	// The link can't be written until after the ImportLayer call.
   767  	w.PendingLinks = append(w.PendingLinks, pendingLink{
   768  		Path:       name,
   769  		Target:     target,
   770  		TargetRoot: selectedRoot,
   771  	})
   772  	w.addedFiles[name] = true
   773  	return nil
   774  }
   775  
   776  func (w *legacyLayerWriter) Remove(name string) error {
   777  	name = filepath.Clean(name)
   778  	if hasPathPrefix(name, filesPath) {
   779  		w.Tombstones = append(w.Tombstones, name)
   780  	} else if hasPathPrefix(name, utilityVMFilesPath) {
   781  		err := w.initUtilityVM()
   782  		if err != nil {
   783  			return err
   784  		}
   785  		// Make sure the path exists; os.RemoveAll will not fail if the file is
   786  		// already gone, and this needs to be a fatal error for diagnostics
   787  		// purposes.
   788  		if _, err := safefile.LstatRelative(name, w.destRoot); err != nil {
   789  			return err
   790  		}
   791  		err = safefile.RemoveAllRelative(name, w.destRoot)
   792  		if err != nil {
   793  			return err
   794  		}
   795  	} else {
   796  		return fmt.Errorf("invalid tombstone %s", name)
   797  	}
   798  
   799  	return nil
   800  }
   801  
   802  func (w *legacyLayerWriter) Write(b []byte) (int, error) {
   803  	if w.backupWriter == nil && w.currentFile == nil {
   804  		return 0, errors.New("closed")
   805  	}
   806  	return w.bufWriter.Write(b)
   807  }
   808  
   809  func (w *legacyLayerWriter) Close() error {
   810  	if err := w.reset(); err != nil {
   811  		return err
   812  	}
   813  	if err := safefile.RemoveRelative("tombstones.txt", w.root); err != nil && !os.IsNotExist(err) {
   814  		return err
   815  	}
   816  	for _, pd := range w.pendingDirs {
   817  		err := safefile.MkdirRelative(pd.Path, pd.Root)
   818  		if err != nil {
   819  			return err
   820  		}
   821  	}
   822  	return nil
   823  }
   824  

View as plain text