forked from mirrors/tar-split
440ba9e519
Convert splitUSTARPath to return a bool rather than an error since the caller never ever uses the error other than to check if it is nil. Thus, we can remove errNameTooLong as well. Also, fold the checking of the length <= fileNameSize and whether the string is ASCII into the split function itself. Lastly, remove logic to set the MAGIC since that's already done on L200. Thus, setting the magic is redundant. There is no overall logic change. Updates #12638 Change-Id: I26b6992578199abad723c2a2af7f4fc078af9c17 Reviewed-on: https://go-review.googlesource.com/14723 Reviewed-by: David Symonds <dsymonds@golang.org> Run-TryBot: David Symonds <dsymonds@golang.org>
580 lines
15 KiB
Go
580 lines
15 KiB
Go
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package tar
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"testing/iotest"
|
|
"time"
|
|
)
|
|
|
|
type writerTestEntry struct {
|
|
header *Header
|
|
contents string
|
|
}
|
|
|
|
type writerTest struct {
|
|
file string // filename of expected output
|
|
entries []*writerTestEntry
|
|
}
|
|
|
|
var writerTests = []*writerTest{
|
|
// The writer test file was produced with this command:
|
|
// tar (GNU tar) 1.26
|
|
// ln -s small.txt link.txt
|
|
// tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
|
|
{
|
|
file: "testdata/writer.tar",
|
|
entries: []*writerTestEntry{
|
|
{
|
|
header: &Header{
|
|
Name: "small.txt",
|
|
Mode: 0640,
|
|
Uid: 73025,
|
|
Gid: 5000,
|
|
Size: 5,
|
|
ModTime: time.Unix(1246508266, 0),
|
|
Typeflag: '0',
|
|
Uname: "dsymonds",
|
|
Gname: "eng",
|
|
},
|
|
contents: "Kilts",
|
|
},
|
|
{
|
|
header: &Header{
|
|
Name: "small2.txt",
|
|
Mode: 0640,
|
|
Uid: 73025,
|
|
Gid: 5000,
|
|
Size: 11,
|
|
ModTime: time.Unix(1245217492, 0),
|
|
Typeflag: '0',
|
|
Uname: "dsymonds",
|
|
Gname: "eng",
|
|
},
|
|
contents: "Google.com\n",
|
|
},
|
|
{
|
|
header: &Header{
|
|
Name: "link.txt",
|
|
Mode: 0777,
|
|
Uid: 1000,
|
|
Gid: 1000,
|
|
Size: 0,
|
|
ModTime: time.Unix(1314603082, 0),
|
|
Typeflag: '2',
|
|
Linkname: "small.txt",
|
|
Uname: "strings",
|
|
Gname: "strings",
|
|
},
|
|
// no contents
|
|
},
|
|
},
|
|
},
|
|
// The truncated test file was produced using these commands:
|
|
// dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
|
|
// tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
|
|
{
|
|
file: "testdata/writer-big.tar",
|
|
entries: []*writerTestEntry{
|
|
{
|
|
header: &Header{
|
|
Name: "tmp/16gig.txt",
|
|
Mode: 0640,
|
|
Uid: 73025,
|
|
Gid: 5000,
|
|
Size: 16 << 30,
|
|
ModTime: time.Unix(1254699560, 0),
|
|
Typeflag: '0',
|
|
Uname: "dsymonds",
|
|
Gname: "eng",
|
|
},
|
|
// fake contents
|
|
contents: strings.Repeat("\x00", 4<<10),
|
|
},
|
|
},
|
|
},
|
|
// The truncated test file was produced using these commands:
|
|
// dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt
|
|
// tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar
|
|
{
|
|
file: "testdata/writer-big-long.tar",
|
|
entries: []*writerTestEntry{
|
|
{
|
|
header: &Header{
|
|
Name: strings.Repeat("longname/", 15) + "16gig.txt",
|
|
Mode: 0644,
|
|
Uid: 1000,
|
|
Gid: 1000,
|
|
Size: 16 << 30,
|
|
ModTime: time.Unix(1399583047, 0),
|
|
Typeflag: '0',
|
|
Uname: "guillaume",
|
|
Gname: "guillaume",
|
|
},
|
|
// fake contents
|
|
contents: strings.Repeat("\x00", 4<<10),
|
|
},
|
|
},
|
|
},
|
|
// This file was produced using gnu tar 1.17
|
|
// gnutar -b 4 --format=ustar (longname/)*15 + file.txt
|
|
{
|
|
file: "testdata/ustar.tar",
|
|
entries: []*writerTestEntry{
|
|
{
|
|
header: &Header{
|
|
Name: strings.Repeat("longname/", 15) + "file.txt",
|
|
Mode: 0644,
|
|
Uid: 0765,
|
|
Gid: 024,
|
|
Size: 06,
|
|
ModTime: time.Unix(1360135598, 0),
|
|
Typeflag: '0',
|
|
Uname: "shane",
|
|
Gname: "staff",
|
|
},
|
|
contents: "hello\n",
|
|
},
|
|
},
|
|
},
|
|
// This file was produced using gnu tar 1.26
|
|
// echo "Slartibartfast" > file.txt
|
|
// ln file.txt hard.txt
|
|
// tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt
|
|
{
|
|
file: "testdata/hardlink.tar",
|
|
entries: []*writerTestEntry{
|
|
{
|
|
header: &Header{
|
|
Name: "file.txt",
|
|
Mode: 0644,
|
|
Uid: 1000,
|
|
Gid: 100,
|
|
Size: 15,
|
|
ModTime: time.Unix(1425484303, 0),
|
|
Typeflag: '0',
|
|
Uname: "vbatts",
|
|
Gname: "users",
|
|
},
|
|
contents: "Slartibartfast\n",
|
|
},
|
|
{
|
|
header: &Header{
|
|
Name: "hard.txt",
|
|
Mode: 0644,
|
|
Uid: 1000,
|
|
Gid: 100,
|
|
Size: 0,
|
|
ModTime: time.Unix(1425484303, 0),
|
|
Typeflag: '1',
|
|
Linkname: "file.txt",
|
|
Uname: "vbatts",
|
|
Gname: "users",
|
|
},
|
|
// no contents
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Render byte array in a two-character hexadecimal string, spaced for easy visual inspection.
|
|
func bytestr(offset int, b []byte) string {
|
|
const rowLen = 32
|
|
s := fmt.Sprintf("%04x ", offset)
|
|
for _, ch := range b {
|
|
switch {
|
|
case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z':
|
|
s += fmt.Sprintf(" %c", ch)
|
|
default:
|
|
s += fmt.Sprintf(" %02x", ch)
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Render a pseudo-diff between two blocks of bytes.
|
|
func bytediff(a []byte, b []byte) string {
|
|
const rowLen = 32
|
|
s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b))
|
|
for offset := 0; len(a)+len(b) > 0; offset += rowLen {
|
|
na, nb := rowLen, rowLen
|
|
if na > len(a) {
|
|
na = len(a)
|
|
}
|
|
if nb > len(b) {
|
|
nb = len(b)
|
|
}
|
|
sa := bytestr(offset, a[0:na])
|
|
sb := bytestr(offset, b[0:nb])
|
|
if sa != sb {
|
|
s += fmt.Sprintf("-%v\n+%v\n", sa, sb)
|
|
}
|
|
a = a[na:]
|
|
b = b[nb:]
|
|
}
|
|
return s
|
|
}
|
|
|
|
func TestWriter(t *testing.T) {
|
|
testLoop:
|
|
for i, test := range writerTests {
|
|
expected, err := ioutil.ReadFile(test.file)
|
|
if err != nil {
|
|
t.Errorf("test %d: Unexpected error: %v", i, err)
|
|
continue
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB
|
|
big := false
|
|
for j, entry := range test.entries {
|
|
big = big || entry.header.Size > 1<<10
|
|
if err := tw.WriteHeader(entry.header); err != nil {
|
|
t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err)
|
|
continue testLoop
|
|
}
|
|
if _, err := io.WriteString(tw, entry.contents); err != nil {
|
|
t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err)
|
|
continue testLoop
|
|
}
|
|
}
|
|
// Only interested in Close failures for the small tests.
|
|
if err := tw.Close(); err != nil && !big {
|
|
t.Errorf("test %d: Failed closing archive: %v", i, err)
|
|
continue testLoop
|
|
}
|
|
|
|
actual := buf.Bytes()
|
|
if !bytes.Equal(expected, actual) {
|
|
t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v",
|
|
i, bytediff(expected, actual))
|
|
}
|
|
if testing.Short() { // The second test is expensive.
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPax(t *testing.T) {
|
|
// Create an archive with a large name
|
|
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)
|
|
}
|
|
// Force a PAX long name to be written
|
|
longName := strings.Repeat("ab", 100)
|
|
contents := strings.Repeat(" ", int(hdr.Size))
|
|
hdr.Name = longName
|
|
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)
|
|
}
|
|
// Simple test to make sure PAX extensions are in effect
|
|
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
|
|
t.Fatal("Expected at least one PAX header to be written.")
|
|
}
|
|
// Test that we can get a long name back out of the archive.
|
|
reader := NewReader(&buf)
|
|
hdr, err = reader.Next()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if hdr.Name != longName {
|
|
t.Fatal("Couldn't recover long file name")
|
|
}
|
|
}
|
|
|
|
func TestPaxSymlink(t *testing.T) {
|
|
// Create an archive with a large linkname
|
|
fileinfo, err := os.Stat("testdata/small.txt")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hdr, err := FileInfoHeader(fileinfo, "")
|
|
hdr.Typeflag = TypeSymlink
|
|
if err != nil {
|
|
t.Fatalf("os.Stat:1 %v", err)
|
|
}
|
|
// Force a PAX long linkname to be written
|
|
longLinkname := strings.Repeat("1234567890/1234567890", 10)
|
|
hdr.Linkname = longLinkname
|
|
|
|
hdr.Size = 0
|
|
var buf bytes.Buffer
|
|
writer := NewWriter(&buf)
|
|
if err := writer.WriteHeader(hdr); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := writer.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Simple test to make sure PAX extensions are in effect
|
|
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
|
|
t.Fatal("Expected at least one PAX header to be written.")
|
|
}
|
|
// Test that we can get a long name back out of the archive.
|
|
reader := NewReader(&buf)
|
|
hdr, err = reader.Next()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if hdr.Linkname != longLinkname {
|
|
t.Fatal("Couldn't recover long link name")
|
|
}
|
|
}
|
|
|
|
func TestPaxNonAscii(t *testing.T) {
|
|
// Create an archive with non ascii. These should trigger a pax header
|
|
// because pax headers have a defined utf-8 encoding.
|
|
fileinfo, err := os.Stat("testdata/small.txt")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
hdr, err := FileInfoHeader(fileinfo, "")
|
|
if err != nil {
|
|
t.Fatalf("os.Stat:1 %v", err)
|
|
}
|
|
|
|
// some sample data
|
|
chineseFilename := "文件名"
|
|
chineseGroupname := "組"
|
|
chineseUsername := "用戶名"
|
|
|
|
hdr.Name = chineseFilename
|
|
hdr.Gname = chineseGroupname
|
|
hdr.Uname = chineseUsername
|
|
|
|
contents := strings.Repeat(" ", int(hdr.Size))
|
|
|
|
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)
|
|
}
|
|
// Simple test to make sure PAX extensions are in effect
|
|
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
|
|
t.Fatal("Expected at least one PAX header to be written.")
|
|
}
|
|
// Test that we can get a long name back out of the archive.
|
|
reader := NewReader(&buf)
|
|
hdr, err = reader.Next()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if hdr.Name != chineseFilename {
|
|
t.Fatal("Couldn't recover unicode name")
|
|
}
|
|
if hdr.Gname != chineseGroupname {
|
|
t.Fatal("Couldn't recover unicode group")
|
|
}
|
|
if hdr.Uname != chineseUsername {
|
|
t.Fatal("Couldn't recover unicode user")
|
|
}
|
|
}
|
|
|
|
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)
|
|
paxTests := [][2]string{
|
|
{paxPath + "=/etc/hosts", "19 path=/etc/hosts\n"},
|
|
{"a=b", "6 a=b\n"}, // Single digit length
|
|
{"a=names", "11 a=names\n"}, // Test case involving carries
|
|
{paxPath + "=" + longName, fmt.Sprintf("210 path=%s\n", longName)},
|
|
{paxPath + "=" + medName, fmt.Sprintf("110 path=%s\n", medName)}}
|
|
|
|
for _, test := range paxTests {
|
|
key, expected := test[0], test[1]
|
|
if result := paxHeader(key); result != expected {
|
|
t.Fatalf("paxHeader: got %s, expected %s", result, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUSTARLongName(t *testing.T) {
|
|
// Create an archive with a path that failed to split with USTAR extension in previous versions.
|
|
fileinfo, err := os.Stat("testdata/small.txt")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hdr, err := FileInfoHeader(fileinfo, "")
|
|
hdr.Typeflag = TypeDir
|
|
if err != nil {
|
|
t.Fatalf("os.Stat:1 %v", err)
|
|
}
|
|
// Force a PAX long name to be written. The name was taken from a practical example
|
|
// that fails and replaced ever char through numbers to anonymize the sample.
|
|
longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/"
|
|
hdr.Name = longName
|
|
|
|
hdr.Size = 0
|
|
var buf bytes.Buffer
|
|
writer := NewWriter(&buf)
|
|
if err := writer.WriteHeader(hdr); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := writer.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Test that we can get a long name back out of the archive.
|
|
reader := NewReader(&buf)
|
|
hdr, err = reader.Next()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if hdr.Name != longName {
|
|
t.Fatal("Couldn't recover long name")
|
|
}
|
|
}
|
|
|
|
func TestValidTypeflagWithPAXHeader(t *testing.T) {
|
|
var buffer bytes.Buffer
|
|
tw := NewWriter(&buffer)
|
|
|
|
fileName := strings.Repeat("ab", 100)
|
|
|
|
hdr := &Header{
|
|
Name: fileName,
|
|
Size: 4,
|
|
Typeflag: 0,
|
|
}
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
t.Fatalf("Failed to write header: %s", err)
|
|
}
|
|
if _, err := tw.Write([]byte("fooo")); err != nil {
|
|
t.Fatalf("Failed to write the file's data: %s", err)
|
|
}
|
|
tw.Close()
|
|
|
|
tr := NewReader(&buffer)
|
|
|
|
for {
|
|
header, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("Failed to read header: %s", err)
|
|
}
|
|
if header.Typeflag != 0 {
|
|
t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWriteAfterClose(t *testing.T) {
|
|
var buffer bytes.Buffer
|
|
tw := NewWriter(&buffer)
|
|
|
|
hdr := &Header{
|
|
Name: "small.txt",
|
|
Size: 5,
|
|
}
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
t.Fatalf("Failed to write header: %s", err)
|
|
}
|
|
tw.Close()
|
|
if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose {
|
|
t.Fatalf("Write: got %v; want ErrWriteAfterClose", err)
|
|
}
|
|
}
|
|
|
|
func TestSplitUSTARPath(t *testing.T) {
|
|
var sr = strings.Repeat
|
|
|
|
var vectors = []struct {
|
|
input string // Input path
|
|
prefix string // Expected output prefix
|
|
suffix string // Expected output suffix
|
|
ok bool // Split success?
|
|
}{
|
|
{"", "", "", false},
|
|
{"abc", "", "", false},
|
|
{"用戶名", "", "", false},
|
|
{sr("a", fileNameSize), "", "", false},
|
|
{sr("a", fileNameSize) + "/", "", "", false},
|
|
{sr("a", fileNameSize) + "/a", sr("a", fileNameSize), "a", true},
|
|
{sr("a", fileNamePrefixSize) + "/", "", "", false},
|
|
{sr("a", fileNamePrefixSize) + "/a", sr("a", fileNamePrefixSize), "a", true},
|
|
{sr("a", fileNameSize+1), "", "", false},
|
|
{sr("/", fileNameSize+1), sr("/", fileNameSize-1), "/", true},
|
|
{sr("a", fileNamePrefixSize) + "/" + sr("b", fileNameSize),
|
|
sr("a", fileNamePrefixSize), sr("b", fileNameSize), true},
|
|
{sr("a", fileNamePrefixSize) + "//" + sr("b", fileNameSize), "", "", false},
|
|
{sr("a/", fileNameSize), sr("a/", 77) + "a", sr("a/", 22), true},
|
|
}
|
|
|
|
for _, v := range vectors {
|
|
prefix, suffix, ok := splitUSTARPath(v.input)
|
|
if prefix != v.prefix || suffix != v.suffix || ok != v.ok {
|
|
t.Errorf("splitUSTARPath(%q):\ngot (%q, %q, %v)\nwant (%q, %q, %v)",
|
|
v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok)
|
|
}
|
|
}
|
|
}
|