github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/archive/tar/common.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 implements access to tar archives. 6 // 7 // Tape archives (tar) are a file format for storing a sequence of files that 8 // can be read and written in a streaming manner. 9 // This package aims to cover most variations of the format, 10 // including those produced by GNU and BSD tar tools. 11 package tar 12 13 import ( 14 "errors" 15 "fmt" 16 "io/fs" 17 "math" 18 "path" 19 "reflect" 20 "strconv" 21 "strings" 22 "time" 23 ) 24 25 // BUG: Use of the Uid and Gid fields in Header could overflow on 32-bit 26 // architectures. If a large value is encountered when decoding, the result 27 // stored in Header will be the truncated version. 28 29 var ( 30 ErrHeader = errors.New("archive/tar: invalid tar header") 31 ErrWriteTooLong = errors.New("archive/tar: write too long") 32 ErrFieldTooLong = errors.New("archive/tar: header field too long") 33 ErrWriteAfterClose = errors.New("archive/tar: write after close") 34 errMissData = errors.New("archive/tar: sparse file references non-existent data") 35 errUnrefData = errors.New("archive/tar: sparse file contains unreferenced data") 36 errWriteHole = errors.New("archive/tar: write non-NUL byte in sparse hole") 37 ) 38 39 type headerError []string 40 41 func (he headerError) Error() string { 42 const prefix = "archive/tar: cannot encode header" 43 var ss []string 44 for _, s := range he { 45 if s != "" { 46 ss = append(ss, s) 47 } 48 } 49 if len(ss) == 0 { 50 return prefix 51 } 52 return fmt.Sprintf("%s: %v", prefix, strings.Join(ss, "; and ")) 53 } 54 55 // Type flags for Header.Typeflag. 56 const ( 57 // Type '0' indicates a regular file. 58 TypeReg = '0' 59 TypeRegA = '\x00' // Deprecated: Use TypeReg instead. 60 61 // Type '1' to '6' are header-only flags and may not have a data body. 62 TypeLink = '1' // Hard link 63 TypeSymlink = '2' // Symbolic link 64 TypeChar = '3' // Character device node 65 TypeBlock = '4' // Block device node 66 TypeDir = '5' // Directory 67 TypeFifo = '6' // FIFO node 68 69 // Type '7' is reserved. 70 TypeCont = '7' 71 72 // Type 'x' is used by the PAX format to store key-value records that 73 // are only relevant to the next file. 74 // This package transparently handles these types. 75 TypeXHeader = 'x' 76 77 // Type 'g' is used by the PAX format to store key-value records that 78 // are relevant to all subsequent files. 79 // This package only supports parsing and composing such headers, 80 // but does not currently support persisting the global state across files. 81 TypeXGlobalHeader = 'g' 82 83 // Type 'S' indicates a sparse file in the GNU format. 84 TypeGNUSparse = 'S' 85 86 // Types 'L' and 'K' are used by the GNU format for a meta file 87 // used to store the path or link name for the next file. 88 // This package transparently handles these types. 89 TypeGNULongName = 'L' 90 TypeGNULongLink = 'K' 91 ) 92 93 // Keywords for PAX extended header records. 94 const ( 95 paxNone = "" // Indicates that no PAX key is suitable 96 paxPath = "path" 97 paxLinkpath = "linkpath" 98 paxSize = "size" 99 paxUid = "uid" 100 paxGid = "gid" 101 paxUname = "uname" 102 paxGname = "gname" 103 paxMtime = "mtime" 104 paxAtime = "atime" 105 paxCtime = "ctime" // Removed from later revision of PAX spec, but was valid 106 paxCharset = "charset" // Currently unused 107 paxComment = "comment" // Currently unused 108 109 paxSchilyXattr = "SCHILY.xattr." 110 111 // Keywords for GNU sparse files in a PAX extended header. 112 paxGNUSparse = "GNU.sparse." 113 paxGNUSparseNumBlocks = "GNU.sparse.numblocks" 114 paxGNUSparseOffset = "GNU.sparse.offset" 115 paxGNUSparseNumBytes = "GNU.sparse.numbytes" 116 paxGNUSparseMap = "GNU.sparse.map" 117 paxGNUSparseName = "GNU.sparse.name" 118 paxGNUSparseMajor = "GNU.sparse.major" 119 paxGNUSparseMinor = "GNU.sparse.minor" 120 paxGNUSparseSize = "GNU.sparse.size" 121 paxGNUSparseRealSize = "GNU.sparse.realsize" 122 ) 123 124 // basicKeys is a set of the PAX keys for which we have built-in support. 125 // This does not contain "charset" or "comment", which are both PAX-specific, 126 // so adding them as first-class features of Header is unlikely. 127 // Users can use the PAXRecords field to set it themselves. 128 var basicKeys = map[string]bool{ 129 paxPath: true, paxLinkpath: true, paxSize: true, paxUid: true, paxGid: true, 130 paxUname: true, paxGname: true, paxMtime: true, paxAtime: true, paxCtime: true, 131 } 132 133 // A Header represents a single header in a tar archive. 134 // Some fields may not be populated. 135 // 136 // For forward compatibility, users that retrieve a Header from Reader.Next, 137 // mutate it in some ways, and then pass it back to Writer.WriteHeader 138 // should do so by creating a new Header and copying the fields 139 // that they are interested in preserving. 140 type Header struct { 141 // Typeflag is the type of header entry. 142 // The zero value is automatically promoted to either TypeReg or TypeDir 143 // depending on the presence of a trailing slash in Name. 144 Typeflag byte 145 146 Name string // Name of file entry 147 Linkname string // Target name of link (valid for TypeLink or TypeSymlink) 148 149 Size int64 // Logical file size in bytes 150 Mode int64 // Permission and mode bits 151 Uid int // User ID of owner 152 Gid int // Group ID of owner 153 Uname string // User name of owner 154 Gname string // Group name of owner 155 156 // If the Format is unspecified, then Writer.WriteHeader rounds ModTime 157 // to the nearest second and ignores the AccessTime and ChangeTime fields. 158 // 159 // To use AccessTime or ChangeTime, specify the Format as PAX or GNU. 160 // To use sub-second resolution, specify the Format as PAX. 161 ModTime time.Time // Modification time 162 AccessTime time.Time // Access time (requires either PAX or GNU support) 163 ChangeTime time.Time // Change time (requires either PAX or GNU support) 164 165 Devmajor int64 // Major device number (valid for TypeChar or TypeBlock) 166 Devminor int64 // Minor device number (valid for TypeChar or TypeBlock) 167 168 // Xattrs stores extended attributes as PAX records under the 169 // "SCHILY.xattr." namespace. 170 // 171 // The following are semantically equivalent: 172 // h.Xattrs[key] = value 173 // h.PAXRecords["SCHILY.xattr."+key] = value 174 // 175 // When Writer.WriteHeader is called, the contents of Xattrs will take 176 // precedence over those in PAXRecords. 177 // 178 // Deprecated: Use PAXRecords instead. 179 Xattrs map[string]string 180 181 // PAXRecords is a map of PAX extended header records. 182 // 183 // User-defined records should have keys of the following form: 184 // VENDOR.keyword 185 // Where VENDOR is some namespace in all uppercase, and keyword may 186 // not contain the '=' character (e.g., "GOLANG.pkg.version"). 187 // The key and value should be non-empty UTF-8 strings. 188 // 189 // When Writer.WriteHeader is called, PAX records derived from the 190 // other fields in Header take precedence over PAXRecords. 191 PAXRecords map[string]string 192 193 // Format specifies the format of the tar header. 194 // 195 // This is set by Reader.Next as a best-effort guess at the format. 196 // Since the Reader liberally reads some non-compliant files, 197 // it is possible for this to be FormatUnknown. 198 // 199 // If the format is unspecified when Writer.WriteHeader is called, 200 // then it uses the first format (in the order of USTAR, PAX, GNU) 201 // capable of encoding this Header (see Format). 202 Format Format 203 } 204 205 // sparseEntry represents a Length-sized fragment at Offset in the file. 206 type sparseEntry struct{ Offset, Length int64 } 207 208 func (s sparseEntry) endOffset() int64 { return s.Offset + s.Length } 209 210 // A sparse file can be represented as either a sparseDatas or a sparseHoles. 211 // As long as the total size is known, they are equivalent and one can be 212 // converted to the other form and back. The various tar formats with sparse 213 // file support represent sparse files in the sparseDatas form. That is, they 214 // specify the fragments in the file that has data, and treat everything else as 215 // having zero bytes. As such, the encoding and decoding logic in this package 216 // deals with sparseDatas. 217 // 218 // However, the external API uses sparseHoles instead of sparseDatas because the 219 // zero value of sparseHoles logically represents a normal file (i.e., there are 220 // no holes in it). On the other hand, the zero value of sparseDatas implies 221 // that the file has no data in it, which is rather odd. 222 // 223 // As an example, if the underlying raw file contains the 10-byte data: 224 // 225 // var compactFile = "abcdefgh" 226 // 227 // And the sparse map has the following entries: 228 // 229 // var spd sparseDatas = []sparseEntry{ 230 // {Offset: 2, Length: 5}, // Data fragment for 2..6 231 // {Offset: 18, Length: 3}, // Data fragment for 18..20 232 // } 233 // var sph sparseHoles = []sparseEntry{ 234 // {Offset: 0, Length: 2}, // Hole fragment for 0..1 235 // {Offset: 7, Length: 11}, // Hole fragment for 7..17 236 // {Offset: 21, Length: 4}, // Hole fragment for 21..24 237 // } 238 // 239 // Then the content of the resulting sparse file with a Header.Size of 25 is: 240 // 241 // var sparseFile = "\x00"*2 + "abcde" + "\x00"*11 + "fgh" + "\x00"*4 242 type ( 243 sparseDatas []sparseEntry 244 sparseHoles []sparseEntry 245 ) 246 247 // validateSparseEntries reports whether sp is a valid sparse map. 248 // It does not matter whether sp represents data fragments or hole fragments. 249 func validateSparseEntries(sp []sparseEntry, size int64) bool { 250 // Validate all sparse entries. These are the same checks as performed by 251 // the BSD tar utility. 252 if size < 0 { 253 return false 254 } 255 var pre sparseEntry 256 for _, cur := range sp { 257 switch { 258 case cur.Offset < 0 || cur.Length < 0: 259 return false // Negative values are never okay 260 case cur.Offset > math.MaxInt64-cur.Length: 261 return false // Integer overflow with large length 262 case cur.endOffset() > size: 263 return false // Region extends beyond the actual size 264 case pre.endOffset() > cur.Offset: 265 return false // Regions cannot overlap and must be in order 266 } 267 pre = cur 268 } 269 return true 270 } 271 272 // alignSparseEntries mutates src and returns dst where each fragment's 273 // starting offset is aligned up to the nearest block edge, and each 274 // ending offset is aligned down to the nearest block edge. 275 // 276 // Even though the Go tar Reader and the BSD tar utility can handle entries 277 // with arbitrary offsets and lengths, the GNU tar utility can only handle 278 // offsets and lengths that are multiples of blockSize. 279 func alignSparseEntries(src []sparseEntry, size int64) []sparseEntry { 280 dst := src[:0] 281 for _, s := range src { 282 pos, end := s.Offset, s.endOffset() 283 pos += blockPadding(+pos) // Round-up to nearest blockSize 284 if end != size { 285 end -= blockPadding(-end) // Round-down to nearest blockSize 286 } 287 if pos < end { 288 dst = append(dst, sparseEntry{Offset: pos, Length: end - pos}) 289 } 290 } 291 return dst 292 } 293 294 // invertSparseEntries converts a sparse map from one form to the other. 295 // If the input is sparseHoles, then it will output sparseDatas and vice-versa. 296 // The input must have been already validated. 297 // 298 // This function mutates src and returns a normalized map where: 299 // - adjacent fragments are coalesced together 300 // - only the last fragment may be empty 301 // - the endOffset of the last fragment is the total size 302 func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry { 303 dst := src[:0] 304 var pre sparseEntry 305 for _, cur := range src { 306 if cur.Length == 0 { 307 continue // Skip empty fragments 308 } 309 pre.Length = cur.Offset - pre.Offset 310 if pre.Length > 0 { 311 dst = append(dst, pre) // Only add non-empty fragments 312 } 313 pre.Offset = cur.endOffset() 314 } 315 pre.Length = size - pre.Offset // Possibly the only empty fragment 316 return append(dst, pre) 317 } 318 319 // fileState tracks the number of logical (includes sparse holes) and physical 320 // (actual in tar archive) bytes remaining for the current file. 321 // 322 // Invariant: logicalRemaining >= physicalRemaining 323 type fileState interface { 324 logicalRemaining() int64 325 physicalRemaining() int64 326 } 327 328 // allowedFormats determines which formats can be used. 329 // The value returned is the logical OR of multiple possible formats. 330 // If the value is FormatUnknown, then the input Header cannot be encoded 331 // and an error is returned explaining why. 332 // 333 // As a by-product of checking the fields, this function returns paxHdrs, which 334 // contain all fields that could not be directly encoded. 335 // A value receiver ensures that this method does not mutate the source Header. 336 func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err error) { 337 format = FormatUSTAR | FormatPAX | FormatGNU 338 paxHdrs = make(map[string]string) 339 340 var whyNoUSTAR, whyNoPAX, whyNoGNU string 341 var preferPAX bool // Prefer PAX over USTAR 342 verifyString := func(s string, size int, name, paxKey string) { 343 // NUL-terminator is optional for path and linkpath. 344 // Technically, it is required for uname and gname, 345 // but neither GNU nor BSD tar checks for it. 346 tooLong := len(s) > size 347 allowLongGNU := paxKey == paxPath || paxKey == paxLinkpath 348 if hasNUL(s) || (tooLong && !allowLongGNU) { 349 whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%q", name, s) 350 format.mustNotBe(FormatGNU) 351 } 352 if !isASCII(s) || tooLong { 353 canSplitUSTAR := paxKey == paxPath 354 if _, _, ok := splitUSTARPath(s); !canSplitUSTAR || !ok { 355 whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%q", name, s) 356 format.mustNotBe(FormatUSTAR) 357 } 358 if paxKey == paxNone { 359 whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%q", name, s) 360 format.mustNotBe(FormatPAX) 361 } else { 362 paxHdrs[paxKey] = s 363 } 364 } 365 if v, ok := h.PAXRecords[paxKey]; ok && v == s { 366 paxHdrs[paxKey] = v 367 } 368 } 369 verifyNumeric := func(n int64, size int, name, paxKey string) { 370 if !fitsInBase256(size, n) { 371 whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%d", name, n) 372 format.mustNotBe(FormatGNU) 373 } 374 if !fitsInOctal(size, n) { 375 whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%d", name, n) 376 format.mustNotBe(FormatUSTAR) 377 if paxKey == paxNone { 378 whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%d", name, n) 379 format.mustNotBe(FormatPAX) 380 } else { 381 paxHdrs[paxKey] = strconv.FormatInt(n, 10) 382 } 383 } 384 if v, ok := h.PAXRecords[paxKey]; ok && v == strconv.FormatInt(n, 10) { 385 paxHdrs[paxKey] = v 386 } 387 } 388 verifyTime := func(ts time.Time, size int, name, paxKey string) { 389 if ts.IsZero() { 390 return // Always okay 391 } 392 if !fitsInBase256(size, ts.Unix()) { 393 whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%v", name, ts) 394 format.mustNotBe(FormatGNU) 395 } 396 isMtime := paxKey == paxMtime 397 fitsOctal := fitsInOctal(size, ts.Unix()) 398 if (isMtime && !fitsOctal) || !isMtime { 399 whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%v", name, ts) 400 format.mustNotBe(FormatUSTAR) 401 } 402 needsNano := ts.Nanosecond() != 0 403 if !isMtime || !fitsOctal || needsNano { 404 preferPAX = true // USTAR may truncate sub-second measurements 405 if paxKey == paxNone { 406 whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%v", name, ts) 407 format.mustNotBe(FormatPAX) 408 } else { 409 paxHdrs[paxKey] = formatPAXTime(ts) 410 } 411 } 412 if v, ok := h.PAXRecords[paxKey]; ok && v == formatPAXTime(ts) { 413 paxHdrs[paxKey] = v 414 } 415 } 416 417 // Check basic fields. 418 var blk block 419 v7 := blk.toV7() 420 ustar := blk.toUSTAR() 421 gnu := blk.toGNU() 422 verifyString(h.Name, len(v7.name()), "Name", paxPath) 423 verifyString(h.Linkname, len(v7.linkName()), "Linkname", paxLinkpath) 424 verifyString(h.Uname, len(ustar.userName()), "Uname", paxUname) 425 verifyString(h.Gname, len(ustar.groupName()), "Gname", paxGname) 426 verifyNumeric(h.Mode, len(v7.mode()), "Mode", paxNone) 427 verifyNumeric(int64(h.Uid), len(v7.uid()), "Uid", paxUid) 428 verifyNumeric(int64(h.Gid), len(v7.gid()), "Gid", paxGid) 429 verifyNumeric(h.Size, len(v7.size()), "Size", paxSize) 430 verifyNumeric(h.Devmajor, len(ustar.devMajor()), "Devmajor", paxNone) 431 verifyNumeric(h.Devminor, len(ustar.devMinor()), "Devminor", paxNone) 432 verifyTime(h.ModTime, len(v7.modTime()), "ModTime", paxMtime) 433 verifyTime(h.AccessTime, len(gnu.accessTime()), "AccessTime", paxAtime) 434 verifyTime(h.ChangeTime, len(gnu.changeTime()), "ChangeTime", paxCtime) 435 436 // Check for header-only types. 437 var whyOnlyPAX, whyOnlyGNU string 438 switch h.Typeflag { 439 case TypeReg, TypeChar, TypeBlock, TypeFifo, TypeGNUSparse: 440 // Exclude TypeLink and TypeSymlink, since they may reference directories. 441 if strings.HasSuffix(h.Name, "/") { 442 return FormatUnknown, nil, headerError{"filename may not have trailing slash"} 443 } 444 case TypeXHeader, TypeGNULongName, TypeGNULongLink: 445 return FormatUnknown, nil, headerError{"cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers"} 446 case TypeXGlobalHeader: 447 h2 := Header{Name: h.Name, Typeflag: h.Typeflag, Xattrs: h.Xattrs, PAXRecords: h.PAXRecords, Format: h.Format} 448 if !reflect.DeepEqual(h, h2) { 449 return FormatUnknown, nil, headerError{"only PAXRecords should be set for TypeXGlobalHeader"} 450 } 451 whyOnlyPAX = "only PAX supports TypeXGlobalHeader" 452 format.mayOnlyBe(FormatPAX) 453 } 454 if !isHeaderOnlyType(h.Typeflag) && h.Size < 0 { 455 return FormatUnknown, nil, headerError{"negative size on header-only type"} 456 } 457 458 // Check PAX records. 459 if len(h.Xattrs) > 0 { 460 for k, v := range h.Xattrs { 461 paxHdrs[paxSchilyXattr+k] = v 462 } 463 whyOnlyPAX = "only PAX supports Xattrs" 464 format.mayOnlyBe(FormatPAX) 465 } 466 if len(h.PAXRecords) > 0 { 467 for k, v := range h.PAXRecords { 468 switch _, exists := paxHdrs[k]; { 469 case exists: 470 continue // Do not overwrite existing records 471 case h.Typeflag == TypeXGlobalHeader: 472 paxHdrs[k] = v // Copy all records 473 case !basicKeys[k] && !strings.HasPrefix(k, paxGNUSparse): 474 paxHdrs[k] = v // Ignore local records that may conflict 475 } 476 } 477 whyOnlyPAX = "only PAX supports PAXRecords" 478 format.mayOnlyBe(FormatPAX) 479 } 480 for k, v := range paxHdrs { 481 if !validPAXRecord(k, v) { 482 return FormatUnknown, nil, headerError{fmt.Sprintf("invalid PAX record: %q", k+" = "+v)} 483 } 484 } 485 486 // TODO(dsnet): Re-enable this when adding sparse support. 487 // See https://golang.org/issue/22735 488 /* 489 // Check sparse files. 490 if len(h.SparseHoles) > 0 || h.Typeflag == TypeGNUSparse { 491 if isHeaderOnlyType(h.Typeflag) { 492 return FormatUnknown, nil, headerError{"header-only type cannot be sparse"} 493 } 494 if !validateSparseEntries(h.SparseHoles, h.Size) { 495 return FormatUnknown, nil, headerError{"invalid sparse holes"} 496 } 497 if h.Typeflag == TypeGNUSparse { 498 whyOnlyGNU = "only GNU supports TypeGNUSparse" 499 format.mayOnlyBe(FormatGNU) 500 } else { 501 whyNoGNU = "GNU supports sparse files only with TypeGNUSparse" 502 format.mustNotBe(FormatGNU) 503 } 504 whyNoUSTAR = "USTAR does not support sparse files" 505 format.mustNotBe(FormatUSTAR) 506 } 507 */ 508 509 // Check desired format. 510 if wantFormat := h.Format; wantFormat != FormatUnknown { 511 if wantFormat.has(FormatPAX) && !preferPAX { 512 wantFormat.mayBe(FormatUSTAR) // PAX implies USTAR allowed too 513 } 514 format.mayOnlyBe(wantFormat) // Set union of formats allowed and format wanted 515 } 516 if format == FormatUnknown { 517 switch h.Format { 518 case FormatUSTAR: 519 err = headerError{"Format specifies USTAR", whyNoUSTAR, whyOnlyPAX, whyOnlyGNU} 520 case FormatPAX: 521 err = headerError{"Format specifies PAX", whyNoPAX, whyOnlyGNU} 522 case FormatGNU: 523 err = headerError{"Format specifies GNU", whyNoGNU, whyOnlyPAX} 524 default: 525 err = headerError{whyNoUSTAR, whyNoPAX, whyNoGNU, whyOnlyPAX, whyOnlyGNU} 526 } 527 } 528 return format, paxHdrs, err 529 } 530 531 // FileInfo returns an fs.FileInfo for the Header. 532 func (h *Header) FileInfo() fs.FileInfo { 533 return headerFileInfo{h} 534 } 535 536 // headerFileInfo implements fs.FileInfo. 537 type headerFileInfo struct { 538 h *Header 539 } 540 541 func (fi headerFileInfo) Size() int64 { return fi.h.Size } 542 func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() } 543 func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime } 544 func (fi headerFileInfo) Sys() any { return fi.h } 545 546 // Name returns the base name of the file. 547 func (fi headerFileInfo) Name() string { 548 if fi.IsDir() { 549 return path.Base(path.Clean(fi.h.Name)) 550 } 551 return path.Base(fi.h.Name) 552 } 553 554 // Mode returns the permission and mode bits for the headerFileInfo. 555 func (fi headerFileInfo) Mode() (mode fs.FileMode) { 556 // Set file permission bits. 557 mode = fs.FileMode(fi.h.Mode).Perm() 558 559 // Set setuid, setgid and sticky bits. 560 if fi.h.Mode&c_ISUID != 0 { 561 mode |= fs.ModeSetuid 562 } 563 if fi.h.Mode&c_ISGID != 0 { 564 mode |= fs.ModeSetgid 565 } 566 if fi.h.Mode&c_ISVTX != 0 { 567 mode |= fs.ModeSticky 568 } 569 570 // Set file mode bits; clear perm, setuid, setgid, and sticky bits. 571 switch m := fs.FileMode(fi.h.Mode) &^ 07777; m { 572 case c_ISDIR: 573 mode |= fs.ModeDir 574 case c_ISFIFO: 575 mode |= fs.ModeNamedPipe 576 case c_ISLNK: 577 mode |= fs.ModeSymlink 578 case c_ISBLK: 579 mode |= fs.ModeDevice 580 case c_ISCHR: 581 mode |= fs.ModeDevice 582 mode |= fs.ModeCharDevice 583 case c_ISSOCK: 584 mode |= fs.ModeSocket 585 } 586 587 switch fi.h.Typeflag { 588 case TypeSymlink: 589 mode |= fs.ModeSymlink 590 case TypeChar: 591 mode |= fs.ModeDevice 592 mode |= fs.ModeCharDevice 593 case TypeBlock: 594 mode |= fs.ModeDevice 595 case TypeDir: 596 mode |= fs.ModeDir 597 case TypeFifo: 598 mode |= fs.ModeNamedPipe 599 } 600 601 return mode 602 } 603 604 // sysStat, if non-nil, populates h from system-dependent fields of fi. 605 var sysStat func(fi fs.FileInfo, h *Header) error 606 607 const ( 608 // Mode constants from the USTAR spec: 609 // See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06 610 c_ISUID = 04000 // Set uid 611 c_ISGID = 02000 // Set gid 612 c_ISVTX = 01000 // Save text (sticky bit) 613 614 // Common Unix mode constants; these are not defined in any common tar standard. 615 // Header.FileInfo understands these, but FileInfoHeader will never produce these. 616 c_ISDIR = 040000 // Directory 617 c_ISFIFO = 010000 // FIFO 618 c_ISREG = 0100000 // Regular file 619 c_ISLNK = 0120000 // Symbolic link 620 c_ISBLK = 060000 // Block special file 621 c_ISCHR = 020000 // Character special file 622 c_ISSOCK = 0140000 // Socket 623 ) 624 625 // FileInfoHeader creates a partially-populated Header from fi. 626 // If fi describes a symlink, FileInfoHeader records link as the link target. 627 // If fi describes a directory, a slash is appended to the name. 628 // 629 // Since fs.FileInfo's Name method only returns the base name of 630 // the file it describes, it may be necessary to modify Header.Name 631 // to provide the full path name of the file. 632 func FileInfoHeader(fi fs.FileInfo, link string) (*Header, error) { 633 if fi == nil { 634 return nil, errors.New("archive/tar: FileInfo is nil") 635 } 636 fm := fi.Mode() 637 h := &Header{ 638 Name: fi.Name(), 639 ModTime: fi.ModTime(), 640 Mode: int64(fm.Perm()), // or'd with c_IS* constants later 641 } 642 switch { 643 case fm.IsRegular(): 644 h.Typeflag = TypeReg 645 h.Size = fi.Size() 646 case fi.IsDir(): 647 h.Typeflag = TypeDir 648 h.Name += "/" 649 case fm&fs.ModeSymlink != 0: 650 h.Typeflag = TypeSymlink 651 h.Linkname = link 652 case fm&fs.ModeDevice != 0: 653 if fm&fs.ModeCharDevice != 0 { 654 h.Typeflag = TypeChar 655 } else { 656 h.Typeflag = TypeBlock 657 } 658 case fm&fs.ModeNamedPipe != 0: 659 h.Typeflag = TypeFifo 660 case fm&fs.ModeSocket != 0: 661 return nil, fmt.Errorf("archive/tar: sockets not supported") 662 default: 663 return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm) 664 } 665 if fm&fs.ModeSetuid != 0 { 666 h.Mode |= c_ISUID 667 } 668 if fm&fs.ModeSetgid != 0 { 669 h.Mode |= c_ISGID 670 } 671 if fm&fs.ModeSticky != 0 { 672 h.Mode |= c_ISVTX 673 } 674 // If possible, populate additional fields from OS-specific 675 // FileInfo fields. 676 if sys, ok := fi.Sys().(*Header); ok { 677 // This FileInfo came from a Header (not the OS). Use the 678 // original Header to populate all remaining fields. 679 h.Uid = sys.Uid 680 h.Gid = sys.Gid 681 h.Uname = sys.Uname 682 h.Gname = sys.Gname 683 h.AccessTime = sys.AccessTime 684 h.ChangeTime = sys.ChangeTime 685 if sys.Xattrs != nil { 686 h.Xattrs = make(map[string]string) 687 for k, v := range sys.Xattrs { 688 h.Xattrs[k] = v 689 } 690 } 691 if sys.Typeflag == TypeLink { 692 // hard link 693 h.Typeflag = TypeLink 694 h.Size = 0 695 h.Linkname = sys.Linkname 696 } 697 if sys.PAXRecords != nil { 698 h.PAXRecords = make(map[string]string) 699 for k, v := range sys.PAXRecords { 700 h.PAXRecords[k] = v 701 } 702 } 703 } 704 if sysStat != nil { 705 return h, sysStat(fi, h) 706 } 707 return h, nil 708 } 709 710 // isHeaderOnlyType checks if the given type flag is of the type that has no 711 // data section even if a size is specified. 712 func isHeaderOnlyType(flag byte) bool { 713 switch flag { 714 case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo: 715 return true 716 default: 717 return false 718 } 719 } 720 721 func min(a, b int64) int64 { 722 if a < b { 723 return a 724 } 725 return b 726 }