...
Run Format

Source file src/archive/tar/common.go

Documentation: archive/tar

  // 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
  }
  

View as plain text