Use tar fork to encode Windows metadata correctly

This commit is contained in:
John Starks
2016-02-23 12:48:28 -08:00
parent 71ac47a42e
commit b1cf780eb5
2 changed files with 94 additions and 30 deletions
+29 -30
View File
@@ -1,7 +1,6 @@
package backuptar package backuptar
import ( import (
"archive/tar"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -13,6 +12,7 @@ import (
"time" "time"
"github.com/Microsoft/go-winio" "github.com/Microsoft/go-winio"
"github.com/Microsoft/go-winio/archive/tar" // until archive/tar supports pax extensions in its interface
) )
const ( const (
@@ -29,13 +29,13 @@ const (
) )
const ( const (
xattrFileAttributes = "MSWINDOWS.fileattr" hdrFileAttributes = "fileattr"
xattrAccessTime = "MSWINDOWS.accesstime" hdrAccessTime = "accesstime"
xattrChangeTime = "MSWINDOWS.changetime" hdrChangeTime = "changetime"
xattrCreateTime = "MSWINDOWS.createtime" hdrCreateTime = "createtime"
xattrWriteTime = "MSWINDOWS.writetime" hdrWriteTime = "writetime"
xattrSecurityDescriptor = "MSWINDOWS.sd" hdrSecurityDescriptor = "sd"
xattrMountPoint = "MSWINDOWS.mountpoint" hdrMountPoint = "mountpoint"
) )
func writeZeroes(w io.Writer, count int64) error { func writeZeroes(w io.Writer, count int64) error {
@@ -82,8 +82,8 @@ func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
return nil return nil
} }
func win32TimeFromTar(key string, xattrs map[string]string, unixTime time.Time) syscall.Filetime { func win32TimeFromTar(key string, hdrs map[string]string, unixTime time.Time) syscall.Filetime {
if s, ok := xattrs[key]; ok { if s, ok := hdrs[key]; ok {
n, err := strconv.ParseUint(s, 10, 64) n, err := strconv.ParseUint(s, 10, 64)
if err == nil { if err == nil {
return syscall.Filetime{uint32(n & 0xffffffff), uint32(n >> 32)} 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. // 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 encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS.
// This should be fixed archive/tar exposes the ability to write general pax headers.
// //
// The additional Win32 metadata is: // 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 { func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
name = filepath.ToSlash(name) name = filepath.ToSlash(name)
hdr := &tar.Header{ hdr := &tar.Header{
Name: name, Name: name,
Size: size, Size: size,
Typeflag: tar.TypeReg, Typeflag: tar.TypeReg,
Xattrs: make(map[string]string), Winheaders: make(map[string]string),
} }
hdr.Xattrs[xattrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes) hdr.Winheaders[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)
hdr.Xattrs[xattrAccessTime], hdr.AccessTime = win32TimeToTar(fileInfo.LastAccessTime) hdr.Winheaders[hdrAccessTime], hdr.AccessTime = win32TimeToTar(fileInfo.LastAccessTime)
hdr.Xattrs[xattrChangeTime], hdr.ChangeTime = win32TimeToTar(fileInfo.ChangeTime) hdr.Winheaders[hdrChangeTime], hdr.ChangeTime = win32TimeToTar(fileInfo.ChangeTime)
hdr.Xattrs[xattrCreateTime], _ = win32TimeToTar(fileInfo.CreationTime) hdr.Winheaders[hdrCreateTime], _ = win32TimeToTar(fileInfo.CreationTime)
hdr.Xattrs[xattrWriteTime], hdr.ModTime = win32TimeToTar(fileInfo.LastWriteTime) hdr.Winheaders[hdrWriteTime], hdr.ModTime = win32TimeToTar(fileInfo.LastWriteTime)
if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 { if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
hdr.Mode |= c_ISDIR hdr.Mode |= c_ISDIR
@@ -159,7 +158,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size
if err != nil { if err != nil {
return err return err
} }
hdr.Xattrs[xattrSecurityDescriptor] = sddl hdr.Winheaders[hdrSecurityDescriptor] = sddl
case winio.BackupReparseData: case winio.BackupReparseData:
hdr.Mode |= c_ISLNK hdr.Mode |= c_ISLNK
@@ -170,7 +169,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size
return err return err
} }
if rp.IsMountPoint { if rp.IsMountPoint {
hdr.Xattrs[xattrMountPoint] = "1" hdr.Winheaders[hdrMountPoint] = "1"
} }
hdr.Linkname = rp.Target hdr.Linkname = rp.Target
case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData: 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 size = hdr.Size
} }
fileInfo = &winio.FileBasicInfo{ fileInfo = &winio.FileBasicInfo{
LastAccessTime: win32TimeFromTar(xattrAccessTime, hdr.Xattrs, hdr.AccessTime), LastAccessTime: win32TimeFromTar(hdrAccessTime, hdr.Winheaders, hdr.AccessTime),
LastWriteTime: win32TimeFromTar(xattrWriteTime, hdr.Xattrs, hdr.ModTime), LastWriteTime: win32TimeFromTar(hdrWriteTime, hdr.Winheaders, hdr.ModTime),
ChangeTime: win32TimeFromTar(xattrChangeTime, hdr.Xattrs, hdr.ChangeTime), ChangeTime: win32TimeFromTar(hdrChangeTime, hdr.Winheaders, hdr.ChangeTime),
CreationTime: win32TimeFromTar(xattrCreateTime, hdr.Xattrs, hdr.ModTime), 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) attr, err := strconv.ParseUint(attrStr, 10, 32)
if err != nil { if err != nil {
return "", 0, nil, err 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. // 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) { func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
bw := winio.NewBackupStreamWriter(w) bw := winio.NewBackupStreamWriter(w)
if sddl, ok := hdr.Xattrs[xattrSecurityDescriptor]; ok { if sddl, ok := hdr.Winheaders[hdrSecurityDescriptor]; ok {
sd, err := winio.SddlToSecurityDescriptor(sddl) sd, err := winio.SddlToSecurityDescriptor(sddl)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -304,7 +303,7 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (
} }
} }
if hdr.Typeflag == tar.TypeSymlink { if hdr.Typeflag == tar.TypeSymlink {
_, isMountPoint := hdr.Xattrs[xattrMountPoint] _, isMountPoint := hdr.Winheaders[hdrMountPoint]
rp := winio.ReparsePoint{ rp := winio.ReparsePoint{
Target: hdr.Linkname, Target: hdr.Linkname,
IsMountPoint: isMountPoint, IsMountPoint: isMountPoint,
+65
View File
@@ -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")
}