From b1cf780eb53863bb466cc3f9a4da74ca6662abf3 Mon Sep 17 00:00:00 2001 From: John Starks Date: Tue, 23 Feb 2016 12:08:43 -0800 Subject: [PATCH] Use tar fork to encode Windows metadata correctly --- backuptar/tar.go | 59 +++++++++++++++++++-------------------- backuptar/tar_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 30 deletions(-) create mode 100644 backuptar/tar_test.go diff --git a/backuptar/tar.go b/backuptar/tar.go index e46d73c..c988574 100644 --- a/backuptar/tar.go +++ b/backuptar/tar.go @@ -1,7 +1,6 @@ package backuptar import ( - "archive/tar" "errors" "fmt" "io" @@ -13,6 +12,7 @@ import ( "time" "github.com/Microsoft/go-winio" + "github.com/Microsoft/go-winio/archive/tar" // until archive/tar supports pax extensions in its interface ) const ( @@ -29,13 +29,13 @@ const ( ) const ( - xattrFileAttributes = "MSWINDOWS.fileattr" - xattrAccessTime = "MSWINDOWS.accesstime" - xattrChangeTime = "MSWINDOWS.changetime" - xattrCreateTime = "MSWINDOWS.createtime" - xattrWriteTime = "MSWINDOWS.writetime" - xattrSecurityDescriptor = "MSWINDOWS.sd" - xattrMountPoint = "MSWINDOWS.mountpoint" + hdrFileAttributes = "fileattr" + hdrAccessTime = "accesstime" + hdrChangeTime = "changetime" + hdrCreateTime = "createtime" + hdrWriteTime = "writetime" + hdrSecurityDescriptor = "sd" + hdrMountPoint = "mountpoint" ) func writeZeroes(w io.Writer, count int64) error { @@ -82,8 +82,8 @@ func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error { return nil } -func win32TimeFromTar(key string, xattrs map[string]string, unixTime time.Time) syscall.Filetime { - if s, ok := xattrs[key]; ok { +func win32TimeFromTar(key string, hdrs map[string]string, unixTime time.Time) syscall.Filetime { + if s, ok := hdrs[key]; ok { n, err := strconv.ParseUint(s, 10, 64) if err == nil { return syscall.Filetime{uint32(n & 0xffffffff), uint32(n >> 32)} @@ -98,8 +98,7 @@ func win32TimeToTar(ft syscall.Filetime) (string, time.Time) { // Writes a file to a tar writer using data from a Win32 backup stream. // -// Currently this encodes Win32 metadata as tar xattrs (i.e. SCHILY.xattr.*), which is incorrect. -// This should be fixed archive/tar exposes the ability to write general pax headers. +// This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS. // // The additional Win32 metadata is: // @@ -119,16 +118,16 @@ func win32TimeToTar(ft syscall.Filetime) (string, time.Time) { func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error { name = filepath.ToSlash(name) hdr := &tar.Header{ - Name: name, - Size: size, - Typeflag: tar.TypeReg, - Xattrs: make(map[string]string), + Name: name, + Size: size, + Typeflag: tar.TypeReg, + Winheaders: make(map[string]string), } - hdr.Xattrs[xattrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes) - hdr.Xattrs[xattrAccessTime], hdr.AccessTime = win32TimeToTar(fileInfo.LastAccessTime) - hdr.Xattrs[xattrChangeTime], hdr.ChangeTime = win32TimeToTar(fileInfo.ChangeTime) - hdr.Xattrs[xattrCreateTime], _ = win32TimeToTar(fileInfo.CreationTime) - hdr.Xattrs[xattrWriteTime], hdr.ModTime = win32TimeToTar(fileInfo.LastWriteTime) + hdr.Winheaders[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes) + hdr.Winheaders[hdrAccessTime], hdr.AccessTime = win32TimeToTar(fileInfo.LastAccessTime) + hdr.Winheaders[hdrChangeTime], hdr.ChangeTime = win32TimeToTar(fileInfo.ChangeTime) + hdr.Winheaders[hdrCreateTime], _ = win32TimeToTar(fileInfo.CreationTime) + hdr.Winheaders[hdrWriteTime], hdr.ModTime = win32TimeToTar(fileInfo.LastWriteTime) if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 { hdr.Mode |= c_ISDIR @@ -159,7 +158,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size if err != nil { return err } - hdr.Xattrs[xattrSecurityDescriptor] = sddl + hdr.Winheaders[hdrSecurityDescriptor] = sddl case winio.BackupReparseData: hdr.Mode |= c_ISLNK @@ -170,7 +169,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size return err } if rp.IsMountPoint { - hdr.Xattrs[xattrMountPoint] = "1" + hdr.Winheaders[hdrMountPoint] = "1" } hdr.Linkname = rp.Target case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData: @@ -261,12 +260,12 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win size = hdr.Size } fileInfo = &winio.FileBasicInfo{ - LastAccessTime: win32TimeFromTar(xattrAccessTime, hdr.Xattrs, hdr.AccessTime), - LastWriteTime: win32TimeFromTar(xattrWriteTime, hdr.Xattrs, hdr.ModTime), - ChangeTime: win32TimeFromTar(xattrChangeTime, hdr.Xattrs, hdr.ChangeTime), - CreationTime: win32TimeFromTar(xattrCreateTime, hdr.Xattrs, hdr.ModTime), + LastAccessTime: win32TimeFromTar(hdrAccessTime, hdr.Winheaders, hdr.AccessTime), + LastWriteTime: win32TimeFromTar(hdrWriteTime, hdr.Winheaders, hdr.ModTime), + ChangeTime: win32TimeFromTar(hdrChangeTime, hdr.Winheaders, hdr.ChangeTime), + CreationTime: win32TimeFromTar(hdrCreateTime, hdr.Winheaders, hdr.ModTime), } - if attrStr, ok := hdr.Xattrs[xattrFileAttributes]; ok { + if attrStr, ok := hdr.Winheaders[hdrFileAttributes]; ok { attr, err := strconv.ParseUint(attrStr, 10, 32) if err != nil { return "", 0, nil, err @@ -285,7 +284,7 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win // tar file that was not processed, or io.EOF is there are no more. func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) { bw := winio.NewBackupStreamWriter(w) - if sddl, ok := hdr.Xattrs[xattrSecurityDescriptor]; ok { + if sddl, ok := hdr.Winheaders[hdrSecurityDescriptor]; ok { sd, err := winio.SddlToSecurityDescriptor(sddl) if err != nil { return nil, err @@ -304,7 +303,7 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) ( } } if hdr.Typeflag == tar.TypeSymlink { - _, isMountPoint := hdr.Xattrs[xattrMountPoint] + _, isMountPoint := hdr.Winheaders[hdrMountPoint] rp := winio.ReparsePoint{ Target: hdr.Linkname, IsMountPoint: isMountPoint, diff --git a/backuptar/tar_test.go b/backuptar/tar_test.go new file mode 100644 index 0000000..f02b045 --- /dev/null +++ b/backuptar/tar_test.go @@ -0,0 +1,65 @@ +package backuptar + +import ( + "bytes" + "io/ioutil" + "os" + "testing" + + "github.com/Microsoft/go-winio" + "github.com/Microsoft/go-winio/archive/tar" +) + +func ensurePresent(t *testing.T, m map[string]string, keys ...string) { + for _, k := range keys { + if _, ok := m[k]; !ok { + t.Error(k, "not present in tar header") + } + } +} + +func TestRoundTrip(t *testing.T) { + f, err := ioutil.TempFile("", "tst") + if err != nil { + t.Fatal(err) + } + defer f.Close() + defer os.Remove(f.Name()) + + if _, err = f.Write([]byte("testing 1 2 3\n")); err != nil { + t.Fatal(err) + } + + if _, err = f.Seek(0, 0); err != nil { + t.Fatal(err) + } + + fi, err := f.Stat() + if err != nil { + t.Fatal(err) + } + + bi, err := winio.GetFileBasicInfo(f) + if err != nil { + t.Fatal(err) + } + + br := winio.NewBackupFileReader(f, true) + defer br.Close() + + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + + err = WriteTarFileFromBackupStream(tw, br, f.Name(), fi.Size(), bi) + if err != nil { + t.Fatal(err) + } + + tr := tar.NewReader(&buf) + hdr, err := tr.Next() + if err != nil { + t.Fatal(err) + } + + ensurePresent(t, hdr.Winheaders, "fileattr", "sd", "accesstime", "changetime", "createtime", "writetime") +}