...

Source file src/archive/tar/common.go

Documentation: archive/tar

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

View as plain text