|  | // Copyright 2009 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | // Package tar implements access to tar archives. | 
|  | // | 
|  | // Tape archives (tar) are a file format for storing a sequence of files that | 
|  | // can be read and written in a streaming manner. | 
|  | // This package aims to cover most variations of the format, | 
|  | // including those produced by GNU and BSD tar tools. | 
|  | package tar | 
|  |  | 
|  | import ( | 
|  | "errors" | 
|  | "fmt" | 
|  | "math" | 
|  | "os" | 
|  | "path" | 
|  | "reflect" | 
|  | "strconv" | 
|  | "strings" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | // BUG: Use of the Uid and Gid fields in Header could overflow on 32-bit | 
|  | // architectures. If a large value is encountered when decoding, the result | 
|  | // stored in Header will be the truncated version. | 
|  |  | 
|  | var ( | 
|  | ErrHeader          = errors.New("archive/tar: invalid tar header") | 
|  | ErrWriteTooLong    = errors.New("archive/tar: write too long") | 
|  | ErrFieldTooLong    = errors.New("archive/tar: header field too long") | 
|  | ErrWriteAfterClose = errors.New("archive/tar: write after close") | 
|  | errMissData        = errors.New("archive/tar: sparse file references non-existent data") | 
|  | errUnrefData       = errors.New("archive/tar: sparse file contains unreferenced data") | 
|  | errWriteHole       = errors.New("archive/tar: write non-NUL byte in sparse hole") | 
|  | ) | 
|  |  | 
|  | type headerError []string | 
|  |  | 
|  | func (he headerError) Error() string { | 
|  | const prefix = "archive/tar: cannot encode header" | 
|  | var ss []string | 
|  | for _, s := range he { | 
|  | if s != "" { | 
|  | ss = append(ss, s) | 
|  | } | 
|  | } | 
|  | if len(ss) == 0 { | 
|  | return prefix | 
|  | } | 
|  | return fmt.Sprintf("%s: %v", prefix, strings.Join(ss, "; and ")) | 
|  | } | 
|  |  | 
|  | // Type flags for Header.Typeflag. | 
|  | const ( | 
|  | // Type '0' indicates a regular file. | 
|  | TypeReg  = '0' | 
|  | TypeRegA = '\x00' // Deprecated: Use TypeReg instead. | 
|  |  | 
|  | // Type '1' to '6' are header-only flags and may not have a data body. | 
|  | TypeLink    = '1' // Hard link | 
|  | TypeSymlink = '2' // Symbolic link | 
|  | TypeChar    = '3' // Character device node | 
|  | TypeBlock   = '4' // Block device node | 
|  | TypeDir     = '5' // Directory | 
|  | TypeFifo    = '6' // FIFO node | 
|  |  | 
|  | // Type '7' is reserved. | 
|  | TypeCont = '7' | 
|  |  | 
|  | // Type 'x' is used by the PAX format to store key-value records that | 
|  | // are only relevant to the next file. | 
|  | // This package transparently handles these types. | 
|  | TypeXHeader = 'x' | 
|  |  | 
|  | // Type 'g' is used by the PAX format to store key-value records that | 
|  | // are relevant to all subsequent files. | 
|  | // This package only supports parsing and composing such headers, | 
|  | // but does not currently support persisting the global state across files. | 
|  | TypeXGlobalHeader = 'g' | 
|  |  | 
|  | // Type 'S' indicates a sparse file in the GNU format. | 
|  | TypeGNUSparse = 'S' | 
|  |  | 
|  | // Types 'L' and 'K' are used by the GNU format for a meta file | 
|  | // used to store the path or link name for the next file. | 
|  | // This package transparently handles these types. | 
|  | TypeGNULongName = 'L' | 
|  | TypeGNULongLink = 'K' | 
|  | ) | 
|  |  | 
|  | // Keywords for PAX extended header records. | 
|  | const ( | 
|  | paxNone     = "" // Indicates that no PAX key is suitable | 
|  | paxPath     = "path" | 
|  | paxLinkpath = "linkpath" | 
|  | paxSize     = "size" | 
|  | paxUid      = "uid" | 
|  | paxGid      = "gid" | 
|  | paxUname    = "uname" | 
|  | paxGname    = "gname" | 
|  | paxMtime    = "mtime" | 
|  | paxAtime    = "atime" | 
|  | paxCtime    = "ctime"   // Removed from later revision of PAX spec, but was valid | 
|  | paxCharset  = "charset" // Currently unused | 
|  | paxComment  = "comment" // Currently unused | 
|  |  | 
|  | paxSchilyXattr = "SCHILY.xattr." | 
|  |  | 
|  | // Keywords for GNU sparse files in a PAX extended header. | 
|  | paxGNUSparse          = "GNU.sparse." | 
|  | paxGNUSparseNumBlocks = "GNU.sparse.numblocks" | 
|  | paxGNUSparseOffset    = "GNU.sparse.offset" | 
|  | paxGNUSparseNumBytes  = "GNU.sparse.numbytes" | 
|  | paxGNUSparseMap       = "GNU.sparse.map" | 
|  | paxGNUSparseName      = "GNU.sparse.name" | 
|  | paxGNUSparseMajor     = "GNU.sparse.major" | 
|  | paxGNUSparseMinor     = "GNU.sparse.minor" | 
|  | paxGNUSparseSize      = "GNU.sparse.size" | 
|  | paxGNUSparseRealSize  = "GNU.sparse.realsize" | 
|  | ) | 
|  |  | 
|  | // basicKeys is a set of the PAX keys for which we have built-in support. | 
|  | // This does not contain "charset" or "comment", which are both PAX-specific, | 
|  | // so adding them as first-class features of Header is unlikely. | 
|  | // Users can use the PAXRecords field to set it themselves. | 
|  | var basicKeys = map[string]bool{ | 
|  | paxPath: true, paxLinkpath: true, paxSize: true, paxUid: true, paxGid: true, | 
|  | paxUname: true, paxGname: true, paxMtime: true, paxAtime: true, paxCtime: true, | 
|  | } | 
|  |  | 
|  | // A Header represents a single header in a tar archive. | 
|  | // Some fields may not be populated. | 
|  | // | 
|  | // For forward compatibility, users that retrieve a Header from Reader.Next, | 
|  | // mutate it in some ways, and then pass it back to Writer.WriteHeader | 
|  | // should do so by creating a new Header and copying the fields | 
|  | // that they are interested in preserving. | 
|  | type Header struct { | 
|  | // Typeflag is the type of header entry. | 
|  | // The zero value is automatically promoted to either TypeReg or TypeDir | 
|  | // depending on the presence of a trailing slash in Name. | 
|  | Typeflag byte | 
|  |  | 
|  | Name     string // Name of file entry | 
|  | Linkname string // Target name of link (valid for TypeLink or TypeSymlink) | 
|  |  | 
|  | Size  int64  // Logical file size in bytes | 
|  | Mode  int64  // Permission and mode bits | 
|  | Uid   int    // User ID of owner | 
|  | Gid   int    // Group ID of owner | 
|  | Uname string // User name of owner | 
|  | Gname string // Group name of owner | 
|  |  | 
|  | // If the Format is unspecified, then Writer.WriteHeader rounds ModTime | 
|  | // to the nearest second and ignores the AccessTime and ChangeTime fields. | 
|  | // | 
|  | // To use AccessTime or ChangeTime, specify the Format as PAX or GNU. | 
|  | // To use sub-second resolution, specify the Format as PAX. | 
|  | ModTime    time.Time // Modification time | 
|  | AccessTime time.Time // Access time (requires either PAX or GNU support) | 
|  | ChangeTime time.Time // Change time (requires either PAX or GNU support) | 
|  |  | 
|  | Devmajor int64 // Major device number (valid for TypeChar or TypeBlock) | 
|  | Devminor int64 // Minor device number (valid for TypeChar or TypeBlock) | 
|  |  | 
|  | // Xattrs stores extended attributes as PAX records under the | 
|  | // "SCHILY.xattr." namespace. | 
|  | // | 
|  | // The following are semantically equivalent: | 
|  | //  h.Xattrs[key] = value | 
|  | //  h.PAXRecords["SCHILY.xattr."+key] = value | 
|  | // | 
|  | // When Writer.WriteHeader is called, the contents of Xattrs will take | 
|  | // precedence over those in PAXRecords. | 
|  | // | 
|  | // Deprecated: Use PAXRecords instead. | 
|  | Xattrs map[string]string | 
|  |  | 
|  | // PAXRecords is a map of PAX extended header records. | 
|  | // | 
|  | // User-defined records should have keys of the following form: | 
|  | //	VENDOR.keyword | 
|  | // Where VENDOR is some namespace in all uppercase, and keyword may | 
|  | // not contain the '=' character (e.g., "GOLANG.pkg.version"). | 
|  | // The key and value should be non-empty UTF-8 strings. | 
|  | // | 
|  | // When Writer.WriteHeader is called, PAX records derived from the | 
|  | // other fields in Header take precedence over PAXRecords. | 
|  | PAXRecords map[string]string | 
|  |  | 
|  | // Format specifies the format of the tar header. | 
|  | // | 
|  | // This is set by Reader.Next as a best-effort guess at the format. | 
|  | // Since the Reader liberally reads some non-compliant files, | 
|  | // it is possible for this to be FormatUnknown. | 
|  | // | 
|  | // If the format is unspecified when Writer.WriteHeader is called, | 
|  | // then it uses the first format (in the order of USTAR, PAX, GNU) | 
|  | // capable of encoding this Header (see Format). | 
|  | Format Format | 
|  | } | 
|  |  | 
|  | // sparseEntry represents a Length-sized fragment at Offset in the file. | 
|  | type sparseEntry struct{ Offset, Length int64 } | 
|  |  | 
|  | func (s sparseEntry) endOffset() int64 { return s.Offset + s.Length } | 
|  |  | 
|  | // A sparse file can be represented as either a sparseDatas or a sparseHoles. | 
|  | // As long as the total size is known, they are equivalent and one can be | 
|  | // converted to the other form and back. The various tar formats with sparse | 
|  | // file support represent sparse files in the sparseDatas form. That is, they | 
|  | // specify the fragments in the file that has data, and treat everything else as | 
|  | // having zero bytes. As such, the encoding and decoding logic in this package | 
|  | // deals with sparseDatas. | 
|  | // | 
|  | // However, the external API uses sparseHoles instead of sparseDatas because the | 
|  | // zero value of sparseHoles logically represents a normal file (i.e., there are | 
|  | // no holes in it). On the other hand, the zero value of sparseDatas implies | 
|  | // that the file has no data in it, which is rather odd. | 
|  | // | 
|  | // As an example, if the underlying raw file contains the 10-byte data: | 
|  | //	var compactFile = "abcdefgh" | 
|  | // | 
|  | // And the sparse map has the following entries: | 
|  | //	var spd sparseDatas = []sparseEntry{ | 
|  | //		{Offset: 2,  Length: 5},  // Data fragment for 2..6 | 
|  | //		{Offset: 18, Length: 3},  // Data fragment for 18..20 | 
|  | //	} | 
|  | //	var sph sparseHoles = []sparseEntry{ | 
|  | //		{Offset: 0,  Length: 2},  // Hole fragment for 0..1 | 
|  | //		{Offset: 7,  Length: 11}, // Hole fragment for 7..17 | 
|  | //		{Offset: 21, Length: 4},  // Hole fragment for 21..24 | 
|  | //	} | 
|  | // | 
|  | // Then the content of the resulting sparse file with a Header.Size of 25 is: | 
|  | //	var sparseFile = "\x00"*2 + "abcde" + "\x00"*11 + "fgh" + "\x00"*4 | 
|  | type ( | 
|  | sparseDatas []sparseEntry | 
|  | sparseHoles []sparseEntry | 
|  | ) | 
|  |  | 
|  | // validateSparseEntries reports whether sp is a valid sparse map. | 
|  | // It does not matter whether sp represents data fragments or hole fragments. | 
|  | func validateSparseEntries(sp []sparseEntry, size int64) bool { | 
|  | // Validate all sparse entries. These are the same checks as performed by | 
|  | // the BSD tar utility. | 
|  | if size < 0 { | 
|  | return false | 
|  | } | 
|  | var pre sparseEntry | 
|  | for _, cur := range sp { | 
|  | switch { | 
|  | case cur.Offset < 0 || cur.Length < 0: | 
|  | return false // Negative values are never okay | 
|  | case cur.Offset > math.MaxInt64-cur.Length: | 
|  | return false // Integer overflow with large length | 
|  | case cur.endOffset() > size: | 
|  | return false // Region extends beyond the actual size | 
|  | case pre.endOffset() > cur.Offset: | 
|  | return false // Regions cannot overlap and must be in order | 
|  | } | 
|  | pre = cur | 
|  | } | 
|  | return true | 
|  | } | 
|  |  | 
|  | // alignSparseEntries mutates src and returns dst where each fragment's | 
|  | // starting offset is aligned up to the nearest block edge, and each | 
|  | // ending offset is aligned down to the nearest block edge. | 
|  | // | 
|  | // Even though the Go tar Reader and the BSD tar utility can handle entries | 
|  | // with arbitrary offsets and lengths, the GNU tar utility can only handle | 
|  | // offsets and lengths that are multiples of blockSize. | 
|  | func alignSparseEntries(src []sparseEntry, size int64) []sparseEntry { | 
|  | dst := src[:0] | 
|  | for _, s := range src { | 
|  | pos, end := s.Offset, s.endOffset() | 
|  | pos += blockPadding(+pos) // Round-up to nearest blockSize | 
|  | if end != size { | 
|  | end -= blockPadding(-end) // Round-down to nearest blockSize | 
|  | } | 
|  | if pos < end { | 
|  | dst = append(dst, sparseEntry{Offset: pos, Length: end - pos}) | 
|  | } | 
|  | } | 
|  | return dst | 
|  | } | 
|  |  | 
|  | // invertSparseEntries converts a sparse map from one form to the other. | 
|  | // If the input is sparseHoles, then it will output sparseDatas and vice-versa. | 
|  | // The input must have been already validated. | 
|  | // | 
|  | // This function mutates src and returns a normalized map where: | 
|  | //	* adjacent fragments are coalesced together | 
|  | //	* only the last fragment may be empty | 
|  | //	* the endOffset of the last fragment is the total size | 
|  | func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry { | 
|  | dst := src[:0] | 
|  | var pre sparseEntry | 
|  | for _, cur := range src { | 
|  | if cur.Length == 0 { | 
|  | continue // Skip empty fragments | 
|  | } | 
|  | pre.Length = cur.Offset - pre.Offset | 
|  | if pre.Length > 0 { | 
|  | dst = append(dst, pre) // Only add non-empty fragments | 
|  | } | 
|  | pre.Offset = cur.endOffset() | 
|  | } | 
|  | pre.Length = size - pre.Offset // Possibly the only empty fragment | 
|  | return append(dst, pre) | 
|  | } | 
|  |  | 
|  | // fileState tracks the number of logical (includes sparse holes) and physical | 
|  | // (actual in tar archive) bytes remaining for the current file. | 
|  | // | 
|  | // Invariant: LogicalRemaining >= PhysicalRemaining | 
|  | type fileState interface { | 
|  | LogicalRemaining() int64 | 
|  | PhysicalRemaining() int64 | 
|  | } | 
|  |  | 
|  | // allowedFormats determines which formats can be used. | 
|  | // The value returned is the logical OR of multiple possible formats. | 
|  | // If the value is FormatUnknown, then the input Header cannot be encoded | 
|  | // and an error is returned explaining why. | 
|  | // | 
|  | // As a by-product of checking the fields, this function returns paxHdrs, which | 
|  | // contain all fields that could not be directly encoded. | 
|  | // A value receiver ensures that this method does not mutate the source Header. | 
|  | func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err error) { | 
|  | format = FormatUSTAR | FormatPAX | FormatGNU | 
|  | paxHdrs = make(map[string]string) | 
|  |  | 
|  | var whyNoUSTAR, whyNoPAX, whyNoGNU string | 
|  | var preferPAX bool // Prefer PAX over USTAR | 
|  | verifyString := func(s string, size int, name, paxKey string) { | 
|  | // NUL-terminator is optional for path and linkpath. | 
|  | // Technically, it is required for uname and gname, | 
|  | // but neither GNU nor BSD tar checks for it. | 
|  | tooLong := len(s) > size | 
|  | allowLongGNU := paxKey == paxPath || paxKey == paxLinkpath | 
|  | if hasNUL(s) || (tooLong && !allowLongGNU) { | 
|  | whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%q", name, s) | 
|  | format.mustNotBe(FormatGNU) | 
|  | } | 
|  | if !isASCII(s) || tooLong { | 
|  | canSplitUSTAR := paxKey == paxPath | 
|  | if _, _, ok := splitUSTARPath(s); !canSplitUSTAR || !ok { | 
|  | whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%q", name, s) | 
|  | format.mustNotBe(FormatUSTAR) | 
|  | } | 
|  | if paxKey == paxNone { | 
|  | whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%q", name, s) | 
|  | format.mustNotBe(FormatPAX) | 
|  | } else { | 
|  | paxHdrs[paxKey] = s | 
|  | } | 
|  | } | 
|  | if v, ok := h.PAXRecords[paxKey]; ok && v == s { | 
|  | paxHdrs[paxKey] = v | 
|  | } | 
|  | } | 
|  | verifyNumeric := func(n int64, size int, name, paxKey string) { | 
|  | if !fitsInBase256(size, n) { | 
|  | whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%d", name, n) | 
|  | format.mustNotBe(FormatGNU) | 
|  | } | 
|  | if !fitsInOctal(size, n) { | 
|  | whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%d", name, n) | 
|  | format.mustNotBe(FormatUSTAR) | 
|  | if paxKey == paxNone { | 
|  | whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%d", name, n) | 
|  | format.mustNotBe(FormatPAX) | 
|  | } else { | 
|  | paxHdrs[paxKey] = strconv.FormatInt(n, 10) | 
|  | } | 
|  | } | 
|  | if v, ok := h.PAXRecords[paxKey]; ok && v == strconv.FormatInt(n, 10) { | 
|  | paxHdrs[paxKey] = v | 
|  | } | 
|  | } | 
|  | verifyTime := func(ts time.Time, size int, name, paxKey string) { | 
|  | if ts.IsZero() { | 
|  | return // Always okay | 
|  | } | 
|  | if !fitsInBase256(size, ts.Unix()) { | 
|  | whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%v", name, ts) | 
|  | format.mustNotBe(FormatGNU) | 
|  | } | 
|  | isMtime := paxKey == paxMtime | 
|  | fitsOctal := fitsInOctal(size, ts.Unix()) | 
|  | if (isMtime && !fitsOctal) || !isMtime { | 
|  | whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%v", name, ts) | 
|  | format.mustNotBe(FormatUSTAR) | 
|  | } | 
|  | needsNano := ts.Nanosecond() != 0 | 
|  | if !isMtime || !fitsOctal || needsNano { | 
|  | preferPAX = true // USTAR may truncate sub-second measurements | 
|  | if paxKey == paxNone { | 
|  | whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%v", name, ts) | 
|  | format.mustNotBe(FormatPAX) | 
|  | } else { | 
|  | paxHdrs[paxKey] = formatPAXTime(ts) | 
|  | } | 
|  | } | 
|  | if v, ok := h.PAXRecords[paxKey]; ok && v == formatPAXTime(ts) { | 
|  | paxHdrs[paxKey] = v | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check basic fields. | 
|  | var blk block | 
|  | v7 := blk.V7() | 
|  | ustar := blk.USTAR() | 
|  | gnu := blk.GNU() | 
|  | verifyString(h.Name, len(v7.Name()), "Name", paxPath) | 
|  | verifyString(h.Linkname, len(v7.LinkName()), "Linkname", paxLinkpath) | 
|  | verifyString(h.Uname, len(ustar.UserName()), "Uname", paxUname) | 
|  | verifyString(h.Gname, len(ustar.GroupName()), "Gname", paxGname) | 
|  | verifyNumeric(h.Mode, len(v7.Mode()), "Mode", paxNone) | 
|  | verifyNumeric(int64(h.Uid), len(v7.UID()), "Uid", paxUid) | 
|  | verifyNumeric(int64(h.Gid), len(v7.GID()), "Gid", paxGid) | 
|  | verifyNumeric(h.Size, len(v7.Size()), "Size", paxSize) | 
|  | verifyNumeric(h.Devmajor, len(ustar.DevMajor()), "Devmajor", paxNone) | 
|  | verifyNumeric(h.Devminor, len(ustar.DevMinor()), "Devminor", paxNone) | 
|  | verifyTime(h.ModTime, len(v7.ModTime()), "ModTime", paxMtime) | 
|  | verifyTime(h.AccessTime, len(gnu.AccessTime()), "AccessTime", paxAtime) | 
|  | verifyTime(h.ChangeTime, len(gnu.ChangeTime()), "ChangeTime", paxCtime) | 
|  |  | 
|  | // Check for header-only types. | 
|  | var whyOnlyPAX, whyOnlyGNU string | 
|  | switch h.Typeflag { | 
|  | case TypeReg, TypeChar, TypeBlock, TypeFifo, TypeGNUSparse: | 
|  | // Exclude TypeLink and TypeSymlink, since they may reference directories. | 
|  | if strings.HasSuffix(h.Name, "/") { | 
|  | return FormatUnknown, nil, headerError{"filename may not have trailing slash"} | 
|  | } | 
|  | case TypeXHeader, TypeGNULongName, TypeGNULongLink: | 
|  | return FormatUnknown, nil, headerError{"cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers"} | 
|  | case TypeXGlobalHeader: | 
|  | h2 := Header{Name: h.Name, Typeflag: h.Typeflag, Xattrs: h.Xattrs, PAXRecords: h.PAXRecords, Format: h.Format} | 
|  | if !reflect.DeepEqual(h, h2) { | 
|  | return FormatUnknown, nil, headerError{"only PAXRecords should be set for TypeXGlobalHeader"} | 
|  | } | 
|  | whyOnlyPAX = "only PAX supports TypeXGlobalHeader" | 
|  | format.mayOnlyBe(FormatPAX) | 
|  | } | 
|  | if !isHeaderOnlyType(h.Typeflag) && h.Size < 0 { | 
|  | return FormatUnknown, nil, headerError{"negative size on header-only type"} | 
|  | } | 
|  |  | 
|  | // Check PAX records. | 
|  | if len(h.Xattrs) > 0 { | 
|  | for k, v := range h.Xattrs { | 
|  | paxHdrs[paxSchilyXattr+k] = v | 
|  | } | 
|  | whyOnlyPAX = "only PAX supports Xattrs" | 
|  | format.mayOnlyBe(FormatPAX) | 
|  | } | 
|  | if len(h.PAXRecords) > 0 { | 
|  | for k, v := range h.PAXRecords { | 
|  | switch _, exists := paxHdrs[k]; { | 
|  | case exists: | 
|  | continue // Do not overwrite existing records | 
|  | case h.Typeflag == TypeXGlobalHeader: | 
|  | paxHdrs[k] = v // Copy all records | 
|  | case !basicKeys[k] && !strings.HasPrefix(k, paxGNUSparse): | 
|  | paxHdrs[k] = v // Ignore local records that may conflict | 
|  | } | 
|  | } | 
|  | whyOnlyPAX = "only PAX supports PAXRecords" | 
|  | format.mayOnlyBe(FormatPAX) | 
|  | } | 
|  | for k, v := range paxHdrs { | 
|  | if !validPAXRecord(k, v) { | 
|  | return FormatUnknown, nil, headerError{fmt.Sprintf("invalid PAX record: %q", k+" = "+v)} | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO(dsnet): Re-enable this when adding sparse support. | 
|  | // See https://golang.org/issue/22735 | 
|  | /* | 
|  | // Check sparse files. | 
|  | if len(h.SparseHoles) > 0 || h.Typeflag == TypeGNUSparse { | 
|  | if isHeaderOnlyType(h.Typeflag) { | 
|  | return FormatUnknown, nil, headerError{"header-only type cannot be sparse"} | 
|  | } | 
|  | if !validateSparseEntries(h.SparseHoles, h.Size) { | 
|  | return FormatUnknown, nil, headerError{"invalid sparse holes"} | 
|  | } | 
|  | if h.Typeflag == TypeGNUSparse { | 
|  | whyOnlyGNU = "only GNU supports TypeGNUSparse" | 
|  | format.mayOnlyBe(FormatGNU) | 
|  | } else { | 
|  | whyNoGNU = "GNU supports sparse files only with TypeGNUSparse" | 
|  | format.mustNotBe(FormatGNU) | 
|  | } | 
|  | whyNoUSTAR = "USTAR does not support sparse files" | 
|  | format.mustNotBe(FormatUSTAR) | 
|  | } | 
|  | */ | 
|  |  | 
|  | // Check desired format. | 
|  | if wantFormat := h.Format; wantFormat != FormatUnknown { | 
|  | if wantFormat.has(FormatPAX) && !preferPAX { | 
|  | wantFormat.mayBe(FormatUSTAR) // PAX implies USTAR allowed too | 
|  | } | 
|  | format.mayOnlyBe(wantFormat) // Set union of formats allowed and format wanted | 
|  | } | 
|  | if format == FormatUnknown { | 
|  | switch h.Format { | 
|  | case FormatUSTAR: | 
|  | err = headerError{"Format specifies USTAR", whyNoUSTAR, whyOnlyPAX, whyOnlyGNU} | 
|  | case FormatPAX: | 
|  | err = headerError{"Format specifies PAX", whyNoPAX, whyOnlyGNU} | 
|  | case FormatGNU: | 
|  | err = headerError{"Format specifies GNU", whyNoGNU, whyOnlyPAX} | 
|  | default: | 
|  | err = headerError{whyNoUSTAR, whyNoPAX, whyNoGNU, whyOnlyPAX, whyOnlyGNU} | 
|  | } | 
|  | } | 
|  | return format, paxHdrs, err | 
|  | } | 
|  |  | 
|  | // FileInfo returns an os.FileInfo for the Header. | 
|  | func (h *Header) FileInfo() os.FileInfo { | 
|  | return headerFileInfo{h} | 
|  | } | 
|  |  | 
|  | // headerFileInfo implements os.FileInfo. | 
|  | type headerFileInfo struct { | 
|  | h *Header | 
|  | } | 
|  |  | 
|  | func (fi headerFileInfo) Size() int64        { return fi.h.Size } | 
|  | func (fi headerFileInfo) IsDir() bool        { return fi.Mode().IsDir() } | 
|  | func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime } | 
|  | func (fi headerFileInfo) Sys() interface{}   { return fi.h } | 
|  |  | 
|  | // Name returns the base name of the file. | 
|  | func (fi headerFileInfo) Name() string { | 
|  | if fi.IsDir() { | 
|  | return path.Base(path.Clean(fi.h.Name)) | 
|  | } | 
|  | return path.Base(fi.h.Name) | 
|  | } | 
|  |  | 
|  | // Mode returns the permission and mode bits for the headerFileInfo. | 
|  | func (fi headerFileInfo) Mode() (mode os.FileMode) { | 
|  | // Set file permission bits. | 
|  | mode = os.FileMode(fi.h.Mode).Perm() | 
|  |  | 
|  | // Set setuid, setgid and sticky bits. | 
|  | if fi.h.Mode&c_ISUID != 0 { | 
|  | mode |= os.ModeSetuid | 
|  | } | 
|  | if fi.h.Mode&c_ISGID != 0 { | 
|  | mode |= os.ModeSetgid | 
|  | } | 
|  | if fi.h.Mode&c_ISVTX != 0 { | 
|  | mode |= os.ModeSticky | 
|  | } | 
|  |  | 
|  | // Set file mode bits; clear perm, setuid, setgid, and sticky bits. | 
|  | switch m := os.FileMode(fi.h.Mode) &^ 07777; m { | 
|  | case c_ISDIR: | 
|  | mode |= os.ModeDir | 
|  | case c_ISFIFO: | 
|  | mode |= os.ModeNamedPipe | 
|  | case c_ISLNK: | 
|  | mode |= os.ModeSymlink | 
|  | case c_ISBLK: | 
|  | mode |= os.ModeDevice | 
|  | case c_ISCHR: | 
|  | mode |= os.ModeDevice | 
|  | mode |= os.ModeCharDevice | 
|  | case c_ISSOCK: | 
|  | mode |= os.ModeSocket | 
|  | } | 
|  |  | 
|  | switch fi.h.Typeflag { | 
|  | case TypeSymlink: | 
|  | mode |= os.ModeSymlink | 
|  | case TypeChar: | 
|  | mode |= os.ModeDevice | 
|  | mode |= os.ModeCharDevice | 
|  | case TypeBlock: | 
|  | mode |= os.ModeDevice | 
|  | case TypeDir: | 
|  | mode |= os.ModeDir | 
|  | case TypeFifo: | 
|  | mode |= os.ModeNamedPipe | 
|  | } | 
|  |  | 
|  | return mode | 
|  | } | 
|  |  | 
|  | // sysStat, if non-nil, populates h from system-dependent fields of fi. | 
|  | var sysStat func(fi os.FileInfo, h *Header) error | 
|  |  | 
|  | const ( | 
|  | // Mode constants from the USTAR spec: | 
|  | // See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06 | 
|  | c_ISUID = 04000 // Set uid | 
|  | c_ISGID = 02000 // Set gid | 
|  | c_ISVTX = 01000 // Save text (sticky bit) | 
|  |  | 
|  | // Common Unix mode constants; these are not defined in any common tar standard. | 
|  | // Header.FileInfo understands these, but FileInfoHeader will never produce these. | 
|  | c_ISDIR  = 040000  // Directory | 
|  | c_ISFIFO = 010000  // FIFO | 
|  | c_ISREG  = 0100000 // Regular file | 
|  | c_ISLNK  = 0120000 // Symbolic link | 
|  | c_ISBLK  = 060000  // Block special file | 
|  | c_ISCHR  = 020000  // Character special file | 
|  | c_ISSOCK = 0140000 // Socket | 
|  | ) | 
|  |  | 
|  | // FileInfoHeader creates a partially-populated Header from fi. | 
|  | // If fi describes a symlink, FileInfoHeader records link as the link target. | 
|  | // If fi describes a directory, a slash is appended to the name. | 
|  | // | 
|  | // Since os.FileInfo's Name method only returns the base name of | 
|  | // the file it describes, it may be necessary to modify Header.Name | 
|  | // to provide the full path name of the file. | 
|  | func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) { | 
|  | if fi == nil { | 
|  | return nil, errors.New("archive/tar: FileInfo is nil") | 
|  | } | 
|  | fm := fi.Mode() | 
|  | h := &Header{ | 
|  | Name:    fi.Name(), | 
|  | ModTime: fi.ModTime(), | 
|  | Mode:    int64(fm.Perm()), // or'd with c_IS* constants later | 
|  | } | 
|  | switch { | 
|  | case fm.IsRegular(): | 
|  | h.Typeflag = TypeReg | 
|  | h.Size = fi.Size() | 
|  | case fi.IsDir(): | 
|  | h.Typeflag = TypeDir | 
|  | h.Name += "/" | 
|  | case fm&os.ModeSymlink != 0: | 
|  | h.Typeflag = TypeSymlink | 
|  | h.Linkname = link | 
|  | case fm&os.ModeDevice != 0: | 
|  | if fm&os.ModeCharDevice != 0 { | 
|  | h.Typeflag = TypeChar | 
|  | } else { | 
|  | h.Typeflag = TypeBlock | 
|  | } | 
|  | case fm&os.ModeNamedPipe != 0: | 
|  | h.Typeflag = TypeFifo | 
|  | case fm&os.ModeSocket != 0: | 
|  | return nil, fmt.Errorf("archive/tar: sockets not supported") | 
|  | default: | 
|  | return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm) | 
|  | } | 
|  | if fm&os.ModeSetuid != 0 { | 
|  | h.Mode |= c_ISUID | 
|  | } | 
|  | if fm&os.ModeSetgid != 0 { | 
|  | h.Mode |= c_ISGID | 
|  | } | 
|  | if fm&os.ModeSticky != 0 { | 
|  | h.Mode |= c_ISVTX | 
|  | } | 
|  | // If possible, populate additional fields from OS-specific | 
|  | // FileInfo fields. | 
|  | if sys, ok := fi.Sys().(*Header); ok { | 
|  | // This FileInfo came from a Header (not the OS). Use the | 
|  | // original Header to populate all remaining fields. | 
|  | h.Uid = sys.Uid | 
|  | h.Gid = sys.Gid | 
|  | h.Uname = sys.Uname | 
|  | h.Gname = sys.Gname | 
|  | h.AccessTime = sys.AccessTime | 
|  | h.ChangeTime = sys.ChangeTime | 
|  | if sys.Xattrs != nil { | 
|  | h.Xattrs = make(map[string]string) | 
|  | for k, v := range sys.Xattrs { | 
|  | h.Xattrs[k] = v | 
|  | } | 
|  | } | 
|  | if sys.Typeflag == TypeLink { | 
|  | // hard link | 
|  | h.Typeflag = TypeLink | 
|  | h.Size = 0 | 
|  | h.Linkname = sys.Linkname | 
|  | } | 
|  | if sys.PAXRecords != nil { | 
|  | h.PAXRecords = make(map[string]string) | 
|  | for k, v := range sys.PAXRecords { | 
|  | h.PAXRecords[k] = v | 
|  | } | 
|  | } | 
|  | } | 
|  | if sysStat != nil { | 
|  | return h, sysStat(fi, h) | 
|  | } | 
|  | return h, nil | 
|  | } | 
|  |  | 
|  | // isHeaderOnlyType checks if the given type flag is of the type that has no | 
|  | // data section even if a size is specified. | 
|  | func isHeaderOnlyType(flag byte) bool { | 
|  | switch flag { | 
|  | case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo: | 
|  | return true | 
|  | default: | 
|  | return false | 
|  | } | 
|  | } | 
|  |  | 
|  | func min(a, b int64) int64 { | 
|  | if a < b { | 
|  | return a | 
|  | } | 
|  | return b | 
|  | } |