# HG changeset patch # User Alexander Larsson # Date 1392282510 -39600 # Node ID a15f344a9efa35ef168c8feaa92a15a1cdc93db5 # Parent 1a32fe60e0798d82bbff6c945001c7f0ba8de5ea 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 issue 7154. LGTM=dsymonds R=golang-codereviews, gobot, dsymonds CC=golang-codereviews https://codereview.appspot.com/54570043 Committer: David Symonds diff -r 1a32fe60e079 -r a15f344a9efa src/pkg/archive/tar/common.go --- a/src/pkg/archive/tar/common.go Thu Feb 13 03:09:03 2014 -0500 +++ b/src/pkg/archive/tar/common.go Thu Feb 13 20:08:30 2014 +1100 @@ -57,6 +57,7 @@ Devminor int64 // minor number of character or block device AccessTime time.Time // access time ChangeTime time.Time // status change time + Xattrs map[string]string } // File name constants from the tar spec. @@ -189,6 +190,7 @@ paxSize = "size" paxUid = "uid" paxUname = "uname" + paxXattr = "SCHILY.xattr." paxNone = "" ) diff -r 1a32fe60e079 -r a15f344a9efa src/pkg/archive/tar/reader.go --- a/src/pkg/archive/tar/reader.go Thu Feb 13 03:09:03 2014 -0500 +++ b/src/pkg/archive/tar/reader.go Thu Feb 13 20:08:30 2014 +1100 @@ -139,8 +139,14 @@ return err } 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 } diff -r 1a32fe60e079 -r a15f344a9efa src/pkg/archive/tar/reader_test.go --- a/src/pkg/archive/tar/reader_test.go Thu Feb 13 03:09:03 2014 -0500 +++ b/src/pkg/archive/tar/reader_test.go Thu Feb 13 20:08:30 2014 +1100 @@ -161,6 +161,46 @@ }, }, }, + { + 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) { @@ -180,7 +220,7 @@ f.Close() continue testLoop } - if *hdr != *header { + if !reflect.DeepEqual(*hdr, *header) { t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v", i, j, *hdr, *header) } @@ -253,7 +293,7 @@ } // check the header - if *hdr != *headers[nread] { + if !reflect.DeepEqual(*hdr, *headers[nread]) { t.Errorf("Incorrect header:\nhave %+v\nwant %+v", *hdr, headers[nread]) } diff -r 1a32fe60e079 -r a15f344a9efa src/pkg/archive/tar/writer.go --- a/src/pkg/archive/tar/writer.go Thu Feb 13 03:09:03 2014 -0500 +++ b/src/pkg/archive/tar/writer.go Thu Feb 13 20:08:30 2014 +1100 @@ -236,6 +236,12 @@ return tw.err } + if allowPax { + for k, v := range hdr.Xattrs { + paxHeaders[paxXattr+k] = v + } + } + if len(paxHeaders) > 0 { if !allowPax { return errInvalidHeader diff -r 1a32fe60e079 -r a15f344a9efa src/pkg/archive/tar/writer_test.go --- a/src/pkg/archive/tar/writer_test.go Thu Feb 13 03:09:03 2014 -0500 +++ b/src/pkg/archive/tar/writer_test.go Thu Feb 13 20:08:30 2014 +1100 @@ -10,6 +10,7 @@ "io" "io/ioutil" "os" + "reflect" "strings" "testing" "testing/iotest" @@ -338,6 +339,45 @@ } } +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) { medName := strings.Repeat("CD", 50) longName := strings.Repeat("AB", 100)