Skip to content

Commit 87d58f4

Browse files
alexlarssondsymonds
authored andcommitted
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
1 parent ca6186a commit 87d58f4

File tree

6 files changed

+97
-3
lines changed

6 files changed

+97
-3
lines changed

src/pkg/archive/tar/common.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type Header struct {
5757
Devminor int64 // minor number of character or block device
5858
AccessTime time.Time // access time
5959
ChangeTime time.Time // status change time
60+
Xattrs map[string]string
6061
}
6162

6263
// File name constants from the tar spec.
@@ -189,6 +190,7 @@ const (
189190
paxSize = "size"
190191
paxUid = "uid"
191192
paxUname = "uname"
193+
paxXattr = "SCHILY.xattr."
192194
paxNone = ""
193195
)
194196

src/pkg/archive/tar/reader.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,14 @@ func mergePAX(hdr *Header, headers map[string]string) error {
139139
return err
140140
}
141141
hdr.Size = int64(size)
142+
default:
143+
if strings.HasPrefix(k, paxXattr) {
144+
if hdr.Xattrs == nil {
145+
hdr.Xattrs = make(map[string]string)
146+
}
147+
hdr.Xattrs[k[len(paxXattr):]] = v
148+
}
142149
}
143-
144150
}
145151
return nil
146152
}

src/pkg/archive/tar/reader_test.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,46 @@ var untarTests = []*untarTest{
161161
},
162162
},
163163
},
164+
{
165+
file: "testdata/xattrs.tar",
166+
headers: []*Header{
167+
{
168+
Name: "small.txt",
169+
Mode: 0644,
170+
Uid: 1000,
171+
Gid: 10,
172+
Size: 5,
173+
ModTime: time.Unix(1386065770, 448252320),
174+
Typeflag: '0',
175+
Uname: "alex",
176+
Gname: "wheel",
177+
AccessTime: time.Unix(1389782991, 419875220),
178+
ChangeTime: time.Unix(1389782956, 794414986),
179+
Xattrs: map[string]string{
180+
"user.key": "value",
181+
"user.key2": "value2",
182+
// Interestingly, selinux encodes the terminating null inside the xattr
183+
"security.selinux": "unconfined_u:object_r:default_t:s0\x00",
184+
},
185+
},
186+
{
187+
Name: "small2.txt",
188+
Mode: 0644,
189+
Uid: 1000,
190+
Gid: 10,
191+
Size: 11,
192+
ModTime: time.Unix(1386065770, 449252304),
193+
Typeflag: '0',
194+
Uname: "alex",
195+
Gname: "wheel",
196+
AccessTime: time.Unix(1389782991, 419875220),
197+
ChangeTime: time.Unix(1386065770, 449252304),
198+
Xattrs: map[string]string{
199+
"security.selinux": "unconfined_u:object_r:default_t:s0\x00",
200+
},
201+
},
202+
},
203+
},
164204
}
165205

166206
func TestReader(t *testing.T) {
@@ -180,7 +220,7 @@ testLoop:
180220
f.Close()
181221
continue testLoop
182222
}
183-
if *hdr != *header {
223+
if !reflect.DeepEqual(*hdr, *header) {
184224
t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v",
185225
i, j, *hdr, *header)
186226
}
@@ -253,7 +293,7 @@ func TestIncrementalRead(t *testing.T) {
253293
}
254294

255295
// check the header
256-
if *hdr != *headers[nread] {
296+
if !reflect.DeepEqual(*hdr, *headers[nread]) {
257297
t.Errorf("Incorrect header:\nhave %+v\nwant %+v",
258298
*hdr, headers[nread])
259299
}
5 KB
Binary file not shown.

src/pkg/archive/tar/writer.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,12 @@ func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error {
236236
return tw.err
237237
}
238238

239+
if allowPax {
240+
for k, v := range hdr.Xattrs {
241+
paxHeaders[paxXattr+k] = v
242+
}
243+
}
244+
239245
if len(paxHeaders) > 0 {
240246
if !allowPax {
241247
return errInvalidHeader

src/pkg/archive/tar/writer_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"io"
1111
"io/ioutil"
1212
"os"
13+
"reflect"
1314
"strings"
1415
"testing"
1516
"testing/iotest"
@@ -338,6 +339,45 @@ func TestPaxNonAscii(t *testing.T) {
338339
}
339340
}
340341

342+
func TestPaxXattrs(t *testing.T) {
343+
xattrs := map[string]string{
344+
"user.key": "value",
345+
}
346+
347+
// Create an archive with an xattr
348+
fileinfo, err := os.Stat("testdata/small.txt")
349+
if err != nil {
350+
t.Fatal(err)
351+
}
352+
hdr, err := FileInfoHeader(fileinfo, "")
353+
if err != nil {
354+
t.Fatalf("os.Stat: %v", err)
355+
}
356+
contents := "Kilts"
357+
hdr.Xattrs = xattrs
358+
var buf bytes.Buffer
359+
writer := NewWriter(&buf)
360+
if err := writer.WriteHeader(hdr); err != nil {
361+
t.Fatal(err)
362+
}
363+
if _, err = writer.Write([]byte(contents)); err != nil {
364+
t.Fatal(err)
365+
}
366+
if err := writer.Close(); err != nil {
367+
t.Fatal(err)
368+
}
369+
// Test that we can get the xattrs back out of the archive.
370+
reader := NewReader(&buf)
371+
hdr, err = reader.Next()
372+
if err != nil {
373+
t.Fatal(err)
374+
}
375+
if !reflect.DeepEqual(hdr.Xattrs, xattrs) {
376+
t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
377+
hdr.Xattrs, xattrs)
378+
}
379+
}
380+
341381
func TestPAXHeader(t *testing.T) {
342382
medName := strings.Repeat("CD", 50)
343383
longName := strings.Repeat("AB", 100)

0 commit comments

Comments
 (0)