Commit 87d58f44 authored by Alexander Larsson's avatar Alexander Larsson Committed by David Symonds

archive/tar: support extended attributes

This adds support for archives with the SCHILY.xattr field in the
pax header. This is what gnu tar and star generate.
Fixes #7154.

LGTM=dsymonds
R=golang-codereviews, gobot, dsymonds
CC=golang-codereviews
https://golang.org/cl/54570043
parent ca6186aa
...@@ -57,6 +57,7 @@ type Header struct { ...@@ -57,6 +57,7 @@ type Header struct {
Devminor int64 // minor number of character or block device Devminor int64 // minor number of character or block device
AccessTime time.Time // access time AccessTime time.Time // access time
ChangeTime time.Time // status change time ChangeTime time.Time // status change time
Xattrs map[string]string
} }
// File name constants from the tar spec. // File name constants from the tar spec.
...@@ -189,6 +190,7 @@ const ( ...@@ -189,6 +190,7 @@ const (
paxSize = "size" paxSize = "size"
paxUid = "uid" paxUid = "uid"
paxUname = "uname" paxUname = "uname"
paxXattr = "SCHILY.xattr."
paxNone = "" paxNone = ""
) )
......
...@@ -139,8 +139,14 @@ func mergePAX(hdr *Header, headers map[string]string) error { ...@@ -139,8 +139,14 @@ func mergePAX(hdr *Header, headers map[string]string) error {
return err return err
} }
hdr.Size = int64(size) hdr.Size = int64(size)
default:
if strings.HasPrefix(k, paxXattr) {
if hdr.Xattrs == nil {
hdr.Xattrs = make(map[string]string)
}
hdr.Xattrs[k[len(paxXattr):]] = v
}
} }
} }
return nil return nil
} }
......
...@@ -161,6 +161,46 @@ var untarTests = []*untarTest{ ...@@ -161,6 +161,46 @@ var untarTests = []*untarTest{
}, },
}, },
}, },
{
file: "testdata/xattrs.tar",
headers: []*Header{
{
Name: "small.txt",
Mode: 0644,
Uid: 1000,
Gid: 10,
Size: 5,
ModTime: time.Unix(1386065770, 448252320),
Typeflag: '0',
Uname: "alex",
Gname: "wheel",
AccessTime: time.Unix(1389782991, 419875220),
ChangeTime: time.Unix(1389782956, 794414986),
Xattrs: map[string]string{
"user.key": "value",
"user.key2": "value2",
// Interestingly, selinux encodes the terminating null inside the xattr
"security.selinux": "unconfined_u:object_r:default_t:s0\x00",
},
},
{
Name: "small2.txt",
Mode: 0644,
Uid: 1000,
Gid: 10,
Size: 11,
ModTime: time.Unix(1386065770, 449252304),
Typeflag: '0',
Uname: "alex",
Gname: "wheel",
AccessTime: time.Unix(1389782991, 419875220),
ChangeTime: time.Unix(1386065770, 449252304),
Xattrs: map[string]string{
"security.selinux": "unconfined_u:object_r:default_t:s0\x00",
},
},
},
},
} }
func TestReader(t *testing.T) { func TestReader(t *testing.T) {
...@@ -180,7 +220,7 @@ testLoop: ...@@ -180,7 +220,7 @@ testLoop:
f.Close() f.Close()
continue testLoop continue testLoop
} }
if *hdr != *header { if !reflect.DeepEqual(*hdr, *header) {
t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v", t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v",
i, j, *hdr, *header) i, j, *hdr, *header)
} }
...@@ -253,7 +293,7 @@ func TestIncrementalRead(t *testing.T) { ...@@ -253,7 +293,7 @@ func TestIncrementalRead(t *testing.T) {
} }
// check the header // check the header
if *hdr != *headers[nread] { if !reflect.DeepEqual(*hdr, *headers[nread]) {
t.Errorf("Incorrect header:\nhave %+v\nwant %+v", t.Errorf("Incorrect header:\nhave %+v\nwant %+v",
*hdr, headers[nread]) *hdr, headers[nread])
} }
......
...@@ -236,6 +236,12 @@ func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error { ...@@ -236,6 +236,12 @@ func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error {
return tw.err return tw.err
} }
if allowPax {
for k, v := range hdr.Xattrs {
paxHeaders[paxXattr+k] = v
}
}
if len(paxHeaders) > 0 { if len(paxHeaders) > 0 {
if !allowPax { if !allowPax {
return errInvalidHeader return errInvalidHeader
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"reflect"
"strings" "strings"
"testing" "testing"
"testing/iotest" "testing/iotest"
...@@ -338,6 +339,45 @@ func TestPaxNonAscii(t *testing.T) { ...@@ -338,6 +339,45 @@ func TestPaxNonAscii(t *testing.T) {
} }
} }
func TestPaxXattrs(t *testing.T) {
xattrs := map[string]string{
"user.key": "value",
}
// Create an archive with an xattr
fileinfo, err := os.Stat("testdata/small.txt")
if err != nil {
t.Fatal(err)
}
hdr, err := FileInfoHeader(fileinfo, "")
if err != nil {
t.Fatalf("os.Stat: %v", err)
}
contents := "Kilts"
hdr.Xattrs = xattrs
var buf bytes.Buffer
writer := NewWriter(&buf)
if err := writer.WriteHeader(hdr); err != nil {
t.Fatal(err)
}
if _, err = writer.Write([]byte(contents)); err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
// Test that we can get the xattrs back out of the archive.
reader := NewReader(&buf)
hdr, err = reader.Next()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(hdr.Xattrs, xattrs) {
t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
hdr.Xattrs, xattrs)
}
}
func TestPAXHeader(t *testing.T) { func TestPAXHeader(t *testing.T) {
medName := strings.Repeat("CD", 50) medName := strings.Repeat("CD", 50)
longName := strings.Repeat("AB", 100) longName := strings.Repeat("AB", 100)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment