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.
11package tar
12
13import (
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
29var (
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
39type headerError []string
40
41func (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.
56const (
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.
94const (
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.
128var 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.
140type 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.
206type sparseEntry struct{ Offset, Length int64 }
207
208func (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
239type (
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.
246func 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.
276func 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
299func 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
320type 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.
333func (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.toV7()
417	ustar := blk.toUSTAR()
418	gnu := blk.toGNU()
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 fs.FileInfo for the Header.
529func (h *Header) FileInfo() fs.FileInfo {
530	return headerFileInfo{h}
531}
532
533// headerFileInfo implements fs.FileInfo.
534type headerFileInfo struct {
535	h *Header
536}
537
538func (fi headerFileInfo) Size() int64        { return fi.h.Size }
539func (fi headerFileInfo) IsDir() bool        { return fi.Mode().IsDir() }
540func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
541func (fi headerFileInfo) Sys() any           { return fi.h }
542
543// Name returns the base name of the file.
544func (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.
552func (fi headerFileInfo) Mode() (mode fs.FileMode) {
553	// Set file permission bits.
554	mode = fs.FileMode(fi.h.Mode).Perm()
555
556	// Set setuid, setgid and sticky bits.
557	if fi.h.Mode&c_ISUID != 0 {
558		mode |= fs.ModeSetuid
559	}
560	if fi.h.Mode&c_ISGID != 0 {
561		mode |= fs.ModeSetgid
562	}
563	if fi.h.Mode&c_ISVTX != 0 {
564		mode |= fs.ModeSticky
565	}
566
567	// Set file mode bits; clear perm, setuid, setgid, and sticky bits.
568	switch m := fs.FileMode(fi.h.Mode) &^ 07777; m {
569	case c_ISDIR:
570		mode |= fs.ModeDir
571	case c_ISFIFO:
572		mode |= fs.ModeNamedPipe
573	case c_ISLNK:
574		mode |= fs.ModeSymlink
575	case c_ISBLK:
576		mode |= fs.ModeDevice
577	case c_ISCHR:
578		mode |= fs.ModeDevice
579		mode |= fs.ModeCharDevice
580	case c_ISSOCK:
581		mode |= fs.ModeSocket
582	}
583
584	switch fi.h.Typeflag {
585	case TypeSymlink:
586		mode |= fs.ModeSymlink
587	case TypeChar:
588		mode |= fs.ModeDevice
589		mode |= fs.ModeCharDevice
590	case TypeBlock:
591		mode |= fs.ModeDevice
592	case TypeDir:
593		mode |= fs.ModeDir
594	case TypeFifo:
595		mode |= fs.ModeNamedPipe
596	}
597
598	return mode
599}
600
601// sysStat, if non-nil, populates h from system-dependent fields of fi.
602var sysStat func(fi fs.FileInfo, h *Header) error
603
604const (
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 fs.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.
629func FileInfoHeader(fi fs.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&fs.ModeSymlink != 0:
647		h.Typeflag = TypeSymlink
648		h.Linkname = link
649	case fm&fs.ModeDevice != 0:
650		if fm&fs.ModeCharDevice != 0 {
651			h.Typeflag = TypeChar
652		} else {
653			h.Typeflag = TypeBlock
654		}
655	case fm&fs.ModeNamedPipe != 0:
656		h.Typeflag = TypeFifo
657	case fm&fs.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&fs.ModeSetuid != 0 {
663		h.Mode |= c_ISUID
664	}
665	if fm&fs.ModeSetgid != 0 {
666		h.Mode |= c_ISGID
667	}
668	if fm&fs.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.
709func 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
718func min(a, b int64) int64 {
719	if a < b {
720		return a
721	}
722	return b
723}
724