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