123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 |
- package hcsshim
- import (
- "bufio"
- "encoding/binary"
- "errors"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "strings"
- "syscall"
- "github.com/Microsoft/go-winio"
- )
- var errorIterationCanceled = errors.New("")
- func openFileOrDir(path string, mode uint32, createDisposition uint32) (file *os.File, err error) {
- return winio.OpenForBackup(path, mode, syscall.FILE_SHARE_READ, createDisposition)
- }
- func makeLongAbsPath(path string) (string, error) {
- if strings.HasPrefix(path, `\\?\`) || strings.HasPrefix(path, `\\.\`) {
- return path, nil
- }
- if !filepath.IsAbs(path) {
- absPath, err := filepath.Abs(path)
- if err != nil {
- return "", err
- }
- path = absPath
- }
- if strings.HasPrefix(path, `\\`) {
- return `\\?\UNC\` + path[2:], nil
- }
- return `\\?\` + path, nil
- }
- 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
- isTP4Format bool
- }
- // newLegacyLayerReader returns a new LayerReader that can read the Windows
- // TP4 transport format from disk.
- func newLegacyLayerReader(root string) *legacyLayerReader {
- r := &legacyLayerReader{
- root: root,
- result: make(chan *fileEntry),
- proceed: make(chan bool),
- isTP4Format: IsTP4(),
- }
- 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("Files", 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 := makeLongAbsPath(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
- }
- 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() && strings.HasPrefix(path, `Files\`) {
- 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 !strings.HasPrefix(path, `Files\`) {
- size = fe.fi.Size()
- r.backupReader = winio.NewBackupFileReader(f, false)
- if path == "Hives" || path == "Files" {
- // 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, `Hives\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 {
- beginning := int64(0)
- if !r.isTP4Format {
- // In TP5, the file attributes were added before the backup stream
- var attr uint32
- err = binary.Read(f, binary.LittleEndian, &attr)
- if err != nil {
- return
- }
- fileInfo.FileAttributes = uintptr(attr)
- beginning = 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) Close() error {
- r.proceed <- false
- <-r.result
- r.reset()
- return nil
- }
- type legacyLayerWriter struct {
- root string
- currentFile *os.File
- backupWriter *winio.BackupFileWriter
- tombstones []string
- isTP4Format bool
- pathFixed bool
- }
- // newLegacyLayerWriter returns a LayerWriter that can write the TP4 transport format
- // to disk.
- func newLegacyLayerWriter(root string) *legacyLayerWriter {
- return &legacyLayerWriter{
- root: root,
- isTP4Format: IsTP4(),
- }
- }
- func (w *legacyLayerWriter) init() error {
- if !w.pathFixed {
- path, err := makeLongAbsPath(w.root)
- if err != nil {
- return err
- }
- w.root = path
- w.pathFixed = true
- }
- return nil
- }
- func (w *legacyLayerWriter) reset() {
- if w.backupWriter != nil {
- w.backupWriter.Close()
- w.backupWriter = nil
- }
- if w.currentFile != nil {
- w.currentFile.Close()
- w.currentFile = nil
- }
- }
- func (w *legacyLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo) error {
- w.reset()
- err := w.init()
- if err != nil {
- return err
- }
- path := filepath.Join(w.root, name)
- createDisposition := uint32(syscall.CREATE_NEW)
- if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
- err := os.Mkdir(path, 0)
- if err != nil {
- return err
- }
- path += ".$wcidirs$"
- }
- f, err := openFileOrDir(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, createDisposition)
- if err != nil {
- return err
- }
- defer func() {
- if f != nil {
- f.Close()
- os.Remove(path)
- }
- }()
- strippedFi := *fileInfo
- strippedFi.FileAttributes = 0
- err = winio.SetFileBasicInfo(f, &strippedFi)
- if err != nil {
- return err
- }
- if strings.HasPrefix(name, `Hives\`) {
- w.backupWriter = winio.NewBackupFileWriter(f, false)
- } else {
- if !w.isTP4Format {
- // In TP5, the file attributes were added to the header
- err = binary.Write(f, binary.LittleEndian, uint32(fileInfo.FileAttributes))
- if err != nil {
- return err
- }
- }
- }
- w.currentFile = f
- f = nil
- return nil
- }
- func (w *legacyLayerWriter) AddLink(name string, target string) error {
- return errors.New("hard links not supported with legacy writer")
- }
- func (w *legacyLayerWriter) Remove(name string) error {
- if !strings.HasPrefix(name, `Files\`) {
- return fmt.Errorf("invalid tombstone %s", name)
- }
- w.tombstones = append(w.tombstones, name[len(`Files\`):])
- return nil
- }
- func (w *legacyLayerWriter) Write(b []byte) (int, error) {
- if w.backupWriter == nil {
- if w.currentFile == nil {
- return 0, errors.New("closed")
- }
- return w.currentFile.Write(b)
- }
- return w.backupWriter.Write(b)
- }
- func (w *legacyLayerWriter) Close() error {
- w.reset()
- err := w.init()
- if err != nil {
- return err
- }
- tf, err := os.Create(filepath.Join(w.root, "tombstones.txt"))
- if err != nil {
- return err
- }
- defer tf.Close()
- _, err = tf.Write([]byte("\xef\xbb\xbfVersion 1.0\n"))
- if err != nil {
- return err
- }
- for _, t := range w.tombstones {
- _, err = tf.Write([]byte(filepath.Join(`\`, t) + "\n"))
- if err != nil {
- return err
- }
- }
- return nil
- }
|