...

Source file src/archive/tar/reader_test.go

Documentation: archive/tar

		 1  // Copyright 2009 The Go Authors. All rights reserved.
		 2  // Use of this source code is governed by a BSD-style
		 3  // license that can be found in the LICENSE file.
		 4  
		 5  package tar
		 6  
		 7  import (
		 8  	"bytes"
		 9  	"crypto/md5"
		10  	"errors"
		11  	"fmt"
		12  	"io"
		13  	"math"
		14  	"os"
		15  	"path"
		16  	"reflect"
		17  	"strconv"
		18  	"strings"
		19  	"testing"
		20  	"time"
		21  )
		22  
		23  func TestReader(t *testing.T) {
		24  	vectors := []struct {
		25  		file		string		// Test input file
		26  		headers []*Header // Expected output headers
		27  		chksums []string	// MD5 checksum of files, leave as nil if not checked
		28  		err		 error		 // Expected error to occur
		29  	}{{
		30  		file: "testdata/gnu.tar",
		31  		headers: []*Header{{
		32  			Name:		 "small.txt",
		33  			Mode:		 0640,
		34  			Uid:			73025,
		35  			Gid:			5000,
		36  			Size:		 5,
		37  			ModTime:	time.Unix(1244428340, 0),
		38  			Typeflag: '0',
		39  			Uname:		"dsymonds",
		40  			Gname:		"eng",
		41  			Format:	 FormatGNU,
		42  		}, {
		43  			Name:		 "small2.txt",
		44  			Mode:		 0640,
		45  			Uid:			73025,
		46  			Gid:			5000,
		47  			Size:		 11,
		48  			ModTime:	time.Unix(1244436044, 0),
		49  			Typeflag: '0',
		50  			Uname:		"dsymonds",
		51  			Gname:		"eng",
		52  			Format:	 FormatGNU,
		53  		}},
		54  		chksums: []string{
		55  			"e38b27eaccb4391bdec553a7f3ae6b2f",
		56  			"c65bd2e50a56a2138bf1716f2fd56fe9",
		57  		},
		58  	}, {
		59  		file: "testdata/sparse-formats.tar",
		60  		headers: []*Header{{
		61  			Name:		 "sparse-gnu",
		62  			Mode:		 420,
		63  			Uid:			1000,
		64  			Gid:			1000,
		65  			Size:		 200,
		66  			ModTime:	time.Unix(1392395740, 0),
		67  			Typeflag: 0x53,
		68  			Linkname: "",
		69  			Uname:		"david",
		70  			Gname:		"david",
		71  			Devmajor: 0,
		72  			Devminor: 0,
		73  			Format:	 FormatGNU,
		74  		}, {
		75  			Name:		 "sparse-posix-0.0",
		76  			Mode:		 420,
		77  			Uid:			1000,
		78  			Gid:			1000,
		79  			Size:		 200,
		80  			ModTime:	time.Unix(1392342187, 0),
		81  			Typeflag: 0x30,
		82  			Linkname: "",
		83  			Uname:		"david",
		84  			Gname:		"david",
		85  			Devmajor: 0,
		86  			Devminor: 0,
		87  			PAXRecords: map[string]string{
		88  				"GNU.sparse.size":			"200",
		89  				"GNU.sparse.numblocks": "95",
		90  				"GNU.sparse.map":			 "1,1,3,1,5,1,7,1,9,1,11,1,13,1,15,1,17,1,19,1,21,1,23,1,25,1,27,1,29,1,31,1,33,1,35,1,37,1,39,1,41,1,43,1,45,1,47,1,49,1,51,1,53,1,55,1,57,1,59,1,61,1,63,1,65,1,67,1,69,1,71,1,73,1,75,1,77,1,79,1,81,1,83,1,85,1,87,1,89,1,91,1,93,1,95,1,97,1,99,1,101,1,103,1,105,1,107,1,109,1,111,1,113,1,115,1,117,1,119,1,121,1,123,1,125,1,127,1,129,1,131,1,133,1,135,1,137,1,139,1,141,1,143,1,145,1,147,1,149,1,151,1,153,1,155,1,157,1,159,1,161,1,163,1,165,1,167,1,169,1,171,1,173,1,175,1,177,1,179,1,181,1,183,1,185,1,187,1,189,1",
		91  			},
		92  			Format: FormatPAX,
		93  		}, {
		94  			Name:		 "sparse-posix-0.1",
		95  			Mode:		 420,
		96  			Uid:			1000,
		97  			Gid:			1000,
		98  			Size:		 200,
		99  			ModTime:	time.Unix(1392340456, 0),
	 100  			Typeflag: 0x30,
	 101  			Linkname: "",
	 102  			Uname:		"david",
	 103  			Gname:		"david",
	 104  			Devmajor: 0,
	 105  			Devminor: 0,
	 106  			PAXRecords: map[string]string{
	 107  				"GNU.sparse.size":			"200",
	 108  				"GNU.sparse.numblocks": "95",
	 109  				"GNU.sparse.map":			 "1,1,3,1,5,1,7,1,9,1,11,1,13,1,15,1,17,1,19,1,21,1,23,1,25,1,27,1,29,1,31,1,33,1,35,1,37,1,39,1,41,1,43,1,45,1,47,1,49,1,51,1,53,1,55,1,57,1,59,1,61,1,63,1,65,1,67,1,69,1,71,1,73,1,75,1,77,1,79,1,81,1,83,1,85,1,87,1,89,1,91,1,93,1,95,1,97,1,99,1,101,1,103,1,105,1,107,1,109,1,111,1,113,1,115,1,117,1,119,1,121,1,123,1,125,1,127,1,129,1,131,1,133,1,135,1,137,1,139,1,141,1,143,1,145,1,147,1,149,1,151,1,153,1,155,1,157,1,159,1,161,1,163,1,165,1,167,1,169,1,171,1,173,1,175,1,177,1,179,1,181,1,183,1,185,1,187,1,189,1",
	 110  				"GNU.sparse.name":			"sparse-posix-0.1",
	 111  			},
	 112  			Format: FormatPAX,
	 113  		}, {
	 114  			Name:		 "sparse-posix-1.0",
	 115  			Mode:		 420,
	 116  			Uid:			1000,
	 117  			Gid:			1000,
	 118  			Size:		 200,
	 119  			ModTime:	time.Unix(1392337404, 0),
	 120  			Typeflag: 0x30,
	 121  			Linkname: "",
	 122  			Uname:		"david",
	 123  			Gname:		"david",
	 124  			Devmajor: 0,
	 125  			Devminor: 0,
	 126  			PAXRecords: map[string]string{
	 127  				"GNU.sparse.major":		"1",
	 128  				"GNU.sparse.minor":		"0",
	 129  				"GNU.sparse.realsize": "200",
	 130  				"GNU.sparse.name":		 "sparse-posix-1.0",
	 131  			},
	 132  			Format: FormatPAX,
	 133  		}, {
	 134  			Name:		 "end",
	 135  			Mode:		 420,
	 136  			Uid:			1000,
	 137  			Gid:			1000,
	 138  			Size:		 4,
	 139  			ModTime:	time.Unix(1392398319, 0),
	 140  			Typeflag: 0x30,
	 141  			Linkname: "",
	 142  			Uname:		"david",
	 143  			Gname:		"david",
	 144  			Devmajor: 0,
	 145  			Devminor: 0,
	 146  			Format:	 FormatGNU,
	 147  		}},
	 148  		chksums: []string{
	 149  			"6f53234398c2449fe67c1812d993012f",
	 150  			"6f53234398c2449fe67c1812d993012f",
	 151  			"6f53234398c2449fe67c1812d993012f",
	 152  			"6f53234398c2449fe67c1812d993012f",
	 153  			"b0061974914468de549a2af8ced10316",
	 154  		},
	 155  	}, {
	 156  		file: "testdata/star.tar",
	 157  		headers: []*Header{{
	 158  			Name:			 "small.txt",
	 159  			Mode:			 0640,
	 160  			Uid:				73025,
	 161  			Gid:				5000,
	 162  			Size:			 5,
	 163  			ModTime:		time.Unix(1244592783, 0),
	 164  			Typeflag:	 '0',
	 165  			Uname:			"dsymonds",
	 166  			Gname:			"eng",
	 167  			AccessTime: time.Unix(1244592783, 0),
	 168  			ChangeTime: time.Unix(1244592783, 0),
	 169  		}, {
	 170  			Name:			 "small2.txt",
	 171  			Mode:			 0640,
	 172  			Uid:				73025,
	 173  			Gid:				5000,
	 174  			Size:			 11,
	 175  			ModTime:		time.Unix(1244592783, 0),
	 176  			Typeflag:	 '0',
	 177  			Uname:			"dsymonds",
	 178  			Gname:			"eng",
	 179  			AccessTime: time.Unix(1244592783, 0),
	 180  			ChangeTime: time.Unix(1244592783, 0),
	 181  		}},
	 182  	}, {
	 183  		file: "testdata/v7.tar",
	 184  		headers: []*Header{{
	 185  			Name:		 "small.txt",
	 186  			Mode:		 0444,
	 187  			Uid:			73025,
	 188  			Gid:			5000,
	 189  			Size:		 5,
	 190  			ModTime:	time.Unix(1244593104, 0),
	 191  			Typeflag: '0',
	 192  		}, {
	 193  			Name:		 "small2.txt",
	 194  			Mode:		 0444,
	 195  			Uid:			73025,
	 196  			Gid:			5000,
	 197  			Size:		 11,
	 198  			ModTime:	time.Unix(1244593104, 0),
	 199  			Typeflag: '0',
	 200  		}},
	 201  	}, {
	 202  		file: "testdata/pax.tar",
	 203  		headers: []*Header{{
	 204  			Name:			 "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
	 205  			Mode:			 0664,
	 206  			Uid:				1000,
	 207  			Gid:				1000,
	 208  			Uname:			"shane",
	 209  			Gname:			"shane",
	 210  			Size:			 7,
	 211  			ModTime:		time.Unix(1350244992, 23960108),
	 212  			ChangeTime: time.Unix(1350244992, 23960108),
	 213  			AccessTime: time.Unix(1350244992, 23960108),
	 214  			Typeflag:	 TypeReg,
	 215  			PAXRecords: map[string]string{
	 216  				"path":	"a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
	 217  				"mtime": "1350244992.023960108",
	 218  				"atime": "1350244992.023960108",
	 219  				"ctime": "1350244992.023960108",
	 220  			},
	 221  			Format: FormatPAX,
	 222  		}, {
	 223  			Name:			 "a/b",
	 224  			Mode:			 0777,
	 225  			Uid:				1000,
	 226  			Gid:				1000,
	 227  			Uname:			"shane",
	 228  			Gname:			"shane",
	 229  			Size:			 0,
	 230  			ModTime:		time.Unix(1350266320, 910238425),
	 231  			ChangeTime: time.Unix(1350266320, 910238425),
	 232  			AccessTime: time.Unix(1350266320, 910238425),
	 233  			Typeflag:	 TypeSymlink,
	 234  			Linkname:	 "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
	 235  			PAXRecords: map[string]string{
	 236  				"linkpath": "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
	 237  				"mtime":		"1350266320.910238425",
	 238  				"atime":		"1350266320.910238425",
	 239  				"ctime":		"1350266320.910238425",
	 240  			},
	 241  			Format: FormatPAX,
	 242  		}},
	 243  	}, {
	 244  		file: "testdata/pax-bad-hdr-file.tar",
	 245  		err:	ErrHeader,
	 246  	}, {
	 247  		file: "testdata/pax-bad-mtime-file.tar",
	 248  		err:	ErrHeader,
	 249  	}, {
	 250  		file: "testdata/pax-pos-size-file.tar",
	 251  		headers: []*Header{{
	 252  			Name:		 "foo",
	 253  			Mode:		 0640,
	 254  			Uid:			319973,
	 255  			Gid:			5000,
	 256  			Size:		 999,
	 257  			ModTime:	time.Unix(1442282516, 0),
	 258  			Typeflag: '0',
	 259  			Uname:		"joetsai",
	 260  			Gname:		"eng",
	 261  			PAXRecords: map[string]string{
	 262  				"size": "000000000000000000000999",
	 263  			},
	 264  			Format: FormatPAX,
	 265  		}},
	 266  		chksums: []string{
	 267  			"0afb597b283fe61b5d4879669a350556",
	 268  		},
	 269  	}, {
	 270  		file: "testdata/pax-records.tar",
	 271  		headers: []*Header{{
	 272  			Typeflag: TypeReg,
	 273  			Name:		 "file",
	 274  			Uname:		strings.Repeat("long", 10),
	 275  			ModTime:	time.Unix(0, 0),
	 276  			PAXRecords: map[string]string{
	 277  				"GOLANG.pkg": "tar",
	 278  				"comment":		"Hello, 世界",
	 279  				"uname":			strings.Repeat("long", 10),
	 280  			},
	 281  			Format: FormatPAX,
	 282  		}},
	 283  	}, {
	 284  		file: "testdata/pax-global-records.tar",
	 285  		headers: []*Header{{
	 286  			Typeflag:	 TypeXGlobalHeader,
	 287  			Name:			 "global1",
	 288  			PAXRecords: map[string]string{"path": "global1", "mtime": "1500000000.0"},
	 289  			Format:		 FormatPAX,
	 290  		}, {
	 291  			Typeflag: TypeReg,
	 292  			Name:		 "file1",
	 293  			ModTime:	time.Unix(0, 0),
	 294  			Format:	 FormatUSTAR,
	 295  		}, {
	 296  			Typeflag:	 TypeReg,
	 297  			Name:			 "file2",
	 298  			PAXRecords: map[string]string{"path": "file2"},
	 299  			ModTime:		time.Unix(0, 0),
	 300  			Format:		 FormatPAX,
	 301  		}, {
	 302  			Typeflag:	 TypeXGlobalHeader,
	 303  			Name:			 "GlobalHead.0.0",
	 304  			PAXRecords: map[string]string{"path": ""},
	 305  			Format:		 FormatPAX,
	 306  		}, {
	 307  			Typeflag: TypeReg,
	 308  			Name:		 "file3",
	 309  			ModTime:	time.Unix(0, 0),
	 310  			Format:	 FormatUSTAR,
	 311  		}, {
	 312  			Typeflag:	 TypeReg,
	 313  			Name:			 "file4",
	 314  			ModTime:		time.Unix(1400000000, 0),
	 315  			PAXRecords: map[string]string{"mtime": "1400000000"},
	 316  			Format:		 FormatPAX,
	 317  		}},
	 318  	}, {
	 319  		file: "testdata/nil-uid.tar", // golang.org/issue/5290
	 320  		headers: []*Header{{
	 321  			Name:		 "P1050238.JPG.log",
	 322  			Mode:		 0664,
	 323  			Uid:			0,
	 324  			Gid:			0,
	 325  			Size:		 14,
	 326  			ModTime:	time.Unix(1365454838, 0),
	 327  			Typeflag: TypeReg,
	 328  			Linkname: "",
	 329  			Uname:		"eyefi",
	 330  			Gname:		"eyefi",
	 331  			Devmajor: 0,
	 332  			Devminor: 0,
	 333  			Format:	 FormatGNU,
	 334  		}},
	 335  	}, {
	 336  		file: "testdata/xattrs.tar",
	 337  		headers: []*Header{{
	 338  			Name:			 "small.txt",
	 339  			Mode:			 0644,
	 340  			Uid:				1000,
	 341  			Gid:				10,
	 342  			Size:			 5,
	 343  			ModTime:		time.Unix(1386065770, 448252320),
	 344  			Typeflag:	 '0',
	 345  			Uname:			"alex",
	 346  			Gname:			"wheel",
	 347  			AccessTime: time.Unix(1389782991, 419875220),
	 348  			ChangeTime: time.Unix(1389782956, 794414986),
	 349  			Xattrs: map[string]string{
	 350  				"user.key":	"value",
	 351  				"user.key2": "value2",
	 352  				// Interestingly, selinux encodes the terminating null inside the xattr
	 353  				"security.selinux": "unconfined_u:object_r:default_t:s0\x00",
	 354  			},
	 355  			PAXRecords: map[string]string{
	 356  				"mtime":												 "1386065770.44825232",
	 357  				"atime":												 "1389782991.41987522",
	 358  				"ctime":												 "1389782956.794414986",
	 359  				"SCHILY.xattr.user.key":				 "value",
	 360  				"SCHILY.xattr.user.key2":				"value2",
	 361  				"SCHILY.xattr.security.selinux": "unconfined_u:object_r:default_t:s0\x00",
	 362  			},
	 363  			Format: FormatPAX,
	 364  		}, {
	 365  			Name:			 "small2.txt",
	 366  			Mode:			 0644,
	 367  			Uid:				1000,
	 368  			Gid:				10,
	 369  			Size:			 11,
	 370  			ModTime:		time.Unix(1386065770, 449252304),
	 371  			Typeflag:	 '0',
	 372  			Uname:			"alex",
	 373  			Gname:			"wheel",
	 374  			AccessTime: time.Unix(1389782991, 419875220),
	 375  			ChangeTime: time.Unix(1386065770, 449252304),
	 376  			Xattrs: map[string]string{
	 377  				"security.selinux": "unconfined_u:object_r:default_t:s0\x00",
	 378  			},
	 379  			PAXRecords: map[string]string{
	 380  				"mtime":												 "1386065770.449252304",
	 381  				"atime":												 "1389782991.41987522",
	 382  				"ctime":												 "1386065770.449252304",
	 383  				"SCHILY.xattr.security.selinux": "unconfined_u:object_r:default_t:s0\x00",
	 384  			},
	 385  			Format: FormatPAX,
	 386  		}},
	 387  	}, {
	 388  		// Matches the behavior of GNU, BSD, and STAR tar utilities.
	 389  		file: "testdata/gnu-multi-hdrs.tar",
	 390  		headers: []*Header{{
	 391  			Name:		 "GNU2/GNU2/long-path-name",
	 392  			Linkname: "GNU4/GNU4/long-linkpath-name",
	 393  			ModTime:	time.Unix(0, 0),
	 394  			Typeflag: '2',
	 395  			Format:	 FormatGNU,
	 396  		}},
	 397  	}, {
	 398  		// GNU tar file with atime and ctime fields set.
	 399  		// Created with the GNU tar v1.27.1.
	 400  		//	tar --incremental -S -cvf gnu-incremental.tar test2
	 401  		file: "testdata/gnu-incremental.tar",
	 402  		headers: []*Header{{
	 403  			Name:			 "test2/",
	 404  			Mode:			 16877,
	 405  			Uid:				1000,
	 406  			Gid:				1000,
	 407  			Size:			 14,
	 408  			ModTime:		time.Unix(1441973427, 0),
	 409  			Typeflag:	 'D',
	 410  			Uname:			"rawr",
	 411  			Gname:			"dsnet",
	 412  			AccessTime: time.Unix(1441974501, 0),
	 413  			ChangeTime: time.Unix(1441973436, 0),
	 414  			Format:		 FormatGNU,
	 415  		}, {
	 416  			Name:			 "test2/foo",
	 417  			Mode:			 33188,
	 418  			Uid:				1000,
	 419  			Gid:				1000,
	 420  			Size:			 64,
	 421  			ModTime:		time.Unix(1441973363, 0),
	 422  			Typeflag:	 '0',
	 423  			Uname:			"rawr",
	 424  			Gname:			"dsnet",
	 425  			AccessTime: time.Unix(1441974501, 0),
	 426  			ChangeTime: time.Unix(1441973436, 0),
	 427  			Format:		 FormatGNU,
	 428  		}, {
	 429  			Name:			 "test2/sparse",
	 430  			Mode:			 33188,
	 431  			Uid:				1000,
	 432  			Gid:				1000,
	 433  			Size:			 536870912,
	 434  			ModTime:		time.Unix(1441973427, 0),
	 435  			Typeflag:	 'S',
	 436  			Uname:			"rawr",
	 437  			Gname:			"dsnet",
	 438  			AccessTime: time.Unix(1441991948, 0),
	 439  			ChangeTime: time.Unix(1441973436, 0),
	 440  			Format:		 FormatGNU,
	 441  		}},
	 442  	}, {
	 443  		// Matches the behavior of GNU and BSD tar utilities.
	 444  		file: "testdata/pax-multi-hdrs.tar",
	 445  		headers: []*Header{{
	 446  			Name:		 "bar",
	 447  			Linkname: "PAX4/PAX4/long-linkpath-name",
	 448  			ModTime:	time.Unix(0, 0),
	 449  			Typeflag: '2',
	 450  			PAXRecords: map[string]string{
	 451  				"linkpath": "PAX4/PAX4/long-linkpath-name",
	 452  			},
	 453  			Format: FormatPAX,
	 454  		}},
	 455  	}, {
	 456  		// Both BSD and GNU tar truncate long names at first NUL even
	 457  		// if there is data following that NUL character.
	 458  		// This is reasonable as GNU long names are C-strings.
	 459  		file: "testdata/gnu-long-nul.tar",
	 460  		headers: []*Header{{
	 461  			Name:		 "0123456789",
	 462  			Mode:		 0644,
	 463  			Uid:			1000,
	 464  			Gid:			1000,
	 465  			ModTime:	time.Unix(1486082191, 0),
	 466  			Typeflag: '0',
	 467  			Uname:		"rawr",
	 468  			Gname:		"dsnet",
	 469  			Format:	 FormatGNU,
	 470  		}},
	 471  	}, {
	 472  		// This archive was generated by Writer but is readable by both
	 473  		// GNU and BSD tar utilities.
	 474  		// The archive generated by GNU is nearly byte-for-byte identical
	 475  		// to the Go version except the Go version sets a negative Devminor
	 476  		// just to force the GNU format.
	 477  		file: "testdata/gnu-utf8.tar",
	 478  		headers: []*Header{{
	 479  			Name: "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹",
	 480  			Mode: 0644,
	 481  			Uid:	1000, Gid: 1000,
	 482  			ModTime:	time.Unix(0, 0),
	 483  			Typeflag: '0',
	 484  			Uname:		"☺",
	 485  			Gname:		"⚹",
	 486  			Format:	 FormatGNU,
	 487  		}},
	 488  	}, {
	 489  		// This archive was generated by Writer but is readable by both
	 490  		// GNU and BSD tar utilities.
	 491  		// The archive generated by GNU is nearly byte-for-byte identical
	 492  		// to the Go version except the Go version sets a negative Devminor
	 493  		// just to force the GNU format.
	 494  		file: "testdata/gnu-not-utf8.tar",
	 495  		headers: []*Header{{
	 496  			Name:		 "hi\x80\x81\x82\x83bye",
	 497  			Mode:		 0644,
	 498  			Uid:			1000,
	 499  			Gid:			1000,
	 500  			ModTime:	time.Unix(0, 0),
	 501  			Typeflag: '0',
	 502  			Uname:		"rawr",
	 503  			Gname:		"dsnet",
	 504  			Format:	 FormatGNU,
	 505  		}},
	 506  	}, {
	 507  		// BSD tar v3.1.2 and GNU tar v1.27.1 both rejects PAX records
	 508  		// with NULs in the key.
	 509  		file: "testdata/pax-nul-xattrs.tar",
	 510  		err:	ErrHeader,
	 511  	}, {
	 512  		// BSD tar v3.1.2 rejects a PAX path with NUL in the value, while
	 513  		// GNU tar v1.27.1 simply truncates at first NUL.
	 514  		// We emulate the behavior of BSD since it is strange doing NUL
	 515  		// truncations since PAX records are length-prefix strings instead
	 516  		// of NUL-terminated C-strings.
	 517  		file: "testdata/pax-nul-path.tar",
	 518  		err:	ErrHeader,
	 519  	}, {
	 520  		file: "testdata/neg-size.tar",
	 521  		err:	ErrHeader,
	 522  	}, {
	 523  		file: "testdata/issue10968.tar",
	 524  		err:	ErrHeader,
	 525  	}, {
	 526  		file: "testdata/issue11169.tar",
	 527  		err:	ErrHeader,
	 528  	}, {
	 529  		file: "testdata/issue12435.tar",
	 530  		err:	ErrHeader,
	 531  	}, {
	 532  		// Ensure that we can read back the original Header as written with
	 533  		// a buggy pre-Go1.8 tar.Writer.
	 534  		file: "testdata/invalid-go17.tar",
	 535  		headers: []*Header{{
	 536  			Name:		 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/foo",
	 537  			Uid:			010000000,
	 538  			ModTime:	time.Unix(0, 0),
	 539  			Typeflag: '0',
	 540  		}},
	 541  	}, {
	 542  		// USTAR archive with a regular entry with non-zero device numbers.
	 543  		file: "testdata/ustar-file-devs.tar",
	 544  		headers: []*Header{{
	 545  			Name:		 "file",
	 546  			Mode:		 0644,
	 547  			Typeflag: '0',
	 548  			ModTime:	time.Unix(0, 0),
	 549  			Devmajor: 1,
	 550  			Devminor: 1,
	 551  			Format:	 FormatUSTAR,
	 552  		}},
	 553  	}, {
	 554  		// Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.
	 555  		file: "testdata/gnu-nil-sparse-data.tar",
	 556  		headers: []*Header{{
	 557  			Name:		 "sparse.db",
	 558  			Typeflag: TypeGNUSparse,
	 559  			Size:		 1000,
	 560  			ModTime:	time.Unix(0, 0),
	 561  			Format:	 FormatGNU,
	 562  		}},
	 563  	}, {
	 564  		// Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.
	 565  		file: "testdata/gnu-nil-sparse-hole.tar",
	 566  		headers: []*Header{{
	 567  			Name:		 "sparse.db",
	 568  			Typeflag: TypeGNUSparse,
	 569  			Size:		 1000,
	 570  			ModTime:	time.Unix(0, 0),
	 571  			Format:	 FormatGNU,
	 572  		}},
	 573  	}, {
	 574  		// Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.
	 575  		file: "testdata/pax-nil-sparse-data.tar",
	 576  		headers: []*Header{{
	 577  			Name:		 "sparse.db",
	 578  			Typeflag: TypeReg,
	 579  			Size:		 1000,
	 580  			ModTime:	time.Unix(0, 0),
	 581  			PAXRecords: map[string]string{
	 582  				"size":								"1512",
	 583  				"GNU.sparse.major":		"1",
	 584  				"GNU.sparse.minor":		"0",
	 585  				"GNU.sparse.realsize": "1000",
	 586  				"GNU.sparse.name":		 "sparse.db",
	 587  			},
	 588  			Format: FormatPAX,
	 589  		}},
	 590  	}, {
	 591  		// Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.
	 592  		file: "testdata/pax-nil-sparse-hole.tar",
	 593  		headers: []*Header{{
	 594  			Name:		 "sparse.db",
	 595  			Typeflag: TypeReg,
	 596  			Size:		 1000,
	 597  			ModTime:	time.Unix(0, 0),
	 598  			PAXRecords: map[string]string{
	 599  				"size":								"512",
	 600  				"GNU.sparse.major":		"1",
	 601  				"GNU.sparse.minor":		"0",
	 602  				"GNU.sparse.realsize": "1000",
	 603  				"GNU.sparse.name":		 "sparse.db",
	 604  			},
	 605  			Format: FormatPAX,
	 606  		}},
	 607  	}, {
	 608  		file: "testdata/trailing-slash.tar",
	 609  		headers: []*Header{{
	 610  			Typeflag: TypeDir,
	 611  			Name:		 strings.Repeat("123456789/", 30),
	 612  			ModTime:	time.Unix(0, 0),
	 613  			PAXRecords: map[string]string{
	 614  				"path": strings.Repeat("123456789/", 30),
	 615  			},
	 616  			Format: FormatPAX,
	 617  		}},
	 618  	}}
	 619  
	 620  	for _, v := range vectors {
	 621  		t.Run(path.Base(v.file), func(t *testing.T) {
	 622  			f, err := os.Open(v.file)
	 623  			if err != nil {
	 624  				t.Fatalf("unexpected error: %v", err)
	 625  			}
	 626  			defer f.Close()
	 627  
	 628  			// Capture all headers and checksums.
	 629  			var (
	 630  				tr			= NewReader(f)
	 631  				hdrs		[]*Header
	 632  				chksums []string
	 633  				rdbuf	 = make([]byte, 8)
	 634  			)
	 635  			for {
	 636  				var hdr *Header
	 637  				hdr, err = tr.Next()
	 638  				if err != nil {
	 639  					if err == io.EOF {
	 640  						err = nil // Expected error
	 641  					}
	 642  					break
	 643  				}
	 644  				hdrs = append(hdrs, hdr)
	 645  
	 646  				if v.chksums == nil {
	 647  					continue
	 648  				}
	 649  				h := md5.New()
	 650  				_, err = io.CopyBuffer(h, tr, rdbuf) // Effectively an incremental read
	 651  				if err != nil {
	 652  					break
	 653  				}
	 654  				chksums = append(chksums, fmt.Sprintf("%x", h.Sum(nil)))
	 655  			}
	 656  
	 657  			for i, hdr := range hdrs {
	 658  				if i >= len(v.headers) {
	 659  					t.Fatalf("entry %d: unexpected header:\ngot %+v", i, *hdr)
	 660  					continue
	 661  				}
	 662  				if !reflect.DeepEqual(*hdr, *v.headers[i]) {
	 663  					t.Fatalf("entry %d: incorrect header:\ngot	%+v\nwant %+v", i, *hdr, *v.headers[i])
	 664  				}
	 665  			}
	 666  			if len(hdrs) != len(v.headers) {
	 667  				t.Fatalf("got %d headers, want %d headers", len(hdrs), len(v.headers))
	 668  			}
	 669  
	 670  			for i, sum := range chksums {
	 671  				if i >= len(v.chksums) {
	 672  					t.Fatalf("entry %d: unexpected sum: got %s", i, sum)
	 673  					continue
	 674  				}
	 675  				if sum != v.chksums[i] {
	 676  					t.Fatalf("entry %d: incorrect checksum: got %s, want %s", i, sum, v.chksums[i])
	 677  				}
	 678  			}
	 679  
	 680  			if err != v.err {
	 681  				t.Fatalf("unexpected error: got %v, want %v", err, v.err)
	 682  			}
	 683  			f.Close()
	 684  		})
	 685  	}
	 686  }
	 687  
	 688  func TestPartialRead(t *testing.T) {
	 689  	type testCase struct {
	 690  		cnt		int		// Number of bytes to read
	 691  		output string // Expected value of string read
	 692  	}
	 693  	vectors := []struct {
	 694  		file	string
	 695  		cases []testCase
	 696  	}{{
	 697  		file: "testdata/gnu.tar",
	 698  		cases: []testCase{
	 699  			{4, "Kilt"},
	 700  			{6, "Google"},
	 701  		},
	 702  	}, {
	 703  		file: "testdata/sparse-formats.tar",
	 704  		cases: []testCase{
	 705  			{2, "\x00G"},
	 706  			{4, "\x00G\x00o"},
	 707  			{6, "\x00G\x00o\x00G"},
	 708  			{8, "\x00G\x00o\x00G\x00o"},
	 709  			{4, "end\n"},
	 710  		},
	 711  	}}
	 712  
	 713  	for _, v := range vectors {
	 714  		t.Run(path.Base(v.file), func(t *testing.T) {
	 715  			f, err := os.Open(v.file)
	 716  			if err != nil {
	 717  				t.Fatalf("Open() error: %v", err)
	 718  			}
	 719  			defer f.Close()
	 720  
	 721  			tr := NewReader(f)
	 722  			for i, tc := range v.cases {
	 723  				hdr, err := tr.Next()
	 724  				if err != nil || hdr == nil {
	 725  					t.Fatalf("entry %d, Next(): got %v, want %v", i, err, nil)
	 726  				}
	 727  				buf := make([]byte, tc.cnt)
	 728  				if _, err := io.ReadFull(tr, buf); err != nil {
	 729  					t.Fatalf("entry %d, ReadFull(): got %v, want %v", i, err, nil)
	 730  				}
	 731  				if string(buf) != tc.output {
	 732  					t.Fatalf("entry %d, ReadFull(): got %q, want %q", i, string(buf), tc.output)
	 733  				}
	 734  			}
	 735  
	 736  			if _, err := tr.Next(); err != io.EOF {
	 737  				t.Fatalf("Next(): got %v, want EOF", err)
	 738  			}
	 739  		})
	 740  	}
	 741  }
	 742  
	 743  func TestUninitializedRead(t *testing.T) {
	 744  	f, err := os.Open("testdata/gnu.tar")
	 745  	if err != nil {
	 746  		t.Fatalf("Unexpected error: %v", err)
	 747  	}
	 748  	defer f.Close()
	 749  
	 750  	tr := NewReader(f)
	 751  	_, err = tr.Read([]byte{})
	 752  	if err == nil || err != io.EOF {
	 753  		t.Errorf("Unexpected error: %v, wanted %v", err, io.EOF)
	 754  	}
	 755  
	 756  }
	 757  
	 758  type reader struct{ io.Reader }
	 759  type readSeeker struct{ io.ReadSeeker }
	 760  type readBadSeeker struct{ io.ReadSeeker }
	 761  
	 762  func (rbs *readBadSeeker) Seek(int64, int) (int64, error) { return 0, fmt.Errorf("illegal seek") }
	 763  
	 764  // TestReadTruncation test the ending condition on various truncated files and
	 765  // that truncated files are still detected even if the underlying io.Reader
	 766  // satisfies io.Seeker.
	 767  func TestReadTruncation(t *testing.T) {
	 768  	var ss []string
	 769  	for _, p := range []string{
	 770  		"testdata/gnu.tar",
	 771  		"testdata/ustar-file-reg.tar",
	 772  		"testdata/pax-path-hdr.tar",
	 773  		"testdata/sparse-formats.tar",
	 774  	} {
	 775  		buf, err := os.ReadFile(p)
	 776  		if err != nil {
	 777  			t.Fatalf("unexpected error: %v", err)
	 778  		}
	 779  		ss = append(ss, string(buf))
	 780  	}
	 781  
	 782  	data1, data2, pax, sparse := ss[0], ss[1], ss[2], ss[3]
	 783  	data2 += strings.Repeat("\x00", 10*512)
	 784  	trash := strings.Repeat("garbage ", 64) // Exactly 512 bytes
	 785  
	 786  	vectors := []struct {
	 787  		input string // Input stream
	 788  		cnt	 int		// Expected number of headers read
	 789  		err	 error	// Expected error outcome
	 790  	}{
	 791  		{"", 0, io.EOF}, // Empty file is a "valid" tar file
	 792  		{data1[:511], 0, io.ErrUnexpectedEOF},
	 793  		{data1[:512], 1, io.ErrUnexpectedEOF},
	 794  		{data1[:1024], 1, io.EOF},
	 795  		{data1[:1536], 2, io.ErrUnexpectedEOF},
	 796  		{data1[:2048], 2, io.EOF},
	 797  		{data1, 2, io.EOF},
	 798  		{data1[:2048] + data2[:1536], 3, io.EOF},
	 799  		{data2[:511], 0, io.ErrUnexpectedEOF},
	 800  		{data2[:512], 1, io.ErrUnexpectedEOF},
	 801  		{data2[:1195], 1, io.ErrUnexpectedEOF},
	 802  		{data2[:1196], 1, io.EOF}, // Exact end of data and start of padding
	 803  		{data2[:1200], 1, io.EOF},
	 804  		{data2[:1535], 1, io.EOF},
	 805  		{data2[:1536], 1, io.EOF}, // Exact end of padding
	 806  		{data2[:1536] + trash[:1], 1, io.ErrUnexpectedEOF},
	 807  		{data2[:1536] + trash[:511], 1, io.ErrUnexpectedEOF},
	 808  		{data2[:1536] + trash, 1, ErrHeader},
	 809  		{data2[:2048], 1, io.EOF}, // Exactly 1 empty block
	 810  		{data2[:2048] + trash[:1], 1, io.ErrUnexpectedEOF},
	 811  		{data2[:2048] + trash[:511], 1, io.ErrUnexpectedEOF},
	 812  		{data2[:2048] + trash, 1, ErrHeader},
	 813  		{data2[:2560], 1, io.EOF}, // Exactly 2 empty blocks (normal end-of-stream)
	 814  		{data2[:2560] + trash[:1], 1, io.EOF},
	 815  		{data2[:2560] + trash[:511], 1, io.EOF},
	 816  		{data2[:2560] + trash, 1, io.EOF},
	 817  		{data2[:3072], 1, io.EOF},
	 818  		{pax, 0, io.EOF}, // PAX header without data is a "valid" tar file
	 819  		{pax + trash[:1], 0, io.ErrUnexpectedEOF},
	 820  		{pax + trash[:511], 0, io.ErrUnexpectedEOF},
	 821  		{sparse[:511], 0, io.ErrUnexpectedEOF},
	 822  		{sparse[:512], 0, io.ErrUnexpectedEOF},
	 823  		{sparse[:3584], 1, io.EOF},
	 824  		{sparse[:9200], 1, io.EOF}, // Terminate in padding of sparse header
	 825  		{sparse[:9216], 1, io.EOF},
	 826  		{sparse[:9728], 2, io.ErrUnexpectedEOF},
	 827  		{sparse[:10240], 2, io.EOF},
	 828  		{sparse[:11264], 2, io.ErrUnexpectedEOF},
	 829  		{sparse, 5, io.EOF},
	 830  		{sparse + trash, 5, io.EOF},
	 831  	}
	 832  
	 833  	for i, v := range vectors {
	 834  		for j := 0; j < 6; j++ {
	 835  			var tr *Reader
	 836  			var s1, s2 string
	 837  
	 838  			switch j {
	 839  			case 0:
	 840  				tr = NewReader(&reader{strings.NewReader(v.input)})
	 841  				s1, s2 = "io.Reader", "auto"
	 842  			case 1:
	 843  				tr = NewReader(&reader{strings.NewReader(v.input)})
	 844  				s1, s2 = "io.Reader", "manual"
	 845  			case 2:
	 846  				tr = NewReader(&readSeeker{strings.NewReader(v.input)})
	 847  				s1, s2 = "io.ReadSeeker", "auto"
	 848  			case 3:
	 849  				tr = NewReader(&readSeeker{strings.NewReader(v.input)})
	 850  				s1, s2 = "io.ReadSeeker", "manual"
	 851  			case 4:
	 852  				tr = NewReader(&readBadSeeker{strings.NewReader(v.input)})
	 853  				s1, s2 = "ReadBadSeeker", "auto"
	 854  			case 5:
	 855  				tr = NewReader(&readBadSeeker{strings.NewReader(v.input)})
	 856  				s1, s2 = "ReadBadSeeker", "manual"
	 857  			}
	 858  
	 859  			var cnt int
	 860  			var err error
	 861  			for {
	 862  				if _, err = tr.Next(); err != nil {
	 863  					break
	 864  				}
	 865  				cnt++
	 866  				if s2 == "manual" {
	 867  					if _, err = tr.writeTo(io.Discard); err != nil {
	 868  						break
	 869  					}
	 870  				}
	 871  			}
	 872  			if err != v.err {
	 873  				t.Errorf("test %d, NewReader(%s) with %s discard: got %v, want %v",
	 874  					i, s1, s2, err, v.err)
	 875  			}
	 876  			if cnt != v.cnt {
	 877  				t.Errorf("test %d, NewReader(%s) with %s discard: got %d headers, want %d headers",
	 878  					i, s1, s2, cnt, v.cnt)
	 879  			}
	 880  		}
	 881  	}
	 882  }
	 883  
	 884  // TestReadHeaderOnly tests that Reader does not attempt to read special
	 885  // header-only files.
	 886  func TestReadHeaderOnly(t *testing.T) {
	 887  	f, err := os.Open("testdata/hdr-only.tar")
	 888  	if err != nil {
	 889  		t.Fatalf("unexpected error: %v", err)
	 890  	}
	 891  	defer f.Close()
	 892  
	 893  	var hdrs []*Header
	 894  	tr := NewReader(f)
	 895  	for {
	 896  		hdr, err := tr.Next()
	 897  		if err == io.EOF {
	 898  			break
	 899  		}
	 900  		if err != nil {
	 901  			t.Errorf("Next(): got %v, want %v", err, nil)
	 902  			continue
	 903  		}
	 904  		hdrs = append(hdrs, hdr)
	 905  
	 906  		// If a special flag, we should read nothing.
	 907  		cnt, _ := io.ReadFull(tr, []byte{0})
	 908  		if cnt > 0 && hdr.Typeflag != TypeReg {
	 909  			t.Errorf("ReadFull(...): got %d bytes, want 0 bytes", cnt)
	 910  		}
	 911  	}
	 912  
	 913  	// File is crafted with 16 entries. The later 8 are identical to the first
	 914  	// 8 except that the size is set.
	 915  	if len(hdrs) != 16 {
	 916  		t.Fatalf("len(hdrs): got %d, want %d", len(hdrs), 16)
	 917  	}
	 918  	for i := 0; i < 8; i++ {
	 919  		hdr1, hdr2 := hdrs[i+0], hdrs[i+8]
	 920  		hdr1.Size, hdr2.Size = 0, 0
	 921  		if !reflect.DeepEqual(*hdr1, *hdr2) {
	 922  			t.Errorf("incorrect header:\ngot	%+v\nwant %+v", *hdr1, *hdr2)
	 923  		}
	 924  	}
	 925  }
	 926  
	 927  func TestMergePAX(t *testing.T) {
	 928  	vectors := []struct {
	 929  		in	 map[string]string
	 930  		want *Header
	 931  		ok	 bool
	 932  	}{{
	 933  		in: map[string]string{
	 934  			"path":	"a/b/c",
	 935  			"uid":	 "1000",
	 936  			"mtime": "1350244992.023960108",
	 937  		},
	 938  		want: &Header{
	 939  			Name:		"a/b/c",
	 940  			Uid:		 1000,
	 941  			ModTime: time.Unix(1350244992, 23960108),
	 942  			PAXRecords: map[string]string{
	 943  				"path":	"a/b/c",
	 944  				"uid":	 "1000",
	 945  				"mtime": "1350244992.023960108",
	 946  			},
	 947  		},
	 948  		ok: true,
	 949  	}, {
	 950  		in: map[string]string{
	 951  			"gid": "gtgergergersagersgers",
	 952  		},
	 953  		ok: false,
	 954  	}, {
	 955  		in: map[string]string{
	 956  			"missing":					"missing",
	 957  			"SCHILY.xattr.key": "value",
	 958  		},
	 959  		want: &Header{
	 960  			Xattrs: map[string]string{"key": "value"},
	 961  			PAXRecords: map[string]string{
	 962  				"missing":					"missing",
	 963  				"SCHILY.xattr.key": "value",
	 964  			},
	 965  		},
	 966  		ok: true,
	 967  	}}
	 968  
	 969  	for i, v := range vectors {
	 970  		got := new(Header)
	 971  		err := mergePAX(got, v.in)
	 972  		if v.ok && !reflect.DeepEqual(*got, *v.want) {
	 973  			t.Errorf("test %d, mergePAX(...):\ngot	%+v\nwant %+v", i, *got, *v.want)
	 974  		}
	 975  		if ok := err == nil; ok != v.ok {
	 976  			t.Errorf("test %d, mergePAX(...): got %v, want %v", i, ok, v.ok)
	 977  		}
	 978  	}
	 979  }
	 980  
	 981  func TestParsePAX(t *testing.T) {
	 982  	vectors := []struct {
	 983  		in	 string
	 984  		want map[string]string
	 985  		ok	 bool
	 986  	}{
	 987  		{"", nil, true},
	 988  		{"6 k=1\n", map[string]string{"k": "1"}, true},
	 989  		{"10 a=name\n", map[string]string{"a": "name"}, true},
	 990  		{"9 a=name\n", map[string]string{"a": "name"}, true},
	 991  		{"30 mtime=1350244992.023960108\n", map[string]string{"mtime": "1350244992.023960108"}, true},
	 992  		{"3 somelongkey=\n", nil, false},
	 993  		{"50 tooshort=\n", nil, false},
	 994  		{"13 key1=haha\n13 key2=nana\n13 key3=kaka\n",
	 995  			map[string]string{"key1": "haha", "key2": "nana", "key3": "kaka"}, true},
	 996  		{"13 key1=val1\n13 key2=val2\n8 key1=\n",
	 997  			map[string]string{"key1": "", "key2": "val2"}, true},
	 998  		{"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=2\n" +
	 999  			"23 GNU.sparse.offset=1\n25 GNU.sparse.numbytes=2\n" +
	1000  			"23 GNU.sparse.offset=3\n25 GNU.sparse.numbytes=4\n",
	1001  			map[string]string{paxGNUSparseSize: "10", paxGNUSparseNumBlocks: "2", paxGNUSparseMap: "1,2,3,4"}, true},
	1002  		{"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=1\n" +
	1003  			"25 GNU.sparse.numbytes=2\n23 GNU.sparse.offset=1\n",
	1004  			nil, false},
	1005  		{"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=1\n" +
	1006  			"25 GNU.sparse.offset=1,2\n25 GNU.sparse.numbytes=2\n",
	1007  			nil, false},
	1008  	}
	1009  
	1010  	for i, v := range vectors {
	1011  		r := strings.NewReader(v.in)
	1012  		got, err := parsePAX(r)
	1013  		if !reflect.DeepEqual(got, v.want) && !(len(got) == 0 && len(v.want) == 0) {
	1014  			t.Errorf("test %d, parsePAX():\ngot	%v\nwant %v", i, got, v.want)
	1015  		}
	1016  		if ok := err == nil; ok != v.ok {
	1017  			t.Errorf("test %d, parsePAX(): got %v, want %v", i, ok, v.ok)
	1018  		}
	1019  	}
	1020  }
	1021  
	1022  func TestReadOldGNUSparseMap(t *testing.T) {
	1023  	populateSparseMap := func(sa sparseArray, sps []string) []string {
	1024  		for i := 0; len(sps) > 0 && i < sa.MaxEntries(); i++ {
	1025  			copy(sa.Entry(i), sps[0])
	1026  			sps = sps[1:]
	1027  		}
	1028  		if len(sps) > 0 {
	1029  			copy(sa.IsExtended(), "\x80")
	1030  		}
	1031  		return sps
	1032  	}
	1033  
	1034  	makeInput := func(format Format, size string, sps ...string) (out []byte) {
	1035  		// Write the initial GNU header.
	1036  		var blk block
	1037  		gnu := blk.GNU()
	1038  		sparse := gnu.Sparse()
	1039  		copy(gnu.RealSize(), size)
	1040  		sps = populateSparseMap(sparse, sps)
	1041  		if format != FormatUnknown {
	1042  			blk.SetFormat(format)
	1043  		}
	1044  		out = append(out, blk[:]...)
	1045  
	1046  		// Write extended sparse blocks.
	1047  		for len(sps) > 0 {
	1048  			var blk block
	1049  			sps = populateSparseMap(blk.Sparse(), sps)
	1050  			out = append(out, blk[:]...)
	1051  		}
	1052  		return out
	1053  	}
	1054  
	1055  	makeSparseStrings := func(sp []sparseEntry) (out []string) {
	1056  		var f formatter
	1057  		for _, s := range sp {
	1058  			var b [24]byte
	1059  			f.formatNumeric(b[:12], s.Offset)
	1060  			f.formatNumeric(b[12:], s.Length)
	1061  			out = append(out, string(b[:]))
	1062  		}
	1063  		return out
	1064  	}
	1065  
	1066  	vectors := []struct {
	1067  		input		[]byte
	1068  		wantMap	sparseDatas
	1069  		wantSize int64
	1070  		wantErr	error
	1071  	}{{
	1072  		input:	 makeInput(FormatUnknown, ""),
	1073  		wantErr: ErrHeader,
	1074  	}, {
	1075  		input:		makeInput(FormatGNU, "1234", "fewa"),
	1076  		wantSize: 01234,
	1077  		wantErr:	ErrHeader,
	1078  	}, {
	1079  		input:		makeInput(FormatGNU, "0031"),
	1080  		wantSize: 031,
	1081  	}, {
	1082  		input:	 makeInput(FormatGNU, "80"),
	1083  		wantErr: ErrHeader,
	1084  	}, {
	1085  		input: makeInput(FormatGNU, "1234",
	1086  			makeSparseStrings(sparseDatas{{0, 0}, {1, 1}})...),
	1087  		wantMap:	sparseDatas{{0, 0}, {1, 1}},
	1088  		wantSize: 01234,
	1089  	}, {
	1090  		input: makeInput(FormatGNU, "1234",
	1091  			append(makeSparseStrings(sparseDatas{{0, 0}, {1, 1}}), []string{"", "blah"}...)...),
	1092  		wantMap:	sparseDatas{{0, 0}, {1, 1}},
	1093  		wantSize: 01234,
	1094  	}, {
	1095  		input: makeInput(FormatGNU, "3333",
	1096  			makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}})...),
	1097  		wantMap:	sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}},
	1098  		wantSize: 03333,
	1099  	}, {
	1100  		input: makeInput(FormatGNU, "",
	1101  			append(append(
	1102  				makeSparseStrings(sparseDatas{{0, 1}, {2, 1}}),
	1103  				[]string{"", ""}...),
	1104  				makeSparseStrings(sparseDatas{{4, 1}, {6, 1}})...)...),
	1105  		wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}},
	1106  	}, {
	1107  		input: makeInput(FormatGNU, "",
	1108  			makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...)[:blockSize],
	1109  		wantErr: io.ErrUnexpectedEOF,
	1110  	}, {
	1111  		input: makeInput(FormatGNU, "",
	1112  			makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...)[:3*blockSize/2],
	1113  		wantErr: io.ErrUnexpectedEOF,
	1114  	}, {
	1115  		input: makeInput(FormatGNU, "",
	1116  			makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...),
	1117  		wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}},
	1118  	}, {
	1119  		input: makeInput(FormatGNU, "",
	1120  			makeSparseStrings(sparseDatas{{10 << 30, 512}, {20 << 30, 512}})...),
	1121  		wantMap: sparseDatas{{10 << 30, 512}, {20 << 30, 512}},
	1122  	}}
	1123  
	1124  	for i, v := range vectors {
	1125  		var blk block
	1126  		var hdr Header
	1127  		v.input = v.input[copy(blk[:], v.input):]
	1128  		tr := Reader{r: bytes.NewReader(v.input)}
	1129  		got, err := tr.readOldGNUSparseMap(&hdr, &blk)
	1130  		if !equalSparseEntries(got, v.wantMap) {
	1131  			t.Errorf("test %d, readOldGNUSparseMap(): got %v, want %v", i, got, v.wantMap)
	1132  		}
	1133  		if err != v.wantErr {
	1134  			t.Errorf("test %d, readOldGNUSparseMap() = %v, want %v", i, err, v.wantErr)
	1135  		}
	1136  		if hdr.Size != v.wantSize {
	1137  			t.Errorf("test %d, Header.Size = %d, want %d", i, hdr.Size, v.wantSize)
	1138  		}
	1139  	}
	1140  }
	1141  
	1142  func TestReadGNUSparsePAXHeaders(t *testing.T) {
	1143  	padInput := func(s string) string {
	1144  		return s + string(zeroBlock[:blockPadding(int64(len(s)))])
	1145  	}
	1146  
	1147  	vectors := []struct {
	1148  		inputData string
	1149  		inputHdrs map[string]string
	1150  		wantMap	 sparseDatas
	1151  		wantSize	int64
	1152  		wantName	string
	1153  		wantErr	 error
	1154  	}{{
	1155  		inputHdrs: nil,
	1156  		wantErr:	 nil,
	1157  	}, {
	1158  		inputHdrs: map[string]string{
	1159  			paxGNUSparseNumBlocks: strconv.FormatInt(math.MaxInt64, 10),
	1160  			paxGNUSparseMap:			 "0,1,2,3",
	1161  		},
	1162  		wantErr: ErrHeader,
	1163  	}, {
	1164  		inputHdrs: map[string]string{
	1165  			paxGNUSparseNumBlocks: "4\x00",
	1166  			paxGNUSparseMap:			 "0,1,2,3",
	1167  		},
	1168  		wantErr: ErrHeader,
	1169  	}, {
	1170  		inputHdrs: map[string]string{
	1171  			paxGNUSparseNumBlocks: "4",
	1172  			paxGNUSparseMap:			 "0,1,2,3",
	1173  		},
	1174  		wantErr: ErrHeader,
	1175  	}, {
	1176  		inputHdrs: map[string]string{
	1177  			paxGNUSparseNumBlocks: "2",
	1178  			paxGNUSparseMap:			 "0,1,2,3",
	1179  		},
	1180  		wantMap: sparseDatas{{0, 1}, {2, 3}},
	1181  	}, {
	1182  		inputHdrs: map[string]string{
	1183  			paxGNUSparseNumBlocks: "2",
	1184  			paxGNUSparseMap:			 "0, 1,2,3",
	1185  		},
	1186  		wantErr: ErrHeader,
	1187  	}, {
	1188  		inputHdrs: map[string]string{
	1189  			paxGNUSparseNumBlocks: "2",
	1190  			paxGNUSparseMap:			 "0,1,02,3",
	1191  			paxGNUSparseRealSize:	"4321",
	1192  		},
	1193  		wantMap:	sparseDatas{{0, 1}, {2, 3}},
	1194  		wantSize: 4321,
	1195  	}, {
	1196  		inputHdrs: map[string]string{
	1197  			paxGNUSparseNumBlocks: "2",
	1198  			paxGNUSparseMap:			 "0,one1,2,3",
	1199  		},
	1200  		wantErr: ErrHeader,
	1201  	}, {
	1202  		inputHdrs: map[string]string{
	1203  			paxGNUSparseMajor:		 "0",
	1204  			paxGNUSparseMinor:		 "0",
	1205  			paxGNUSparseNumBlocks: "2",
	1206  			paxGNUSparseMap:			 "0,1,2,3",
	1207  			paxGNUSparseSize:			"1234",
	1208  			paxGNUSparseRealSize:	"4321",
	1209  			paxGNUSparseName:			"realname",
	1210  		},
	1211  		wantMap:	sparseDatas{{0, 1}, {2, 3}},
	1212  		wantSize: 1234,
	1213  		wantName: "realname",
	1214  	}, {
	1215  		inputHdrs: map[string]string{
	1216  			paxGNUSparseMajor:		 "0",
	1217  			paxGNUSparseMinor:		 "0",
	1218  			paxGNUSparseNumBlocks: "1",
	1219  			paxGNUSparseMap:			 "10737418240,512",
	1220  			paxGNUSparseSize:			"10737418240",
	1221  			paxGNUSparseName:			"realname",
	1222  		},
	1223  		wantMap:	sparseDatas{{10737418240, 512}},
	1224  		wantSize: 10737418240,
	1225  		wantName: "realname",
	1226  	}, {
	1227  		inputHdrs: map[string]string{
	1228  			paxGNUSparseMajor:		 "0",
	1229  			paxGNUSparseMinor:		 "0",
	1230  			paxGNUSparseNumBlocks: "0",
	1231  			paxGNUSparseMap:			 "",
	1232  		},
	1233  		wantMap: sparseDatas{},
	1234  	}, {
	1235  		inputHdrs: map[string]string{
	1236  			paxGNUSparseMajor:		 "0",
	1237  			paxGNUSparseMinor:		 "1",
	1238  			paxGNUSparseNumBlocks: "4",
	1239  			paxGNUSparseMap:			 "0,5,10,5,20,5,30,5",
	1240  		},
	1241  		wantMap: sparseDatas{{0, 5}, {10, 5}, {20, 5}, {30, 5}},
	1242  	}, {
	1243  		inputHdrs: map[string]string{
	1244  			paxGNUSparseMajor:		 "1",
	1245  			paxGNUSparseMinor:		 "0",
	1246  			paxGNUSparseNumBlocks: "4",
	1247  			paxGNUSparseMap:			 "0,5,10,5,20,5,30,5",
	1248  		},
	1249  		wantErr: io.ErrUnexpectedEOF,
	1250  	}, {
	1251  		inputData: padInput("0\n"),
	1252  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1253  		wantMap:	 sparseDatas{},
	1254  	}, {
	1255  		inputData: padInput("0\n")[:blockSize-1] + "#",
	1256  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1257  		wantMap:	 sparseDatas{},
	1258  	}, {
	1259  		inputData: padInput("0"),
	1260  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1261  		wantErr:	 io.ErrUnexpectedEOF,
	1262  	}, {
	1263  		inputData: padInput("ab\n"),
	1264  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1265  		wantErr:	 ErrHeader,
	1266  	}, {
	1267  		inputData: padInput("1\n2\n3\n"),
	1268  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1269  		wantMap:	 sparseDatas{{2, 3}},
	1270  	}, {
	1271  		inputData: padInput("1\n2\n"),
	1272  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1273  		wantErr:	 io.ErrUnexpectedEOF,
	1274  	}, {
	1275  		inputData: padInput("1\n2\n\n"),
	1276  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1277  		wantErr:	 ErrHeader,
	1278  	}, {
	1279  		inputData: string(zeroBlock[:]) + padInput("0\n"),
	1280  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1281  		wantErr:	 ErrHeader,
	1282  	}, {
	1283  		inputData: strings.Repeat("0", blockSize) + padInput("1\n5\n1\n"),
	1284  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1285  		wantMap:	 sparseDatas{{5, 1}},
	1286  	}, {
	1287  		inputData: padInput(fmt.Sprintf("%d\n", int64(math.MaxInt64))),
	1288  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1289  		wantErr:	 ErrHeader,
	1290  	}, {
	1291  		inputData: padInput(strings.Repeat("0", 300) + "1\n" + strings.Repeat("0", 1000) + "5\n" + strings.Repeat("0", 800) + "2\n"),
	1292  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1293  		wantMap:	 sparseDatas{{5, 2}},
	1294  	}, {
	1295  		inputData: padInput("2\n10737418240\n512\n21474836480\n512\n"),
	1296  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1297  		wantMap:	 sparseDatas{{10737418240, 512}, {21474836480, 512}},
	1298  	}, {
	1299  		inputData: padInput("100\n" + func() string {
	1300  			var ss []string
	1301  			for i := 0; i < 100; i++ {
	1302  				ss = append(ss, fmt.Sprintf("%d\n%d\n", int64(i)<<30, 512))
	1303  			}
	1304  			return strings.Join(ss, "")
	1305  		}()),
	1306  		inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
	1307  		wantMap: func() (spd sparseDatas) {
	1308  			for i := 0; i < 100; i++ {
	1309  				spd = append(spd, sparseEntry{int64(i) << 30, 512})
	1310  			}
	1311  			return spd
	1312  		}(),
	1313  	}}
	1314  
	1315  	for i, v := range vectors {
	1316  		var hdr Header
	1317  		hdr.PAXRecords = v.inputHdrs
	1318  		r := strings.NewReader(v.inputData + "#") // Add canary byte
	1319  		tr := Reader{curr: &regFileReader{r, int64(r.Len())}}
	1320  		got, err := tr.readGNUSparsePAXHeaders(&hdr)
	1321  		if !equalSparseEntries(got, v.wantMap) {
	1322  			t.Errorf("test %d, readGNUSparsePAXHeaders(): got %v, want %v", i, got, v.wantMap)
	1323  		}
	1324  		if err != v.wantErr {
	1325  			t.Errorf("test %d, readGNUSparsePAXHeaders() = %v, want %v", i, err, v.wantErr)
	1326  		}
	1327  		if hdr.Size != v.wantSize {
	1328  			t.Errorf("test %d, Header.Size = %d, want %d", i, hdr.Size, v.wantSize)
	1329  		}
	1330  		if hdr.Name != v.wantName {
	1331  			t.Errorf("test %d, Header.Name = %s, want %s", i, hdr.Name, v.wantName)
	1332  		}
	1333  		if v.wantErr == nil && r.Len() == 0 {
	1334  			t.Errorf("test %d, canary byte unexpectedly consumed", i)
	1335  		}
	1336  	}
	1337  }
	1338  
	1339  // testNonEmptyReader wraps an io.Reader and ensures that
	1340  // Read is never called with an empty buffer.
	1341  type testNonEmptyReader struct{ io.Reader }
	1342  
	1343  func (r testNonEmptyReader) Read(b []byte) (int, error) {
	1344  	if len(b) == 0 {
	1345  		return 0, errors.New("unexpected empty Read call")
	1346  	}
	1347  	return r.Reader.Read(b)
	1348  }
	1349  
	1350  func TestFileReader(t *testing.T) {
	1351  	type (
	1352  		testRead struct { // Read(cnt) == (wantStr, wantErr)
	1353  			cnt		 int
	1354  			wantStr string
	1355  			wantErr error
	1356  		}
	1357  		testWriteTo struct { // WriteTo(testFile{ops}) == (wantCnt, wantErr)
	1358  			ops		 fileOps
	1359  			wantCnt int64
	1360  			wantErr error
	1361  		}
	1362  		testRemaining struct { // LogicalRemaining() == wantLCnt, PhysicalRemaining() == wantPCnt
	1363  			wantLCnt int64
	1364  			wantPCnt int64
	1365  		}
	1366  		testFnc interface{} // testRead | testWriteTo | testRemaining
	1367  	)
	1368  
	1369  	type (
	1370  		makeReg struct {
	1371  			str	string
	1372  			size int64
	1373  		}
	1374  		makeSparse struct {
	1375  			makeReg makeReg
	1376  			spd		 sparseDatas
	1377  			size		int64
	1378  		}
	1379  		fileMaker interface{} // makeReg | makeSparse
	1380  	)
	1381  
	1382  	vectors := []struct {
	1383  		maker fileMaker
	1384  		tests []testFnc
	1385  	}{{
	1386  		maker: makeReg{"", 0},
	1387  		tests: []testFnc{
	1388  			testRemaining{0, 0},
	1389  			testRead{0, "", io.EOF},
	1390  			testRead{1, "", io.EOF},
	1391  			testWriteTo{nil, 0, nil},
	1392  			testRemaining{0, 0},
	1393  		},
	1394  	}, {
	1395  		maker: makeReg{"", 1},
	1396  		tests: []testFnc{
	1397  			testRemaining{1, 1},
	1398  			testRead{5, "", io.ErrUnexpectedEOF},
	1399  			testWriteTo{nil, 0, io.ErrUnexpectedEOF},
	1400  			testRemaining{1, 1},
	1401  		},
	1402  	}, {
	1403  		maker: makeReg{"hello", 5},
	1404  		tests: []testFnc{
	1405  			testRemaining{5, 5},
	1406  			testRead{5, "hello", io.EOF},
	1407  			testRemaining{0, 0},
	1408  		},
	1409  	}, {
	1410  		maker: makeReg{"hello, world", 50},
	1411  		tests: []testFnc{
	1412  			testRemaining{50, 50},
	1413  			testRead{7, "hello, ", nil},
	1414  			testRemaining{43, 43},
	1415  			testRead{5, "world", nil},
	1416  			testRemaining{38, 38},
	1417  			testWriteTo{nil, 0, io.ErrUnexpectedEOF},
	1418  			testRead{1, "", io.ErrUnexpectedEOF},
	1419  			testRemaining{38, 38},
	1420  		},
	1421  	}, {
	1422  		maker: makeReg{"hello, world", 5},
	1423  		tests: []testFnc{
	1424  			testRemaining{5, 5},
	1425  			testRead{0, "", nil},
	1426  			testRead{4, "hell", nil},
	1427  			testRemaining{1, 1},
	1428  			testWriteTo{fileOps{"o"}, 1, nil},
	1429  			testRemaining{0, 0},
	1430  			testWriteTo{nil, 0, nil},
	1431  			testRead{0, "", io.EOF},
	1432  		},
	1433  	}, {
	1434  		maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 8},
	1435  		tests: []testFnc{
	1436  			testRemaining{8, 5},
	1437  			testRead{3, "ab\x00", nil},
	1438  			testRead{10, "\x00\x00cde", io.EOF},
	1439  			testRemaining{0, 0},
	1440  		},
	1441  	}, {
	1442  		maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 8},
	1443  		tests: []testFnc{
	1444  			testRemaining{8, 5},
	1445  			testWriteTo{fileOps{"ab", int64(3), "cde"}, 8, nil},
	1446  			testRemaining{0, 0},
	1447  		},
	1448  	}, {
	1449  		maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 10},
	1450  		tests: []testFnc{
	1451  			testRemaining{10, 5},
	1452  			testRead{100, "ab\x00\x00\x00cde\x00\x00", io.EOF},
	1453  			testRemaining{0, 0},
	1454  		},
	1455  	}, {
	1456  		maker: makeSparse{makeReg{"abc", 5}, sparseDatas{{0, 2}, {5, 3}}, 10},
	1457  		tests: []testFnc{
	1458  			testRemaining{10, 5},
	1459  			testRead{100, "ab\x00\x00\x00c", io.ErrUnexpectedEOF},
	1460  			testRemaining{4, 2},
	1461  		},
	1462  	}, {
	1463  		maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 8},
	1464  		tests: []testFnc{
	1465  			testRemaining{8, 5},
	1466  			testRead{8, "\x00abc\x00\x00de", io.EOF},
	1467  			testRemaining{0, 0},
	1468  		},
	1469  	}, {
	1470  		maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 0}, {6, 0}, {6, 2}}, 8},
	1471  		tests: []testFnc{
	1472  			testRemaining{8, 5},
	1473  			testRead{8, "\x00abc\x00\x00de", io.EOF},
	1474  			testRemaining{0, 0},
	1475  		},
	1476  	}, {
	1477  		maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 0}, {6, 0}, {6, 2}}, 8},
	1478  		tests: []testFnc{
	1479  			testRemaining{8, 5},
	1480  			testWriteTo{fileOps{int64(1), "abc", int64(2), "de"}, 8, nil},
	1481  			testRemaining{0, 0},
	1482  		},
	1483  	}, {
	1484  		maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 10},
	1485  		tests: []testFnc{
	1486  			testRead{100, "\x00abc\x00\x00de\x00\x00", io.EOF},
	1487  		},
	1488  	}, {
	1489  		maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 10},
	1490  		tests: []testFnc{
	1491  			testWriteTo{fileOps{int64(1), "abc", int64(2), "de", int64(1), "\x00"}, 10, nil},
	1492  		},
	1493  	}, {
	1494  		maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}, {8, 0}, {8, 0}, {8, 0}, {8, 0}}, 10},
	1495  		tests: []testFnc{
	1496  			testRead{100, "\x00abc\x00\x00de\x00\x00", io.EOF},
	1497  		},
	1498  	}, {
	1499  		maker: makeSparse{makeReg{"", 0}, sparseDatas{}, 2},
	1500  		tests: []testFnc{
	1501  			testRead{100, "\x00\x00", io.EOF},
	1502  		},
	1503  	}, {
	1504  		maker: makeSparse{makeReg{"", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
	1505  		tests: []testFnc{
	1506  			testRead{100, "\x00", io.ErrUnexpectedEOF},
	1507  		},
	1508  	}, {
	1509  		maker: makeSparse{makeReg{"ab", 2}, sparseDatas{{1, 3}, {6, 5}}, 15},
	1510  		tests: []testFnc{
	1511  			testRead{100, "\x00ab", errMissData},
	1512  		},
	1513  	}, {
	1514  		maker: makeSparse{makeReg{"ab", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
	1515  		tests: []testFnc{
	1516  			testRead{100, "\x00ab", io.ErrUnexpectedEOF},
	1517  		},
	1518  	}, {
	1519  		maker: makeSparse{makeReg{"abc", 3}, sparseDatas{{1, 3}, {6, 5}}, 15},
	1520  		tests: []testFnc{
	1521  			testRead{100, "\x00abc\x00\x00", errMissData},
	1522  		},
	1523  	}, {
	1524  		maker: makeSparse{makeReg{"abc", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
	1525  		tests: []testFnc{
	1526  			testRead{100, "\x00abc\x00\x00", io.ErrUnexpectedEOF},
	1527  		},
	1528  	}, {
	1529  		maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 5}}, 15},
	1530  		tests: []testFnc{
	1531  			testRead{100, "\x00abc\x00\x00de", errMissData},
	1532  		},
	1533  	}, {
	1534  		maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 5}}, 15},
	1535  		tests: []testFnc{
	1536  			testWriteTo{fileOps{int64(1), "abc", int64(2), "de"}, 8, errMissData},
	1537  		},
	1538  	}, {
	1539  		maker: makeSparse{makeReg{"abcde", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
	1540  		tests: []testFnc{
	1541  			testRead{100, "\x00abc\x00\x00de", io.ErrUnexpectedEOF},
	1542  		},
	1543  	}, {
	1544  		maker: makeSparse{makeReg{"abcdefghEXTRA", 13}, sparseDatas{{1, 3}, {6, 5}}, 15},
	1545  		tests: []testFnc{
	1546  			testRemaining{15, 13},
	1547  			testRead{100, "\x00abc\x00\x00defgh\x00\x00\x00\x00", errUnrefData},
	1548  			testWriteTo{nil, 0, errUnrefData},
	1549  			testRemaining{0, 5},
	1550  		},
	1551  	}, {
	1552  		maker: makeSparse{makeReg{"abcdefghEXTRA", 13}, sparseDatas{{1, 3}, {6, 5}}, 15},
	1553  		tests: []testFnc{
	1554  			testRemaining{15, 13},
	1555  			testWriteTo{fileOps{int64(1), "abc", int64(2), "defgh", int64(4)}, 15, errUnrefData},
	1556  			testRead{100, "", errUnrefData},
	1557  			testRemaining{0, 5},
	1558  		},
	1559  	}}
	1560  
	1561  	for i, v := range vectors {
	1562  		var fr fileReader
	1563  		switch maker := v.maker.(type) {
	1564  		case makeReg:
	1565  			r := testNonEmptyReader{strings.NewReader(maker.str)}
	1566  			fr = &regFileReader{r, maker.size}
	1567  		case makeSparse:
	1568  			if !validateSparseEntries(maker.spd, maker.size) {
	1569  				t.Fatalf("invalid sparse map: %v", maker.spd)
	1570  			}
	1571  			sph := invertSparseEntries(maker.spd, maker.size)
	1572  			r := testNonEmptyReader{strings.NewReader(maker.makeReg.str)}
	1573  			fr = &regFileReader{r, maker.makeReg.size}
	1574  			fr = &sparseFileReader{fr, sph, 0}
	1575  		default:
	1576  			t.Fatalf("test %d, unknown make operation: %T", i, maker)
	1577  		}
	1578  
	1579  		for j, tf := range v.tests {
	1580  			switch tf := tf.(type) {
	1581  			case testRead:
	1582  				b := make([]byte, tf.cnt)
	1583  				n, err := fr.Read(b)
	1584  				if got := string(b[:n]); got != tf.wantStr || err != tf.wantErr {
	1585  					t.Errorf("test %d.%d, Read(%d):\ngot	(%q, %v)\nwant (%q, %v)", i, j, tf.cnt, got, err, tf.wantStr, tf.wantErr)
	1586  				}
	1587  			case testWriteTo:
	1588  				f := &testFile{ops: tf.ops}
	1589  				got, err := fr.WriteTo(f)
	1590  				if _, ok := err.(testError); ok {
	1591  					t.Errorf("test %d.%d, WriteTo(): %v", i, j, err)
	1592  				} else if got != tf.wantCnt || err != tf.wantErr {
	1593  					t.Errorf("test %d.%d, WriteTo() = (%d, %v), want (%d, %v)", i, j, got, err, tf.wantCnt, tf.wantErr)
	1594  				}
	1595  				if len(f.ops) > 0 {
	1596  					t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops))
	1597  				}
	1598  			case testRemaining:
	1599  				if got := fr.LogicalRemaining(); got != tf.wantLCnt {
	1600  					t.Errorf("test %d.%d, LogicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt)
	1601  				}
	1602  				if got := fr.PhysicalRemaining(); got != tf.wantPCnt {
	1603  					t.Errorf("test %d.%d, PhysicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt)
	1604  				}
	1605  			default:
	1606  				t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)
	1607  			}
	1608  		}
	1609  	}
	1610  }
	1611  

View as plain text