1
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
61
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:])
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
114
115
116
117
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
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
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
248
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
264 fileInfo.CreationTime = fileInfo.LastWriteTime
265 fileInfo.LastAccessTime = fileInfo.LastWriteTime
266 } else {
267
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
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
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
365
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
417
418
419
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
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
447 break
448 }
449 if err != nil {
450 return err
451 }
452 switch bhdr.Id {
453 case winio.BackupReparseData:
454
455
456
457
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
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
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
540
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
559
560 isReparsePoint := fileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0
561
562
563
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
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
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
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
732
733 roots = w.parentRoots
734 } else if hasPathPrefix(target, utilityVMFilesPath) {
735
736
737
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
746
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
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
786
787
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