14 "github.com/Microsoft/go-winio"
17 var errorIterationCanceled = errors.New("")
19 var mutatedUtilityVMFiles = map[string]bool{
20 `EFI\Microsoft\Boot\BCD`: true,
21 `EFI\Microsoft\Boot\BCD.LOG`: true,
22 `EFI\Microsoft\Boot\BCD.LOG1`: true,
23 `EFI\Microsoft\Boot\BCD.LOG2`: true,
29 utilityVMPath = `UtilityVM`
30 utilityVMFilesPath = `UtilityVM\Files`
33 func openFileOrDir(path string, mode uint32, createDisposition uint32) (file *os.File, err error) {
34 return winio.OpenForBackup(path, mode, syscall.FILE_SHARE_READ, createDisposition)
37 func makeLongAbsPath(path string) (string, error) {
38 if strings.HasPrefix(path, `\\?\`) || strings.HasPrefix(path, `\\.\`) {
41 if !filepath.IsAbs(path) {
42 absPath, err := filepath.Abs(path)
48 if strings.HasPrefix(path, `\\`) {
49 return `\\?\UNC\` + path[2:], nil
51 return `\\?\` + path, nil
54 func hasPathPrefix(p, prefix string) bool {
55 return strings.HasPrefix(p, prefix) && len(p) > len(prefix) && p[len(prefix)] == '\\'
58 type fileEntry struct {
64 type legacyLayerReader struct {
66 result chan *fileEntry
69 backupReader *winio.BackupFileReader
72 // newLegacyLayerReader returns a new LayerReader that can read the Windows
73 // container layer transport format from disk.
74 func newLegacyLayerReader(root string) *legacyLayerReader {
75 r := &legacyLayerReader{
77 result: make(chan *fileEntry),
78 proceed: make(chan bool),
84 func readTombstones(path string) (map[string]([]string), error) {
85 tf, err := os.Open(filepath.Join(path, "tombstones.txt"))
90 s := bufio.NewScanner(tf)
91 if !s.Scan() || s.Text() != "\xef\xbb\xbfVersion 1.0" {
92 return nil, errors.New("Invalid tombstones file")
95 ts := make(map[string]([]string))
97 t := filepath.Join(filesPath, s.Text()[1:]) // skip leading `\`
98 dir := filepath.Dir(t)
99 ts[dir] = append(ts[dir], t)
101 if err = s.Err(); err != nil {
108 func (r *legacyLayerReader) walkUntilCancelled() error {
109 root, err := makeLongAbsPath(r.root)
115 ts, err := readTombstones(r.root)
120 err = filepath.Walk(r.root, func(path string, info os.FileInfo, err error) error {
124 if path == r.root || path == filepath.Join(r.root, "tombstones.txt") || strings.HasSuffix(path, ".$wcidirs$") {
128 r.result <- &fileEntry{path, info, nil}
130 return errorIterationCanceled
133 // List all the tombstones.
135 relPath, err := filepath.Rel(r.root, path)
139 if dts, ok := ts[relPath]; ok {
140 for _, t := range dts {
141 r.result <- &fileEntry{filepath.Join(r.root, t), nil, nil}
143 return errorIterationCanceled
150 if err == errorIterationCanceled {
159 func (r *legacyLayerReader) walk() {
160 defer close(r.result)
165 err := r.walkUntilCancelled()
168 r.result <- &fileEntry{err: err}
176 func (r *legacyLayerReader) reset() {
177 if r.backupReader != nil {
178 r.backupReader.Close()
181 if r.currentFile != nil {
182 r.currentFile.Close()
187 func findBackupStreamSize(r io.Reader) (int64, error) {
188 br := winio.NewBackupStreamReader(r)
190 hdr, err := br.Next()
197 if hdr.Id == winio.BackupData {
203 func (r *legacyLayerReader) Next() (path string, size int64, fileInfo *winio.FileBasicInfo, err error) {
208 err = errors.New("LegacyLayerReader closed")
216 path, err = filepath.Rel(r.root, fe.path)
222 // This is a tombstone. Return a nil fileInfo.
226 if fe.fi.IsDir() && hasPathPrefix(path, filesPath) {
227 fe.path += ".$wcidirs$"
230 f, err := openFileOrDir(fe.path, syscall.GENERIC_READ, syscall.OPEN_EXISTING)
240 fileInfo, err = winio.GetFileBasicInfo(f)
245 if !hasPathPrefix(path, filesPath) {
247 r.backupReader = winio.NewBackupFileReader(f, false)
248 if path == hivesPath || path == filesPath {
249 // The Hives directory has a non-deterministic file time because of the
250 // nature of the import process. Use the times from System_Delta.
252 g, err = os.Open(filepath.Join(r.root, hivesPath, `System_Delta`))
256 attr := fileInfo.FileAttributes
257 fileInfo, err = winio.GetFileBasicInfo(g)
262 fileInfo.FileAttributes = attr
265 // The creation time and access time get reset for files outside of the Files path.
266 fileInfo.CreationTime = fileInfo.LastWriteTime
267 fileInfo.LastAccessTime = fileInfo.LastWriteTime
270 // The file attributes are written before the backup stream.
272 err = binary.Read(f, binary.LittleEndian, &attr)
276 fileInfo.FileAttributes = uintptr(attr)
277 beginning := int64(4)
279 // Find the accurate file size.
281 size, err = findBackupStreamSize(f)
283 err = &os.PathError{Op: "findBackupStreamSize", Path: fe.path, Err: err}
288 // Return back to the beginning of the backup stream.
289 _, err = f.Seek(beginning, 0)
300 func (r *legacyLayerReader) Read(b []byte) (int, error) {
301 if r.backupReader == nil {
302 if r.currentFile == nil {
305 return r.currentFile.Read(b)
307 return r.backupReader.Read(b)
310 func (r *legacyLayerReader) Close() error {
317 type pendingLink struct {
321 type legacyLayerWriter struct {
326 backupWriter *winio.BackupFileWriter
331 addedFiles map[string]bool
332 PendingLinks []pendingLink
335 // newLegacyLayerWriter returns a LayerWriter that can write the contaler layer
336 // transport format to disk.
337 func newLegacyLayerWriter(root string, parentRoots []string, destRoot string) *legacyLayerWriter {
338 return &legacyLayerWriter{
340 parentRoots: parentRoots,
342 addedFiles: make(map[string]bool),
346 func (w *legacyLayerWriter) init() error {
348 path, err := makeLongAbsPath(w.root)
352 for i, p := range w.parentRoots {
353 w.parentRoots[i], err = makeLongAbsPath(p)
358 destPath, err := makeLongAbsPath(w.destRoot)
363 w.destRoot = destPath
369 func (w *legacyLayerWriter) initUtilityVM() error {
371 err := os.Mkdir(filepath.Join(w.destRoot, utilityVMPath), 0)
375 // Server 2016 does not support multiple layers for the utility VM, so
376 // clone the utility VM from the parent layer into this layer. Use hard
377 // links to avoid unnecessary copying, since most of the files are
379 err = cloneTree(filepath.Join(w.parentRoots[0], utilityVMFilesPath), filepath.Join(w.destRoot, utilityVMFilesPath), mutatedUtilityVMFiles)
381 return fmt.Errorf("cloning the parent utility VM image failed: %s", err)
383 w.HasUtilityVM = true
388 func (w *legacyLayerWriter) reset() {
389 if w.backupWriter != nil {
390 w.backupWriter.Close()
393 if w.currentFile != nil {
394 w.currentFile.Close()
399 // copyFileWithMetadata copies a file using the backup/restore APIs in order to preserve metadata
400 func copyFileWithMetadata(srcPath, destPath string, isDir bool) (fileInfo *winio.FileBasicInfo, err error) {
401 createDisposition := uint32(syscall.CREATE_NEW)
403 err = os.Mkdir(destPath, 0)
407 createDisposition = syscall.OPEN_EXISTING
410 src, err := openFileOrDir(srcPath, syscall.GENERIC_READ|winio.ACCESS_SYSTEM_SECURITY, syscall.OPEN_EXISTING)
415 srcr := winio.NewBackupFileReader(src, true)
418 fileInfo, err = winio.GetFileBasicInfo(src)
423 dest, err := openFileOrDir(destPath, syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY, createDisposition)
429 err = winio.SetFileBasicInfo(dest, fileInfo)
434 destw := winio.NewBackupFileWriter(dest, true)
436 cerr := destw.Close()
442 _, err = io.Copy(destw, srcr)
450 // cloneTree clones a directory tree using hard links. It skips hard links for
451 // the file names in the provided map and just copies those files.
452 func cloneTree(srcPath, destPath string, mutatedFiles map[string]bool) error {
454 err := filepath.Walk(srcPath, func(srcFilePath string, info os.FileInfo, err error) error {
459 relPath, err := filepath.Rel(srcPath, srcFilePath)
463 destFilePath := filepath.Join(destPath, relPath)
465 // Directories, reparse points, and files that will be mutated during
466 // utility VM import must be copied. All other files can be hard linked.
467 isReparsePoint := info.Sys().(*syscall.Win32FileAttributeData).FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0
468 if info.IsDir() || isReparsePoint || mutatedFiles[relPath] {
469 fi, err := copyFileWithMetadata(srcFilePath, destFilePath, info.IsDir())
473 if info.IsDir() && !isReparsePoint {
474 di = append(di, dirInfo{path: destFilePath, fileInfo: *fi})
477 err = os.Link(srcFilePath, destFilePath)
483 // Don't recurse on reparse points.
484 if info.IsDir() && isReparsePoint {
485 return filepath.SkipDir
494 return reapplyDirectoryTimes(di)
497 func (w *legacyLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo) error {
504 if name == utilityVMPath {
505 return w.initUtilityVM()
508 if hasPathPrefix(name, utilityVMPath) {
510 return errors.New("missing UtilityVM directory")
512 if !hasPathPrefix(name, utilityVMFilesPath) && name != utilityVMFilesPath {
513 return errors.New("invalid UtilityVM layer")
515 path := filepath.Join(w.destRoot, name)
516 createDisposition := uint32(syscall.OPEN_EXISTING)
517 if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
518 st, err := os.Lstat(path)
519 if err != nil && !os.IsNotExist(err) {
523 // Delete the existing file/directory if it is not the same type as this directory.
524 existingAttr := st.Sys().(*syscall.Win32FileAttributeData).FileAttributes
525 if (uint32(fileInfo.FileAttributes)^existingAttr)&(syscall.FILE_ATTRIBUTE_DIRECTORY|syscall.FILE_ATTRIBUTE_REPARSE_POINT) != 0 {
526 if err = os.RemoveAll(path); err != nil {
533 if err = os.Mkdir(path, 0); err != nil {
537 if fileInfo.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
538 w.uvmDi = append(w.uvmDi, dirInfo{path: path, fileInfo: *fileInfo})
541 // Overwrite any existing hard link.
542 err = os.Remove(path)
543 if err != nil && !os.IsNotExist(err) {
546 createDisposition = syscall.CREATE_NEW
549 f, err := openFileOrDir(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY, createDisposition)
560 err = winio.SetFileBasicInfo(f, fileInfo)
565 w.backupWriter = winio.NewBackupFileWriter(f, true)
567 w.addedFiles[name] = true
572 path := filepath.Join(w.root, name)
573 if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
574 err := os.Mkdir(path, 0)
581 f, err := openFileOrDir(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, syscall.CREATE_NEW)
592 strippedFi := *fileInfo
593 strippedFi.FileAttributes = 0
594 err = winio.SetFileBasicInfo(f, &strippedFi)
599 if hasPathPrefix(name, hivesPath) {
600 w.backupWriter = winio.NewBackupFileWriter(f, false)
602 // The file attributes are written before the stream.
603 err = binary.Write(f, binary.LittleEndian, uint32(fileInfo.FileAttributes))
610 w.addedFiles[name] = true
615 func (w *legacyLayerWriter) AddLink(name string, target string) error {
623 if hasPathPrefix(target, filesPath) {
624 // Look for cross-layer hard link targets in the parent layers, since
625 // nothing is in the destination path yet.
626 roots = w.parentRoots
627 } else if hasPathPrefix(target, utilityVMFilesPath) {
628 // Since the utility VM is fully cloned into the destination path
629 // already, look for cross-layer hard link targets directly in the
631 roots = []string{w.destRoot}
634 if roots == nil || (!hasPathPrefix(name, filesPath) && !hasPathPrefix(name, utilityVMFilesPath)) {
635 return errors.New("invalid hard link in layer")
638 // Find to try the target of the link in a previously added file. If that
639 // fails, search in parent layers.
640 var selectedRoot string
641 if _, ok := w.addedFiles[target]; ok {
642 selectedRoot = w.destRoot
644 for _, r := range roots {
645 if _, err = os.Lstat(filepath.Join(r, target)); err != nil {
646 if !os.IsNotExist(err) {
654 if selectedRoot == "" {
655 return fmt.Errorf("failed to find link target for '%s' -> '%s'", name, target)
658 // The link can't be written until after the ImportLayer call.
659 w.PendingLinks = append(w.PendingLinks, pendingLink{
660 Path: filepath.Join(w.destRoot, name),
661 Target: filepath.Join(selectedRoot, target),
663 w.addedFiles[name] = true
667 func (w *legacyLayerWriter) Remove(name string) error {
668 if hasPathPrefix(name, filesPath) {
669 w.tombstones = append(w.tombstones, name[len(filesPath)+1:])
670 } else if hasPathPrefix(name, utilityVMFilesPath) {
671 err := w.initUtilityVM()
675 // Make sure the path exists; os.RemoveAll will not fail if the file is
676 // already gone, and this needs to be a fatal error for diagnostics
678 path := filepath.Join(w.destRoot, name)
679 if _, err := os.Lstat(path); err != nil {
682 err = os.RemoveAll(path)
687 return fmt.Errorf("invalid tombstone %s", name)
693 func (w *legacyLayerWriter) Write(b []byte) (int, error) {
694 if w.backupWriter == nil {
695 if w.currentFile == nil {
696 return 0, errors.New("closed")
698 return w.currentFile.Write(b)
700 return w.backupWriter.Write(b)
703 func (w *legacyLayerWriter) Close() error {
709 tf, err := os.Create(filepath.Join(w.root, "tombstones.txt"))
714 _, err = tf.Write([]byte("\xef\xbb\xbfVersion 1.0\n"))
718 for _, t := range w.tombstones {
719 _, err = tf.Write([]byte(filepath.Join(`\`, t) + "\n"))
725 err = reapplyDirectoryTimes(w.uvmDi)