From 87d58f44a1e53a61c7bd4b11a7f7aa616f6e373d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 13 Feb 2014 20:08:30 +1100 Subject: [PATCH] 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 --- src/pkg/archive/tar/common.go | 2 ++ src/pkg/archive/tar/reader.go | 8 ++++- src/pkg/archive/tar/reader_test.go | 44 ++++++++++++++++++++++-- src/pkg/archive/tar/testdata/xattrs.tar | Bin 0 -> 5120 bytes src/pkg/archive/tar/writer.go | 6 ++++ src/pkg/archive/tar/writer_test.go | 40 +++++++++++++++++++++ 6 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 src/pkg/archive/tar/testdata/xattrs.tar diff --git a/src/pkg/archive/tar/common.go b/src/pkg/archive/tar/common.go index 1b961e3ec6..e8b973c1fa 100644 --- a/src/pkg/archive/tar/common.go +++ b/src/pkg/archive/tar/common.go @@ -57,6 +57,7 @@ type Header struct { 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 @@ const ( paxSize = "size" paxUid = "uid" paxUname = "uname" + paxXattr = "SCHILY.xattr." paxNone = "" ) diff --git a/src/pkg/archive/tar/reader.go b/src/pkg/archive/tar/reader.go index b2d62f3c51..7cb6e649c7 100644 --- a/src/pkg/archive/tar/reader.go +++ b/src/pkg/archive/tar/reader.go @@ -139,8 +139,14 @@ func mergePAX(hdr *Header, headers map[string]string) error { 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 --git a/src/pkg/archive/tar/reader_test.go b/src/pkg/archive/tar/reader_test.go index acd45410e3..f84dbebe98 100644 --- a/src/pkg/archive/tar/reader_test.go +++ b/src/pkg/archive/tar/reader_test.go @@ -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) { @@ -180,7 +220,7 @@ testLoop: 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 @@ func TestIncrementalRead(t *testing.T) { } // 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 --git a/src/pkg/archive/tar/testdata/xattrs.tar b/src/pkg/archive/tar/testdata/xattrs.tar new file mode 100644 index 0000000000000000000000000000000000000000..9701950edd1f0dc82858b7117136b37391be0b08 GIT binary patch literal 5120 zcmeHJv2KGf5M|~o_yWg1+khiw>d;i}P^nX=$R$ooYd`|ilD{uBAv6g^kxC>6-(uu< zHg^v_-l5r}td>fyRbC(vfcdOQq}Iq(#u+Ja9X?}Dv(|CCVoJF~09ZgF;2a!G7^%~| zYNYoMUQ-rE=5KzzBJ^EKyr-Mx-NQ4gq%k=v3zee}wOxElT`HH-ei(K*xV|_} zC{$GDvDuoW?o>&odUrVuVHkt_w?IH zW3PV_@V!Jxt@A^i>Yrj(>;K=H?5X8!tJS~MYVd#a^`?|QJKb&Uduf~MfN4M7$J!Lr zF40zZMF!9x{tqJ#0F5+;{2!=)=Knre|G(mAKU`hAc#r>!#{V(9d;sW1hxVv7@B_zF ze)#eKF~#1~>@WTI`#+&4`lkel_5U6!N8h^5vRAE8lqGgr9-Ul!p=H1_U>TS&1K)l2 B)fNB% literal 0 HcmV?d00001 diff --git a/src/pkg/archive/tar/writer.go b/src/pkg/archive/tar/writer.go index 549f1464c3..9ee9499297 100644 --- a/src/pkg/archive/tar/writer.go +++ b/src/pkg/archive/tar/writer.go @@ -236,6 +236,12 @@ func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error { return tw.err } + if allowPax { + for k, v := range hdr.Xattrs { + paxHeaders[paxXattr+k] = v + } + } + if len(paxHeaders) > 0 { if !allowPax { return errInvalidHeader diff --git a/src/pkg/archive/tar/writer_test.go b/src/pkg/archive/tar/writer_test.go index 30ebf977ac..2b9ea658db 100644 --- a/src/pkg/archive/tar/writer_test.go +++ b/src/pkg/archive/tar/writer_test.go @@ -10,6 +10,7 @@ import ( "io" "io/ioutil" "os" + "reflect" "strings" "testing" "testing/iotest" @@ -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) { medName := strings.Repeat("CD", 50) longName := strings.Repeat("AB", 100) -- 2.48.1