mirror of
https://github.com/vbatts/tar-split.git
synced 2024-11-15 04:58:36 +00:00
commit
b4d27b5426
10 changed files with 206 additions and 18 deletions
|
@ -139,8 +139,8 @@ func (fi headerFileInfo) Mode() (mode os.FileMode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch fi.h.Typeflag {
|
switch fi.h.Typeflag {
|
||||||
case TypeLink, TypeSymlink:
|
case TypeSymlink:
|
||||||
// hard link, symbolic link
|
// symbolic link
|
||||||
mode |= os.ModeSymlink
|
mode |= os.ModeSymlink
|
||||||
case TypeChar:
|
case TypeChar:
|
||||||
// character device node
|
// character device node
|
||||||
|
@ -249,6 +249,30 @@ func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
|
||||||
if fm&os.ModeSticky != 0 {
|
if fm&os.ModeSticky != 0 {
|
||||||
h.Mode |= c_ISVTX
|
h.Mode |= c_ISVTX
|
||||||
}
|
}
|
||||||
|
// If possible, populate additional fields from OS-specific
|
||||||
|
// FileInfo fields.
|
||||||
|
if sys, ok := fi.Sys().(*Header); ok {
|
||||||
|
// This FileInfo came from a Header (not the OS). Use the
|
||||||
|
// original Header to populate all remaining fields.
|
||||||
|
h.Uid = sys.Uid
|
||||||
|
h.Gid = sys.Gid
|
||||||
|
h.Uname = sys.Uname
|
||||||
|
h.Gname = sys.Gname
|
||||||
|
h.AccessTime = sys.AccessTime
|
||||||
|
h.ChangeTime = sys.ChangeTime
|
||||||
|
if sys.Xattrs != nil {
|
||||||
|
h.Xattrs = make(map[string]string)
|
||||||
|
for k, v := range sys.Xattrs {
|
||||||
|
h.Xattrs[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sys.Typeflag == TypeLink {
|
||||||
|
// hard link
|
||||||
|
h.Typeflag = TypeLink
|
||||||
|
h.Size = 0
|
||||||
|
h.Linkname = sys.Linkname
|
||||||
|
}
|
||||||
|
}
|
||||||
if sysStat != nil {
|
if sysStat != nil {
|
||||||
return h, sysStat(fi, h)
|
return h, sysStat(fi, h)
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,13 @@ func (tr *Reader) Next() (*Header, error) {
|
||||||
// We actually read the whole file,
|
// We actually read the whole file,
|
||||||
// but this skips alignment padding
|
// but this skips alignment padding
|
||||||
tr.skipUnread()
|
tr.skipUnread()
|
||||||
|
if tr.err != nil {
|
||||||
|
return nil, tr.err
|
||||||
|
}
|
||||||
hdr = tr.readHeader()
|
hdr = tr.readHeader()
|
||||||
|
if hdr == nil {
|
||||||
|
return nil, tr.err
|
||||||
|
}
|
||||||
mergePAX(hdr, headers)
|
mergePAX(hdr, headers)
|
||||||
|
|
||||||
// Check for a PAX format sparse file
|
// Check for a PAX format sparse file
|
||||||
|
@ -397,7 +403,7 @@ func parsePAX(r io.Reader) (map[string]string, error) {
|
||||||
}
|
}
|
||||||
// Parse the first token as a decimal integer.
|
// Parse the first token as a decimal integer.
|
||||||
n, err := strconv.ParseInt(string(buf[:sp]), 10, 0)
|
n, err := strconv.ParseInt(string(buf[:sp]), 10, 0)
|
||||||
if err != nil {
|
if err != nil || n < 5 || int64(len(buf)) < n {
|
||||||
return nil, ErrHeader
|
return nil, ErrHeader
|
||||||
}
|
}
|
||||||
// Extract everything between the decimal and the n -1 on the
|
// Extract everything between the decimal and the n -1 on the
|
||||||
|
@ -553,6 +559,10 @@ func (tr *Reader) readHeader() *Header {
|
||||||
hdr.Uid = int(tr.octal(s.next(8)))
|
hdr.Uid = int(tr.octal(s.next(8)))
|
||||||
hdr.Gid = int(tr.octal(s.next(8)))
|
hdr.Gid = int(tr.octal(s.next(8)))
|
||||||
hdr.Size = tr.octal(s.next(12))
|
hdr.Size = tr.octal(s.next(12))
|
||||||
|
if hdr.Size < 0 {
|
||||||
|
tr.err = ErrHeader
|
||||||
|
return nil
|
||||||
|
}
|
||||||
hdr.ModTime = time.Unix(tr.octal(s.next(12)), 0)
|
hdr.ModTime = time.Unix(tr.octal(s.next(12)), 0)
|
||||||
s.next(8) // chksum
|
s.next(8) // chksum
|
||||||
hdr.Typeflag = s.next(1)[0]
|
hdr.Typeflag = s.next(1)[0]
|
||||||
|
@ -895,6 +905,9 @@ func (sfr *sparseFileReader) Read(b []byte) (n int, err error) {
|
||||||
// Otherwise, we're at the end of the file
|
// Otherwise, we're at the end of the file
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
if sfr.tot < sfr.sp[0].offset {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
if sfr.pos < sfr.sp[0].offset {
|
if sfr.pos < sfr.sp[0].offset {
|
||||||
// We're in a hole
|
// We're in a hole
|
||||||
n = sfr.readHole(b, sfr.sp[0].offset)
|
n = sfr.readHole(b, sfr.sp[0].offset)
|
||||||
|
|
|
@ -462,11 +462,16 @@ func TestParsePAXHeader(t *testing.T) {
|
||||||
t.Error("Buffer wasn't consumed")
|
t.Error("Buffer wasn't consumed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
badHeader := bytes.NewReader([]byte("3 somelongkey="))
|
badHeaderTests := [][]byte{
|
||||||
if _, err := parsePAX(badHeader); err != ErrHeader {
|
[]byte("3 somelongkey=\n"),
|
||||||
|
[]byte("50 tooshort=\n"),
|
||||||
|
}
|
||||||
|
for _, test := range badHeaderTests {
|
||||||
|
if _, err := parsePAX(bytes.NewReader(test)); err != ErrHeader {
|
||||||
t.Fatal("Unexpected success when parsing bad header")
|
t.Fatal("Unexpected success when parsing bad header")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParsePAXTime(t *testing.T) {
|
func TestParsePAXTime(t *testing.T) {
|
||||||
// Some valid PAX time values
|
// Some valid PAX time values
|
||||||
|
@ -741,3 +746,53 @@ func TestUninitializedRead(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Negative header size should not cause panic.
|
||||||
|
// Issues 10959 and 10960.
|
||||||
|
func TestNegativeHdrSize(t *testing.T) {
|
||||||
|
f, err := os.Open("testdata/neg-size.tar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
r := NewReader(f)
|
||||||
|
_, err = r.Next()
|
||||||
|
if err != ErrHeader {
|
||||||
|
t.Error("want ErrHeader, got", err)
|
||||||
|
}
|
||||||
|
io.Copy(ioutil.Discard, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This used to hang in (*sparseFileReader).readHole due to missing
|
||||||
|
// verification of sparse offsets against file size.
|
||||||
|
func TestIssue10968(t *testing.T) {
|
||||||
|
f, err := os.Open("testdata/issue10968.tar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
r := NewReader(f)
|
||||||
|
_, err = r.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(ioutil.Discard, r)
|
||||||
|
if err != io.ErrUnexpectedEOF {
|
||||||
|
t.Fatalf("expected %q, got %q", io.ErrUnexpectedEOF, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not panic if there are errors in header blocks after the pax header.
|
||||||
|
// Issue 11169
|
||||||
|
func TestIssue11169(t *testing.T) {
|
||||||
|
f, err := os.Open("testdata/issue11169.tar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
r := NewReader(f)
|
||||||
|
_, err = r.Next()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Unexpected success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -147,17 +147,6 @@ func TestHeaderRoundTrip(t *testing.T) {
|
||||||
},
|
},
|
||||||
fm: 0644,
|
fm: 0644,
|
||||||
},
|
},
|
||||||
// hard link.
|
|
||||||
{
|
|
||||||
h: &Header{
|
|
||||||
Name: "hard.txt",
|
|
||||||
Mode: 0644 | c_ISLNK,
|
|
||||||
Size: 0,
|
|
||||||
ModTime: time.Unix(1360600916, 0),
|
|
||||||
Typeflag: TypeLink,
|
|
||||||
},
|
|
||||||
fm: 0644 | os.ModeSymlink,
|
|
||||||
},
|
|
||||||
// symbolic link.
|
// symbolic link.
|
||||||
{
|
{
|
||||||
h: &Header{
|
h: &Header{
|
||||||
|
@ -246,6 +235,33 @@ func TestHeaderRoundTrip(t *testing.T) {
|
||||||
},
|
},
|
||||||
fm: 0600 | os.ModeSticky,
|
fm: 0600 | os.ModeSticky,
|
||||||
},
|
},
|
||||||
|
// hard link.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "hard.txt",
|
||||||
|
Mode: 0644 | c_ISREG,
|
||||||
|
Size: 0,
|
||||||
|
Linkname: "file.txt",
|
||||||
|
ModTime: time.Unix(1360600916, 0),
|
||||||
|
Typeflag: TypeLink,
|
||||||
|
},
|
||||||
|
fm: 0644,
|
||||||
|
},
|
||||||
|
// More information.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "info.txt",
|
||||||
|
Mode: 0600 | c_ISREG,
|
||||||
|
Size: 0,
|
||||||
|
Uid: 1000,
|
||||||
|
Gid: 1000,
|
||||||
|
ModTime: time.Unix(1360602540, 0),
|
||||||
|
Uname: "slartibartfast",
|
||||||
|
Gname: "users",
|
||||||
|
Typeflag: TypeReg,
|
||||||
|
},
|
||||||
|
fm: 0600,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, g := range golden {
|
for i, g := range golden {
|
||||||
|
@ -268,12 +284,37 @@ func TestHeaderRoundTrip(t *testing.T) {
|
||||||
if got, want := h2.Size, g.h.Size; got != want {
|
if got, want := h2.Size, g.h.Size; got != want {
|
||||||
t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
|
t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
|
||||||
}
|
}
|
||||||
|
if got, want := h2.Uid, g.h.Uid; got != want {
|
||||||
|
t.Errorf("i=%d: Uid: got %d, want %d", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Gid, g.h.Gid; got != want {
|
||||||
|
t.Errorf("i=%d: Gid: got %d, want %d", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Uname, g.h.Uname; got != want {
|
||||||
|
t.Errorf("i=%d: Uname: got %q, want %q", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Gname, g.h.Gname; got != want {
|
||||||
|
t.Errorf("i=%d: Gname: got %q, want %q", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Linkname, g.h.Linkname; got != want {
|
||||||
|
t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Typeflag, g.h.Typeflag; got != want {
|
||||||
|
t.Logf("%#v %#v", g.h, fi.Sys())
|
||||||
|
t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want)
|
||||||
|
}
|
||||||
if got, want := h2.Mode, g.h.Mode; got != want {
|
if got, want := h2.Mode, g.h.Mode; got != want {
|
||||||
t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
|
t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
|
||||||
}
|
}
|
||||||
if got, want := fi.Mode(), g.fm; got != want {
|
if got, want := fi.Mode(), g.fm; got != want {
|
||||||
t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
|
t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
|
||||||
}
|
}
|
||||||
|
if got, want := h2.AccessTime, g.h.AccessTime; got != want {
|
||||||
|
t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.ChangeTime, g.h.ChangeTime; got != want {
|
||||||
|
t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
if got, want := h2.ModTime, g.h.ModTime; got != want {
|
if got, want := h2.ModTime, g.h.ModTime; got != want {
|
||||||
t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
|
t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
|
||||||
}
|
}
|
||||||
|
|
BIN
archive/tar/testdata/hardlink.tar
vendored
Normal file
BIN
archive/tar/testdata/hardlink.tar
vendored
Normal file
Binary file not shown.
BIN
archive/tar/testdata/issue10968.tar
vendored
Normal file
BIN
archive/tar/testdata/issue10968.tar
vendored
Normal file
Binary file not shown.
BIN
archive/tar/testdata/issue11169.tar
vendored
Normal file
BIN
archive/tar/testdata/issue11169.tar
vendored
Normal file
Binary file not shown.
BIN
archive/tar/testdata/neg-size.tar
vendored
Normal file
BIN
archive/tar/testdata/neg-size.tar
vendored
Normal file
Binary file not shown.
|
@ -355,7 +355,7 @@ func paxHeader(msg string) string {
|
||||||
// hdr.Size bytes are written after WriteHeader.
|
// hdr.Size bytes are written after WriteHeader.
|
||||||
func (tw *Writer) Write(b []byte) (n int, err error) {
|
func (tw *Writer) Write(b []byte) (n int, err error) {
|
||||||
if tw.closed {
|
if tw.closed {
|
||||||
err = ErrWriteTooLong
|
err = ErrWriteAfterClose
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
overwrite := false
|
overwrite := false
|
||||||
|
|
|
@ -147,6 +147,44 @@ var writerTests = []*writerTest{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// 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.
|
// Render byte array in a two-character hexadecimal string, spaced for easy visual inspection.
|
||||||
|
@ -489,3 +527,20 @@ func TestValidTypeflagWithPAXHeader(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue