...

Source file src/pkg/archive/tar/common.go

     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		"math"
    17		"os"
    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	//	var compactFile = "abcdefgh"
   225	//
   226	// And the sparse map has the following entries:
   227	//	var spd sparseDatas = []sparseEntry{
   228	//		{Offset: 2,  Length: 5},  // Data fragment for 2..6
   229	//		{Offset: 18, Length: 3},  // Data fragment for 18..20
   230	//	}
   231	//	var sph sparseHoles = []sparseEntry{
   232	//		{Offset: 0,  Length: 2},  // Hole fragment for 0..1
   233	//		{Offset: 7,  Length: 11}, // Hole fragment for 7..17
   234	//		{Offset: 21, Length: 4},  // Hole fragment for 21..24
   235	//	}
   236	//
   237	// Then the content of the resulting sparse file with a Header.Size of 25 is:
   238	//	var sparseFile = "\x00"*2 + "abcde" + "\x00"*11 + "fgh" + "\x00"*4
   239	type (
   240		sparseDatas []sparseEntry
   241		sparseHoles []sparseEntry
   242	)
   243	
   244	// validateSparseEntries reports whether sp is a valid sparse map.
   245	// It does not matter whether sp represents data fragments or hole fragments.
   246	func validateSparseEntries(sp []sparseEntry, size int64) bool {
   247		// Validate all sparse entries. These are the same checks as performed by
   248		// the BSD tar utility.
   249		if size < 0 {
   250			return false
   251		}
   252		var pre sparseEntry
   253		for _, cur := range sp {
   254			switch {
   255			case cur.Offset < 0 || cur.Length < 0:
   256				return false // Negative values are never okay
   257			case cur.Offset > math.MaxInt64-cur.Length:
   258				return false // Integer overflow with large length
   259			case cur.endOffset() > size:
   260				return false // Region extends beyond the actual size
   261			case pre.endOffset() > cur.Offset:
   262				return false // Regions cannot overlap and must be in order
   263			}
   264			pre = cur
   265		}
   266		return true
   267	}
   268	
   269	// alignSparseEntries mutates src and returns dst where each fragment's
   270	// starting offset is aligned up to the nearest block edge, and each
   271	// ending offset is aligned down to the nearest block edge.
   272	//
   273	// Even though the Go tar Reader and the BSD tar utility can handle entries
   274	// with arbitrary offsets and lengths, the GNU tar utility can only handle
   275	// offsets and lengths that are multiples of blockSize.
   276	func alignSparseEntries(src []sparseEntry, size int64) []sparseEntry {
   277		dst := src[:0]
   278		for _, s := range src {
   279			pos, end := s.Offset, s.endOffset()
   280			pos += blockPadding(+pos) // Round-up to nearest blockSize
   281			if end != size {
   282				end -= blockPadding(-end) // Round-down to nearest blockSize
   283			}
   284			if pos < end {
   285				dst = append(dst, sparseEntry{Offset: pos, Length: end - pos})
   286			}
   287		}
   288		return dst
   289	}
   290	
   291	// invertSparseEntries converts a sparse map from one form to the other.
   292	// If the input is sparseHoles, then it will output sparseDatas and vice-versa.
   293	// The input must have been already validated.
   294	//
   295	// This function mutates src and returns a normalized map where:
   296	//	* adjacent fragments are coalesced together
   297	//	* only the last fragment may be empty
   298	//	* the endOffset of the last fragment is the total size
   299	func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry {
   300		dst := src[:0]
   301		var pre sparseEntry
   302		for _, cur := range src {
   303			if cur.Length == 0 {
   304				continue // Skip empty fragments
   305			}
   306			pre.Length = cur.Offset - pre.Offset
   307			if pre.Length > 0 {
   308				dst = append(dst, pre) // Only add non-empty fragments
   309			}
   310			pre.Offset = cur.endOffset()
   311		}
   312		pre.Length = size - pre.Offset // Possibly the only empty fragment
   313		return append(dst, pre)
   314	}
   315	
   316	// fileState tracks the number of logical (includes sparse holes) and physical
   317	// (actual in tar archive) bytes remaining for the current file.
   318	//
   319	// Invariant: LogicalRemaining >= PhysicalRemaining
   320	type fileState interface {
   321		LogicalRemaining() int64
   322		PhysicalRemaining() int64
   323	}
   324	
   325	// allowedFormats determines which formats can be used.
   326	// The value returned is the logical OR of multiple possible formats.
   327	// If the value is FormatUnknown, then the input Header cannot be encoded
   328	// and an error is returned explaining why.
   329	//
   330	// As a by-product of checking the fields, this function returns paxHdrs, which
   331	// contain all fields that could not be directly encoded.
   332	// A value receiver ensures that this method does not mutate the source Header.
   333	func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err error) {
   334		format = FormatUSTAR | FormatPAX | FormatGNU
   335		paxHdrs = make(map[string]string)
   336	
   337		var whyNoUSTAR, whyNoPAX, whyNoGNU string
   338		var preferPAX bool // Prefer PAX over USTAR
   339		verifyString := func(s string, size int, name, paxKey string) {
   340			// NUL-terminator is optional for path and linkpath.
   341			// Technically, it is required for uname and gname,
   342			// but neither GNU nor BSD tar checks for it.
   343			tooLong := len(s) > size
   344			allowLongGNU := paxKey == paxPath || paxKey == paxLinkpath
   345			if hasNUL(s) || (tooLong && !allowLongGNU) {
   346				whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%q", name, s)
   347				format.mustNotBe(FormatGNU)
   348			}
   349			if !isASCII(s) || tooLong {
   350				canSplitUSTAR := paxKey == paxPath
   351				if _, _, ok := splitUSTARPath(s); !canSplitUSTAR || !ok {
   352					whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%q", name, s)
   353					format.mustNotBe(FormatUSTAR)
   354				}
   355				if paxKey == paxNone {
   356					whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%q", name, s)
   357					format.mustNotBe(FormatPAX)
   358				} else {
   359					paxHdrs[paxKey] = s
   360				}
   361			}
   362			if v, ok := h.PAXRecords[paxKey]; ok && v == s {
   363				paxHdrs[paxKey] = v
   364			}
   365		}
   366		verifyNumeric := func(n int64, size int, name, paxKey string) {
   367			if !fitsInBase256(size, n) {
   368				whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%d", name, n)
   369				format.mustNotBe(FormatGNU)
   370			}
   371			if !fitsInOctal(size, n) {
   372				whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%d", name, n)
   373				format.mustNotBe(FormatUSTAR)
   374				if paxKey == paxNone {
   375					whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%d", name, n)
   376					format.mustNotBe(FormatPAX)
   377				} else {
   378					paxHdrs[paxKey] = strconv.FormatInt(n, 10)
   379				}
   380			}
   381			if v, ok := h.PAXRecords[paxKey]; ok && v == strconv.FormatInt(n, 10) {
   382				paxHdrs[paxKey] = v
   383			}
   384		}
   385		verifyTime := func(ts time.Time, size int, name, paxKey string) {
   386			if ts.IsZero() {
   387				return // Always okay
   388			}
   389			if !fitsInBase256(size, ts.Unix()) {
   390				whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%v", name, ts)
   391				format.mustNotBe(FormatGNU)
   392			}
   393			isMtime := paxKey == paxMtime
   394			fitsOctal := fitsInOctal(size, ts.Unix())
   395			if (isMtime && !fitsOctal) || !isMtime {
   396				whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%v", name, ts)
   397				format.mustNotBe(FormatUSTAR)
   398			}
   399			needsNano := ts.Nanosecond() != 0
   400			if !isMtime || !fitsOctal || needsNano {
   401				preferPAX = true // USTAR may truncate sub-second measurements
   402				if paxKey == paxNone {
   403					whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%v", name, ts)
   404					format.mustNotBe(FormatPAX)
   405				} else {
   406					paxHdrs[paxKey] = formatPAXTime(ts)
   407				}
   408			}
   409			if v, ok := h.PAXRecords[paxKey]; ok && v == formatPAXTime(ts) {
   410				paxHdrs[paxKey] = v
   411			}
   412		}
   413	
   414		// Check basic fields.
   415		var blk block
   416		v7 := blk.V7()
   417		ustar := blk.USTAR()
   418		gnu := blk.GNU()
   419		verifyString(h.Name, len(v7.Name()), "Name", paxPath)
   420		verifyString(h.Linkname, len(v7.LinkName()), "Linkname", paxLinkpath)
   421		verifyString(h.Uname, len(ustar.UserName()), "Uname", paxUname)
   422		verifyString(h.Gname, len(ustar.GroupName()), "Gname", paxGname)
   423		verifyNumeric(h.Mode, len(v7.Mode()), "Mode", paxNone)
   424		verifyNumeric(int64(h.Uid), len(v7.UID()), "Uid", paxUid)
   425		verifyNumeric(int64(h.Gid), len(v7.GID()), "Gid", paxGid)
   426		verifyNumeric(h.Size, len(v7.Size()), "Size", paxSize)
   427		verifyNumeric(h.Devmajor, len(ustar.DevMajor()), "Devmajor", paxNone)
   428		verifyNumeric(h.Devminor, len(ustar.DevMinor()), "Devminor", paxNone)
   429		verifyTime(h.ModTime, len(v7.ModTime()), "ModTime", paxMtime)
   430		verifyTime(h.AccessTime, len(gnu.AccessTime()), "AccessTime", paxAtime)
   431		verifyTime(h.ChangeTime, len(gnu.ChangeTime()), "ChangeTime", paxCtime)
   432	
   433		// Check for header-only types.
   434		var whyOnlyPAX, whyOnlyGNU string
   435		switch h.Typeflag {
   436		case TypeReg, TypeChar, TypeBlock, TypeFifo, TypeGNUSparse:
   437			// Exclude TypeLink and TypeSymlink, since they may reference directories.
   438			if strings.HasSuffix(h.Name, "/") {
   439				return FormatUnknown, nil, headerError{"filename may not have trailing slash"}
   440			}
   441		case TypeXHeader, TypeGNULongName, TypeGNULongLink:
   442			return FormatUnknown, nil, headerError{"cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers"}
   443		case TypeXGlobalHeader:
   444			h2 := Header{Name: h.Name, Typeflag: h.Typeflag, Xattrs: h.Xattrs, PAXRecords: h.PAXRecords, Format: h.Format}
   445			if !reflect.DeepEqual(h, h2) {
   446				return FormatUnknown, nil, headerError{"only PAXRecords should be set for TypeXGlobalHeader"}
   447			}
   448			whyOnlyPAX = "only PAX supports TypeXGlobalHeader"
   449			format.mayOnlyBe(FormatPAX)
   450		}
   451		if !isHeaderOnlyType(h.Typeflag) && h.Size < 0 {
   452			return FormatUnknown, nil, headerError{"negative size on header-only type"}
   453		}
   454	
   455		// Check PAX records.
   456		if len(h.Xattrs) > 0 {
   457			for k, v := range h.Xattrs {
   458				paxHdrs[paxSchilyXattr+k] = v
   459			}
   460			whyOnlyPAX = "only PAX supports Xattrs"
   461			format.mayOnlyBe(FormatPAX)
   462		}
   463		if len(h.PAXRecords) > 0 {
   464			for k, v := range h.PAXRecords {
   465				switch _, exists := paxHdrs[k]; {
   466				case exists:
   467					continue // Do not overwrite existing records
   468				case h.Typeflag == TypeXGlobalHeader:
   469					paxHdrs[k] = v // Copy all records
   470				case !basicKeys[k] && !strings.HasPrefix(k, paxGNUSparse):
   471					paxHdrs[k] = v // Ignore local records that may conflict
   472				}
   473			}
   474			whyOnlyPAX = "only PAX supports PAXRecords"
   475			format.mayOnlyBe(FormatPAX)
   476		}
   477		for k, v := range paxHdrs {
   478			if !validPAXRecord(k, v) {
   479				return FormatUnknown, nil, headerError{fmt.Sprintf("invalid PAX record: %q", k+" = "+v)}
   480			}
   481		}
   482	
   483		// TODO(dsnet): Re-enable this when adding sparse support.
   484		// See https://golang.org/issue/22735
   485		/*
   486			// Check sparse files.
   487			if len(h.SparseHoles) > 0 || h.Typeflag == TypeGNUSparse {
   488				if isHeaderOnlyType(h.Typeflag) {
   489					return FormatUnknown, nil, headerError{"header-only type cannot be sparse"}
   490				}
   491				if !validateSparseEntries(h.SparseHoles, h.Size) {
   492					return FormatUnknown, nil, headerError{"invalid sparse holes"}
   493				}
   494				if h.Typeflag == TypeGNUSparse {
   495					whyOnlyGNU = "only GNU supports TypeGNUSparse"
   496					format.mayOnlyBe(FormatGNU)
   497				} else {
   498					whyNoGNU = "GNU supports sparse files only with TypeGNUSparse"
   499					format.mustNotBe(FormatGNU)
   500				}
   501				whyNoUSTAR = "USTAR does not support sparse files"
   502				format.mustNotBe(FormatUSTAR)
   503			}
   504		*/
   505	
   506		// Check desired format.
   507		if wantFormat := h.Format; wantFormat != FormatUnknown {
   508			if wantFormat.has(FormatPAX) && !preferPAX {
   509				wantFormat.mayBe(FormatUSTAR) // PAX implies USTAR allowed too
   510			}
   511			format.mayOnlyBe(wantFormat) // Set union of formats allowed and format wanted
   512		}
   513		if format == FormatUnknown {
   514			switch h.Format {
   515			case FormatUSTAR:
   516				err = headerError{"Format specifies USTAR", whyNoUSTAR, whyOnlyPAX, whyOnlyGNU}
   517			case FormatPAX:
   518				err = headerError{"Format specifies PAX", whyNoPAX, whyOnlyGNU}
   519			case FormatGNU:
   520				err = headerError{"Format specifies GNU", whyNoGNU, whyOnlyPAX}
   521			default:
   522				err = headerError{whyNoUSTAR, whyNoPAX, whyNoGNU, whyOnlyPAX, whyOnlyGNU}
   523			}
   524		}
   525		return format, paxHdrs, err
   526	}
   527	
   528	// FileInfo returns an os.FileInfo for the Header.
   529	func (h *Header) FileInfo() os.FileInfo {
   530		return headerFileInfo{h}
   531	}
   532	
   533	// headerFileInfo implements os.FileInfo.
   534	type headerFileInfo struct {
   535		h *Header
   536	}
   537	
   538	func (fi headerFileInfo) Size() int64        { return fi.h.Size }
   539	func (fi headerFileInfo) IsDir() bool        { return fi.Mode().IsDir() }
   540	func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
   541	func (fi headerFileInfo) Sys() interface{}   { return fi.h }
   542	
   543	// Name returns the base name of the file.
   544	func (fi headerFileInfo) Name() string {
   545		if fi.IsDir() {
   546			return path.Base(path.Clean(fi.h.Name))
   547		}
   548		return path.Base(fi.h.Name)
   549	}
   550	
   551	// Mode returns the permission and mode bits for the headerFileInfo.
   552	func (fi headerFileInfo) Mode() (mode os.FileMode) {
   553		// Set file permission bits.
   554		mode = os.FileMode(fi.h.Mode).Perm()
   555	
   556		// Set setuid, setgid and sticky bits.
   557		if fi.h.Mode&c_ISUID != 0 {
   558			mode |= os.ModeSetuid
   559		}
   560		if fi.h.Mode&c_ISGID != 0 {
   561			mode |= os.ModeSetgid
   562		}
   563		if fi.h.Mode&c_ISVTX != 0 {
   564			mode |= os.ModeSticky
   565		}
   566	
   567		// Set file mode bits; clear perm, setuid, setgid, and sticky bits.
   568		switch m := os.FileMode(fi.h.Mode) &^ 07777; m {
   569		case c_ISDIR:
   570			mode |= os.ModeDir
   571		case c_ISFIFO:
   572			mode |= os.ModeNamedPipe
   573		case c_ISLNK:
   574			mode |= os.ModeSymlink
   575		case c_ISBLK:
   576			mode |= os.ModeDevice
   577		case c_ISCHR:
   578			mode |= os.ModeDevice
   579			mode |= os.ModeCharDevice
   580		case c_ISSOCK:
   581			mode |= os.ModeSocket
   582		}
   583	
   584		switch fi.h.Typeflag {
   585		case TypeSymlink:
   586			mode |= os.ModeSymlink
   587		case TypeChar:
   588			mode |= os.ModeDevice
   589			mode |= os.ModeCharDevice
   590		case TypeBlock:
   591			mode |= os.ModeDevice
   592		case TypeDir:
   593			mode |= os.ModeDir
   594		case TypeFifo:
   595			mode |= os.ModeNamedPipe
   596		}
   597	
   598		return mode
   599	}
   600	
   601	// sysStat, if non-nil, populates h from system-dependent fields of fi.
   602	var sysStat func(fi os.FileInfo, h *Header) error
   603	
   604	const (
   605		// Mode constants from the USTAR spec:
   606		// See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06
   607		c_ISUID = 04000 // Set uid
   608		c_ISGID = 02000 // Set gid
   609		c_ISVTX = 01000 // Save text (sticky bit)
   610	
   611		// Common Unix mode constants; these are not defined in any common tar standard.
   612		// Header.FileInfo understands these, but FileInfoHeader will never produce these.
   613		c_ISDIR  = 040000  // Directory
   614		c_ISFIFO = 010000  // FIFO
   615		c_ISREG  = 0100000 // Regular file
   616		c_ISLNK  = 0120000 // Symbolic link
   617		c_ISBLK  = 060000  // Block special file
   618		c_ISCHR  = 020000  // Character special file
   619		c_ISSOCK = 0140000 // Socket
   620	)
   621	
   622	// FileInfoHeader creates a partially-populated Header from fi.
   623	// If fi describes a symlink, FileInfoHeader records link as the link target.
   624	// If fi describes a directory, a slash is appended to the name.
   625	//
   626	// Since os.FileInfo's Name method only returns the base name of
   627	// the file it describes, it may be necessary to modify Header.Name
   628	// to provide the full path name of the file.
   629	func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
   630		if fi == nil {
   631			return nil, errors.New("archive/tar: FileInfo is nil")
   632		}
   633		fm := fi.Mode()
   634		h := &Header{
   635			Name:    fi.Name(),
   636			ModTime: fi.ModTime(),
   637			Mode:    int64(fm.Perm()), // or'd with c_IS* constants later
   638		}
   639		switch {
   640		case fm.IsRegular():
   641			h.Typeflag = TypeReg
   642			h.Size = fi.Size()
   643		case fi.IsDir():
   644			h.Typeflag = TypeDir
   645			h.Name += "/"
   646		case fm&os.ModeSymlink != 0:
   647			h.Typeflag = TypeSymlink
   648			h.Linkname = link
   649		case fm&os.ModeDevice != 0:
   650			if fm&os.ModeCharDevice != 0 {
   651				h.Typeflag = TypeChar
   652			} else {
   653				h.Typeflag = TypeBlock
   654			}
   655		case fm&os.ModeNamedPipe != 0:
   656			h.Typeflag = TypeFifo
   657		case fm&os.ModeSocket != 0:
   658			return nil, fmt.Errorf("archive/tar: sockets not supported")
   659		default:
   660			return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
   661		}
   662		if fm&os.ModeSetuid != 0 {
   663			h.Mode |= c_ISUID
   664		}
   665		if fm&os.ModeSetgid != 0 {
   666			h.Mode |= c_ISGID
   667		}
   668		if fm&os.ModeSticky != 0 {
   669			h.Mode |= c_ISVTX
   670		}
   671		// If possible, populate additional fields from OS-specific
   672		// FileInfo fields.
   673		if sys, ok := fi.Sys().(*Header); ok {
   674			// This FileInfo came from a Header (not the OS). Use the
   675			// original Header to populate all remaining fields.
   676			h.Uid = sys.Uid
   677			h.Gid = sys.Gid
   678			h.Uname = sys.Uname
   679			h.Gname = sys.Gname
   680			h.AccessTime = sys.AccessTime
   681			h.ChangeTime = sys.ChangeTime
   682			if sys.Xattrs != nil {
   683				h.Xattrs = make(map[string]string)
   684				for k, v := range sys.Xattrs {
   685					h.Xattrs[k] = v
   686				}
   687			}
   688			if sys.Typeflag == TypeLink {
   689				// hard link
   690				h.Typeflag = TypeLink
   691				h.Size = 0
   692				h.Linkname = sys.Linkname
   693			}
   694			if sys.PAXRecords != nil {
   695				h.PAXRecords = make(map[string]string)
   696				for k, v := range sys.PAXRecords {
   697					h.PAXRecords[k] = v
   698				}
   699			}
   700		}
   701		if sysStat != nil {
   702			return h, sysStat(fi, h)
   703		}
   704		return h, nil
   705	}
   706	
   707	// isHeaderOnlyType checks if the given type flag is of the type that has no
   708	// data section even if a size is specified.
   709	func isHeaderOnlyType(flag byte) bool {
   710		switch flag {
   711		case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo:
   712			return true
   713		default:
   714			return false
   715		}
   716	}
   717	
   718	func min(a, b int64) int64 {
   719		if a < b {
   720			return a
   721		}
   722		return b
   723	}
   724	

View as plain text