Tizen_4.0 base
[platform/upstream/docker-engine.git] / vendor / github.com / Microsoft / hcsshim / legacy.go
1 package hcsshim
2
3 import (
4         "bufio"
5         "encoding/binary"
6         "errors"
7         "fmt"
8         "io"
9         "os"
10         "path/filepath"
11         "strings"
12         "syscall"
13
14         "github.com/Microsoft/go-winio"
15 )
16
17 var errorIterationCanceled = errors.New("")
18
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,
24 }
25
26 const (
27         filesPath          = `Files`
28         hivesPath          = `Hives`
29         utilityVMPath      = `UtilityVM`
30         utilityVMFilesPath = `UtilityVM\Files`
31 )
32
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)
35 }
36
37 func makeLongAbsPath(path string) (string, error) {
38         if strings.HasPrefix(path, `\\?\`) || strings.HasPrefix(path, `\\.\`) {
39                 return path, nil
40         }
41         if !filepath.IsAbs(path) {
42                 absPath, err := filepath.Abs(path)
43                 if err != nil {
44                         return "", err
45                 }
46                 path = absPath
47         }
48         if strings.HasPrefix(path, `\\`) {
49                 return `\\?\UNC\` + path[2:], nil
50         }
51         return `\\?\` + path, nil
52 }
53
54 func hasPathPrefix(p, prefix string) bool {
55         return strings.HasPrefix(p, prefix) && len(p) > len(prefix) && p[len(prefix)] == '\\'
56 }
57
58 type fileEntry struct {
59         path string
60         fi   os.FileInfo
61         err  error
62 }
63
64 type legacyLayerReader struct {
65         root         string
66         result       chan *fileEntry
67         proceed      chan bool
68         currentFile  *os.File
69         backupReader *winio.BackupFileReader
70 }
71
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{
76                 root:    root,
77                 result:  make(chan *fileEntry),
78                 proceed: make(chan bool),
79         }
80         go r.walk()
81         return r
82 }
83
84 func readTombstones(path string) (map[string]([]string), error) {
85         tf, err := os.Open(filepath.Join(path, "tombstones.txt"))
86         if err != nil {
87                 return nil, err
88         }
89         defer tf.Close()
90         s := bufio.NewScanner(tf)
91         if !s.Scan() || s.Text() != "\xef\xbb\xbfVersion 1.0" {
92                 return nil, errors.New("Invalid tombstones file")
93         }
94
95         ts := make(map[string]([]string))
96         for s.Scan() {
97                 t := filepath.Join(filesPath, s.Text()[1:]) // skip leading `\`
98                 dir := filepath.Dir(t)
99                 ts[dir] = append(ts[dir], t)
100         }
101         if err = s.Err(); err != nil {
102                 return nil, err
103         }
104
105         return ts, nil
106 }
107
108 func (r *legacyLayerReader) walkUntilCancelled() error {
109         root, err := makeLongAbsPath(r.root)
110         if err != nil {
111                 return err
112         }
113
114         r.root = root
115         ts, err := readTombstones(r.root)
116         if err != nil {
117                 return err
118         }
119
120         err = filepath.Walk(r.root, func(path string, info os.FileInfo, err error) error {
121                 if err != nil {
122                         return err
123                 }
124                 if path == r.root || path == filepath.Join(r.root, "tombstones.txt") || strings.HasSuffix(path, ".$wcidirs$") {
125                         return nil
126                 }
127
128                 r.result <- &fileEntry{path, info, nil}
129                 if !<-r.proceed {
130                         return errorIterationCanceled
131                 }
132
133                 // List all the tombstones.
134                 if info.IsDir() {
135                         relPath, err := filepath.Rel(r.root, path)
136                         if err != nil {
137                                 return err
138                         }
139                         if dts, ok := ts[relPath]; ok {
140                                 for _, t := range dts {
141                                         r.result <- &fileEntry{filepath.Join(r.root, t), nil, nil}
142                                         if !<-r.proceed {
143                                                 return errorIterationCanceled
144                                         }
145                                 }
146                         }
147                 }
148                 return nil
149         })
150         if err == errorIterationCanceled {
151                 return nil
152         }
153         if err == nil {
154                 return io.EOF
155         }
156         return err
157 }
158
159 func (r *legacyLayerReader) walk() {
160         defer close(r.result)
161         if !<-r.proceed {
162                 return
163         }
164
165         err := r.walkUntilCancelled()
166         if err != nil {
167                 for {
168                         r.result <- &fileEntry{err: err}
169                         if !<-r.proceed {
170                                 return
171                         }
172                 }
173         }
174 }
175
176 func (r *legacyLayerReader) reset() {
177         if r.backupReader != nil {
178                 r.backupReader.Close()
179                 r.backupReader = nil
180         }
181         if r.currentFile != nil {
182                 r.currentFile.Close()
183                 r.currentFile = nil
184         }
185 }
186
187 func findBackupStreamSize(r io.Reader) (int64, error) {
188         br := winio.NewBackupStreamReader(r)
189         for {
190                 hdr, err := br.Next()
191                 if err != nil {
192                         if err == io.EOF {
193                                 err = nil
194                         }
195                         return 0, err
196                 }
197                 if hdr.Id == winio.BackupData {
198                         return hdr.Size, nil
199                 }
200         }
201 }
202
203 func (r *legacyLayerReader) Next() (path string, size int64, fileInfo *winio.FileBasicInfo, err error) {
204         r.reset()
205         r.proceed <- true
206         fe := <-r.result
207         if fe == nil {
208                 err = errors.New("LegacyLayerReader closed")
209                 return
210         }
211         if fe.err != nil {
212                 err = fe.err
213                 return
214         }
215
216         path, err = filepath.Rel(r.root, fe.path)
217         if err != nil {
218                 return
219         }
220
221         if fe.fi == nil {
222                 // This is a tombstone. Return a nil fileInfo.
223                 return
224         }
225
226         if fe.fi.IsDir() && hasPathPrefix(path, filesPath) {
227                 fe.path += ".$wcidirs$"
228         }
229
230         f, err := openFileOrDir(fe.path, syscall.GENERIC_READ, syscall.OPEN_EXISTING)
231         if err != nil {
232                 return
233         }
234         defer func() {
235                 if f != nil {
236                         f.Close()
237                 }
238         }()
239
240         fileInfo, err = winio.GetFileBasicInfo(f)
241         if err != nil {
242                 return
243         }
244
245         if !hasPathPrefix(path, filesPath) {
246                 size = fe.fi.Size()
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.
251                         var g *os.File
252                         g, err = os.Open(filepath.Join(r.root, hivesPath, `System_Delta`))
253                         if err != nil {
254                                 return
255                         }
256                         attr := fileInfo.FileAttributes
257                         fileInfo, err = winio.GetFileBasicInfo(g)
258                         g.Close()
259                         if err != nil {
260                                 return
261                         }
262                         fileInfo.FileAttributes = attr
263                 }
264
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
268
269         } else {
270                 // The file attributes are written before the backup stream.
271                 var attr uint32
272                 err = binary.Read(f, binary.LittleEndian, &attr)
273                 if err != nil {
274                         return
275                 }
276                 fileInfo.FileAttributes = uintptr(attr)
277                 beginning := int64(4)
278
279                 // Find the accurate file size.
280                 if !fe.fi.IsDir() {
281                         size, err = findBackupStreamSize(f)
282                         if err != nil {
283                                 err = &os.PathError{Op: "findBackupStreamSize", Path: fe.path, Err: err}
284                                 return
285                         }
286                 }
287
288                 // Return back to the beginning of the backup stream.
289                 _, err = f.Seek(beginning, 0)
290                 if err != nil {
291                         return
292                 }
293         }
294
295         r.currentFile = f
296         f = nil
297         return
298 }
299
300 func (r *legacyLayerReader) Read(b []byte) (int, error) {
301         if r.backupReader == nil {
302                 if r.currentFile == nil {
303                         return 0, io.EOF
304                 }
305                 return r.currentFile.Read(b)
306         }
307         return r.backupReader.Read(b)
308 }
309
310 func (r *legacyLayerReader) Close() error {
311         r.proceed <- false
312         <-r.result
313         r.reset()
314         return nil
315 }
316
317 type pendingLink struct {
318         Path, Target string
319 }
320
321 type legacyLayerWriter struct {
322         root         string
323         parentRoots  []string
324         destRoot     string
325         currentFile  *os.File
326         backupWriter *winio.BackupFileWriter
327         tombstones   []string
328         pathFixed    bool
329         HasUtilityVM bool
330         uvmDi        []dirInfo
331         addedFiles   map[string]bool
332         PendingLinks []pendingLink
333 }
334
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{
339                 root:        root,
340                 parentRoots: parentRoots,
341                 destRoot:    destRoot,
342                 addedFiles:  make(map[string]bool),
343         }
344 }
345
346 func (w *legacyLayerWriter) init() error {
347         if !w.pathFixed {
348                 path, err := makeLongAbsPath(w.root)
349                 if err != nil {
350                         return err
351                 }
352                 for i, p := range w.parentRoots {
353                         w.parentRoots[i], err = makeLongAbsPath(p)
354                         if err != nil {
355                                 return err
356                         }
357                 }
358                 destPath, err := makeLongAbsPath(w.destRoot)
359                 if err != nil {
360                         return err
361                 }
362                 w.root = path
363                 w.destRoot = destPath
364                 w.pathFixed = true
365         }
366         return nil
367 }
368
369 func (w *legacyLayerWriter) initUtilityVM() error {
370         if !w.HasUtilityVM {
371                 err := os.Mkdir(filepath.Join(w.destRoot, utilityVMPath), 0)
372                 if err != nil {
373                         return err
374                 }
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
378                 // immutable.
379                 err = cloneTree(filepath.Join(w.parentRoots[0], utilityVMFilesPath), filepath.Join(w.destRoot, utilityVMFilesPath), mutatedUtilityVMFiles)
380                 if err != nil {
381                         return fmt.Errorf("cloning the parent utility VM image failed: %s", err)
382                 }
383                 w.HasUtilityVM = true
384         }
385         return nil
386 }
387
388 func (w *legacyLayerWriter) reset() {
389         if w.backupWriter != nil {
390                 w.backupWriter.Close()
391                 w.backupWriter = nil
392         }
393         if w.currentFile != nil {
394                 w.currentFile.Close()
395                 w.currentFile = nil
396         }
397 }
398
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)
402         if isDir {
403                 err = os.Mkdir(destPath, 0)
404                 if err != nil {
405                         return nil, err
406                 }
407                 createDisposition = syscall.OPEN_EXISTING
408         }
409
410         src, err := openFileOrDir(srcPath, syscall.GENERIC_READ|winio.ACCESS_SYSTEM_SECURITY, syscall.OPEN_EXISTING)
411         if err != nil {
412                 return nil, err
413         }
414         defer src.Close()
415         srcr := winio.NewBackupFileReader(src, true)
416         defer srcr.Close()
417
418         fileInfo, err = winio.GetFileBasicInfo(src)
419         if err != nil {
420                 return nil, err
421         }
422
423         dest, err := openFileOrDir(destPath, syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY, createDisposition)
424         if err != nil {
425                 return nil, err
426         }
427         defer dest.Close()
428
429         err = winio.SetFileBasicInfo(dest, fileInfo)
430         if err != nil {
431                 return nil, err
432         }
433
434         destw := winio.NewBackupFileWriter(dest, true)
435         defer func() {
436                 cerr := destw.Close()
437                 if err == nil {
438                         err = cerr
439                 }
440         }()
441
442         _, err = io.Copy(destw, srcr)
443         if err != nil {
444                 return nil, err
445         }
446
447         return fileInfo, nil
448 }
449
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 {
453         var di []dirInfo
454         err := filepath.Walk(srcPath, func(srcFilePath string, info os.FileInfo, err error) error {
455                 if err != nil {
456                         return err
457                 }
458
459                 relPath, err := filepath.Rel(srcPath, srcFilePath)
460                 if err != nil {
461                         return err
462                 }
463                 destFilePath := filepath.Join(destPath, relPath)
464
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())
470                         if err != nil {
471                                 return err
472                         }
473                         if info.IsDir() && !isReparsePoint {
474                                 di = append(di, dirInfo{path: destFilePath, fileInfo: *fi})
475                         }
476                 } else {
477                         err = os.Link(srcFilePath, destFilePath)
478                         if err != nil {
479                                 return err
480                         }
481                 }
482
483                 // Don't recurse on reparse points.
484                 if info.IsDir() && isReparsePoint {
485                         return filepath.SkipDir
486                 }
487
488                 return nil
489         })
490         if err != nil {
491                 return err
492         }
493
494         return reapplyDirectoryTimes(di)
495 }
496
497 func (w *legacyLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo) error {
498         w.reset()
499         err := w.init()
500         if err != nil {
501                 return err
502         }
503
504         if name == utilityVMPath {
505                 return w.initUtilityVM()
506         }
507
508         if hasPathPrefix(name, utilityVMPath) {
509                 if !w.HasUtilityVM {
510                         return errors.New("missing UtilityVM directory")
511                 }
512                 if !hasPathPrefix(name, utilityVMFilesPath) && name != utilityVMFilesPath {
513                         return errors.New("invalid UtilityVM layer")
514                 }
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) {
520                                 return err
521                         }
522                         if st != nil {
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 {
527                                                 return err
528                                         }
529                                         st = nil
530                                 }
531                         }
532                         if st == nil {
533                                 if err = os.Mkdir(path, 0); err != nil {
534                                         return err
535                                 }
536                         }
537                         if fileInfo.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
538                                 w.uvmDi = append(w.uvmDi, dirInfo{path: path, fileInfo: *fileInfo})
539                         }
540                 } else {
541                         // Overwrite any existing hard link.
542                         err = os.Remove(path)
543                         if err != nil && !os.IsNotExist(err) {
544                                 return err
545                         }
546                         createDisposition = syscall.CREATE_NEW
547                 }
548
549                 f, err := openFileOrDir(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE|winio.WRITE_DAC|winio.WRITE_OWNER|winio.ACCESS_SYSTEM_SECURITY, createDisposition)
550                 if err != nil {
551                         return err
552                 }
553                 defer func() {
554                         if f != nil {
555                                 f.Close()
556                                 os.Remove(path)
557                         }
558                 }()
559
560                 err = winio.SetFileBasicInfo(f, fileInfo)
561                 if err != nil {
562                         return err
563                 }
564
565                 w.backupWriter = winio.NewBackupFileWriter(f, true)
566                 w.currentFile = f
567                 w.addedFiles[name] = true
568                 f = nil
569                 return nil
570         }
571
572         path := filepath.Join(w.root, name)
573         if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
574                 err := os.Mkdir(path, 0)
575                 if err != nil {
576                         return err
577                 }
578                 path += ".$wcidirs$"
579         }
580
581         f, err := openFileOrDir(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, syscall.CREATE_NEW)
582         if err != nil {
583                 return err
584         }
585         defer func() {
586                 if f != nil {
587                         f.Close()
588                         os.Remove(path)
589                 }
590         }()
591
592         strippedFi := *fileInfo
593         strippedFi.FileAttributes = 0
594         err = winio.SetFileBasicInfo(f, &strippedFi)
595         if err != nil {
596                 return err
597         }
598
599         if hasPathPrefix(name, hivesPath) {
600                 w.backupWriter = winio.NewBackupFileWriter(f, false)
601         } else {
602                 // The file attributes are written before the stream.
603                 err = binary.Write(f, binary.LittleEndian, uint32(fileInfo.FileAttributes))
604                 if err != nil {
605                         return err
606                 }
607         }
608
609         w.currentFile = f
610         w.addedFiles[name] = true
611         f = nil
612         return nil
613 }
614
615 func (w *legacyLayerWriter) AddLink(name string, target string) error {
616         w.reset()
617         err := w.init()
618         if err != nil {
619                 return err
620         }
621
622         var roots []string
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
630                 // destination path.
631                 roots = []string{w.destRoot}
632         }
633
634         if roots == nil || (!hasPathPrefix(name, filesPath) && !hasPathPrefix(name, utilityVMFilesPath)) {
635                 return errors.New("invalid hard link in layer")
636         }
637
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
643         } else {
644                 for _, r := range roots {
645                         if _, err = os.Lstat(filepath.Join(r, target)); err != nil {
646                                 if !os.IsNotExist(err) {
647                                         return err
648                                 }
649                         } else {
650                                 selectedRoot = r
651                                 break
652                         }
653                 }
654                 if selectedRoot == "" {
655                         return fmt.Errorf("failed to find link target for '%s' -> '%s'", name, target)
656                 }
657         }
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),
662         })
663         w.addedFiles[name] = true
664         return nil
665 }
666
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()
672                 if err != nil {
673                         return err
674                 }
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
677                 // purposes.
678                 path := filepath.Join(w.destRoot, name)
679                 if _, err := os.Lstat(path); err != nil {
680                         return err
681                 }
682                 err = os.RemoveAll(path)
683                 if err != nil {
684                         return err
685                 }
686         } else {
687                 return fmt.Errorf("invalid tombstone %s", name)
688         }
689
690         return nil
691 }
692
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")
697                 }
698                 return w.currentFile.Write(b)
699         }
700         return w.backupWriter.Write(b)
701 }
702
703 func (w *legacyLayerWriter) Close() error {
704         w.reset()
705         err := w.init()
706         if err != nil {
707                 return err
708         }
709         tf, err := os.Create(filepath.Join(w.root, "tombstones.txt"))
710         if err != nil {
711                 return err
712         }
713         defer tf.Close()
714         _, err = tf.Write([]byte("\xef\xbb\xbfVersion 1.0\n"))
715         if err != nil {
716                 return err
717         }
718         for _, t := range w.tombstones {
719                 _, err = tf.Write([]byte(filepath.Join(`\`, t) + "\n"))
720                 if err != nil {
721                         return err
722                 }
723         }
724         if w.HasUtilityVM {
725                 err = reapplyDirectoryTimes(w.uvmDi)
726                 if err != nil {
727                         return err
728                 }
729         }
730         return nil
731 }