Blob Blame History Raw
# HG changeset patch
# User Alexander Larsson <alexander.larsson@gmail.com>
# 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 <dsymonds@golang.org>

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)