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 "internal/godebug"
17 "io/fs"
18 "maps"
19 "math"
20 "path"
21 22 "strconv"
23 "bytes"
24 "time"
25 )
26 27 // BUG: Use of the Uid and Gid fields in Header could overflow on 32-bit
28 // architectures. If a large value is encountered when decoding, the result
29 // stored in Header will be the truncated version.
30 31 var tarinsecurepath = godebug.New("tarinsecurepath")
32 33 var (
34 ErrHeader = errors.New("archive/tar: invalid tar header")
35 ErrWriteTooLong = errors.New("archive/tar: write too long")
36 ErrFieldTooLong = errors.New("archive/tar: header field too long")
37 ErrWriteAfterClose = errors.New("archive/tar: write after close")
38 ErrInsecurePath = errors.New("archive/tar: insecure file path")
39 errMissData = errors.New("archive/tar: sparse file references non-existent data")
40 errUnrefData = errors.New("archive/tar: sparse file contains unreferenced data")
41 errWriteHole = errors.New("archive/tar: write non-NUL byte in sparse hole")
42 errSparseTooLong = errors.New("archive/tar: sparse map too long")
43 )
44 45 type headerError [][]byte
46 47 func (he headerError) Error() string {
48 const prefix = "archive/tar: cannot encode header"
49 var ss [][]byte
50 for _, s := range he {
51 if s != "" {
52 ss = append(ss, s)
53 }
54 }
55 if len(ss) == 0 {
56 return prefix
57 }
58 return fmt.Sprintf("%s: %v", prefix, bytes.Join(ss, "; and "))
59 }
60 61 // Type flags for Header.Typeflag.
62 const (
63 // Type '0' indicates a regular file.
64 TypeReg = '0'
65 66 // Deprecated: Use TypeReg instead.
67 TypeRegA = '\x00'
68 69 // Type '1' to '6' are header-only flags and may not have a data body.
70 TypeLink = '1' // Hard link
71 TypeSymlink = '2' // Symbolic link
72 TypeChar = '3' // Character device node
73 TypeBlock = '4' // Block device node
74 TypeDir = '5' // Directory
75 TypeFifo = '6' // FIFO node
76 77 // Type '7' is reserved.
78 TypeCont = '7'
79 80 // Type 'x' is used by the PAX format to store key-value records that
81 // are only relevant to the next file.
82 // This package transparently handles these types.
83 TypeXHeader = 'x'
84 85 // Type 'g' is used by the PAX format to store key-value records that
86 // are relevant to all subsequent files.
87 // This package only supports parsing and composing such headers,
88 // but does not currently support persisting the global state across files.
89 TypeXGlobalHeader = 'g'
90 91 // Type 'S' indicates a sparse file in the GNU format.
92 TypeGNUSparse = 'S'
93 94 // Types 'L' and 'K' are used by the GNU format for a meta file
95 // used to store the path or link name for the next file.
96 // This package transparently handles these types.
97 TypeGNULongName = 'L'
98 TypeGNULongLink = 'K'
99 )
100 101 // Keywords for PAX extended header records.
102 const (
103 paxNone = "" // Indicates that no PAX key is suitable
104 paxPath = "path"
105 paxLinkpath = "linkpath"
106 paxSize = "size"
107 paxUid = "uid"
108 paxGid = "gid"
109 paxUname = "uname"
110 paxGname = "gname"
111 paxMtime = "mtime"
112 paxAtime = "atime"
113 paxCtime = "ctime" // Removed from later revision of PAX spec, but was valid
114 paxCharset = "charset" // Currently unused
115 paxComment = "comment" // Currently unused
116 117 paxSchilyXattr = "SCHILY.xattr."
118 119 // Keywords for GNU sparse files in a PAX extended header.
120 paxGNUSparse = "GNU.sparse."
121 paxGNUSparseNumBlocks = "GNU.sparse.numblocks"
122 paxGNUSparseOffset = "GNU.sparse.offset"
123 paxGNUSparseNumBytes = "GNU.sparse.numbytes"
124 paxGNUSparseMap = "GNU.sparse.map"
125 paxGNUSparseName = "GNU.sparse.name"
126 paxGNUSparseMajor = "GNU.sparse.major"
127 paxGNUSparseMinor = "GNU.sparse.minor"
128 paxGNUSparseSize = "GNU.sparse.size"
129 paxGNUSparseRealSize = "GNU.sparse.realsize"
130 )
131 132 // basicKeys is a set of the PAX keys for which we have built-in support.
133 // This does not contain "charset" or "comment", which are both PAX-specific,
134 // so adding them as first-class features of Header is unlikely.
135 // Users can use the PAXRecords field to set it themselves.
136 var basicKeys = map[string]bool{
137 paxPath: true, paxLinkpath: true, paxSize: true, paxUid: true, paxGid: true,
138 paxUname: true, paxGname: true, paxMtime: true, paxAtime: true, paxCtime: true,
139 }
140 141 // A Header represents a single header in a tar archive.
142 // Some fields may not be populated.
143 //
144 // For forward compatibility, users that retrieve a Header from Reader.Next,
145 // mutate it in some ways, and then pass it back to Writer.WriteHeader
146 // should do so by creating a new Header and copying the fields
147 // that they are interested in preserving.
148 type Header struct {
149 // Typeflag is the type of header entry.
150 // The zero value is automatically promoted to either TypeReg or TypeDir
151 // depending on the presence of a trailing slash in Name.
152 Typeflag byte
153 154 Name []byte // Name of file entry
155 Linkname []byte // Target name of link (valid for TypeLink or TypeSymlink)
156 157 Size int64 // Logical file size in bytes
158 Mode int64 // Permission and mode bits
159 Uid int // User ID of owner
160 Gid int // Group ID of owner
161 Uname []byte // User name of owner
162 Gname []byte // Group name of owner
163 164 // If the Format is unspecified, then Writer.WriteHeader rounds ModTime
165 // to the nearest second and ignores the AccessTime and ChangeTime fields.
166 //
167 // To use AccessTime or ChangeTime, specify the Format as PAX or GNU.
168 // To use sub-second resolution, specify the Format as PAX.
169 ModTime time.Time // Modification time
170 AccessTime time.Time // Access time (requires either PAX or GNU support)
171 ChangeTime time.Time // Change time (requires either PAX or GNU support)
172 173 Devmajor int64 // Major device number (valid for TypeChar or TypeBlock)
174 Devminor int64 // Minor device number (valid for TypeChar or TypeBlock)
175 176 // Xattrs stores extended attributes as PAX records under the
177 // "SCHILY.xattr." namespace.
178 //
179 // The following are semantically equivalent:
180 // h.Xattrs[key] = value
181 // h.PAXRecords["SCHILY.xattr."+key] = value
182 //
183 // When Writer.WriteHeader is called, the contents of Xattrs will take
184 // precedence over those in PAXRecords.
185 //
186 // Deprecated: Use PAXRecords instead.
187 Xattrs map[string][]byte
188 189 // PAXRecords is a map of PAX extended header records.
190 //
191 // User-defined records should have keys of the following form:
192 // VENDOR.keyword
193 // Where VENDOR is some namespace in all uppercase, and keyword may
194 // not contain the '=' character (e.g., "GOLANG.pkg.version").
195 // The key and value should be non-empty UTF-8 bytes.
196 //
197 // When Writer.WriteHeader is called, PAX records derived from the
198 // other fields in Header take precedence over PAXRecords.
199 PAXRecords map[string][]byte
200 201 // Format specifies the format of the tar header.
202 //
203 // This is set by Reader.Next as a best-effort guess at the format.
204 // Since the Reader liberally reads some non-compliant files,
205 // it is possible for this to be FormatUnknown.
206 //
207 // If the format is unspecified when Writer.WriteHeader is called,
208 // then it uses the first format (in the order of USTAR, PAX, GNU)
209 // capable of encoding this Header (see Format).
210 Format Format
211 }
212 213 // sparseEntry represents a Length-sized fragment at Offset in the file.
214 type sparseEntry struct{ Offset, Length int64 }
215 216 func (s sparseEntry) endOffset() int64 { return s.Offset + s.Length }
217 218 // A sparse file can be represented as either a sparseDatas or a sparseHoles.
219 // As long as the total size is known, they are equivalent and one can be
220 // converted to the other form and back. The various tar formats with sparse
221 // file support represent sparse files in the sparseDatas form. That is, they
222 // specify the fragments in the file that has data, and treat everything else as
223 // having zero bytes. As such, the encoding and decoding logic in this package
224 // deals with sparseDatas.
225 //
226 // However, the external API uses sparseHoles instead of sparseDatas because the
227 // zero value of sparseHoles logically represents a normal file (i.e., there are
228 // no holes in it). On the other hand, the zero value of sparseDatas implies
229 // that the file has no data in it, which is rather odd.
230 //
231 // As an example, if the underlying raw file contains the 10-byte data:
232 //
233 // var compactFile = "abcdefgh"
234 //
235 // And the sparse map has the following entries:
236 //
237 // var spd sparseDatas = []sparseEntry{
238 // {Offset: 2, Length: 5}, // Data fragment for 2..6
239 // {Offset: 18, Length: 3}, // Data fragment for 18..20
240 // }
241 // var sph sparseHoles = []sparseEntry{
242 // {Offset: 0, Length: 2}, // Hole fragment for 0..1
243 // {Offset: 7, Length: 11}, // Hole fragment for 7..17
244 // {Offset: 21, Length: 4}, // Hole fragment for 21..24
245 // }
246 //
247 // Then the content of the resulting sparse file with a Header.Size of 25 is:
248 //
249 // var sparseFile = "\x00"*2 + "abcde" + "\x00"*11 + "fgh" + "\x00"*4
250 type (
251 sparseDatas []sparseEntry
252 sparseHoles []sparseEntry
253 )
254 255 // validateSparseEntries reports whether sp is a valid sparse map.
256 // It does not matter whether sp represents data fragments or hole fragments.
257 func validateSparseEntries(sp []sparseEntry, size int64) bool {
258 // Validate all sparse entries. These are the same checks as performed by
259 // the BSD tar utility.
260 if size < 0 {
261 return false
262 }
263 var pre sparseEntry
264 for _, cur := range sp {
265 switch {
266 case cur.Offset < 0 || cur.Length < 0:
267 return false // Negative values are never okay
268 case cur.Offset > math.MaxInt64-cur.Length:
269 return false // Integer overflow with large length
270 case cur.endOffset() > size:
271 return false // Region extends beyond the actual size
272 case pre.endOffset() > cur.Offset:
273 return false // Regions cannot overlap and must be in order
274 }
275 pre = cur
276 }
277 return true
278 }
279 280 // alignSparseEntries mutates src and returns dst where each fragment's
281 // starting offset is aligned up to the nearest block edge, and each
282 // ending offset is aligned down to the nearest block edge.
283 //
284 // Even though the Go tar Reader and the BSD tar utility can handle entries
285 // with arbitrary offsets and lengths, the GNU tar utility can only handle
286 // offsets and lengths that are multiples of blockSize.
287 func alignSparseEntries(src []sparseEntry, size int64) []sparseEntry {
288 dst := src[:0]
289 for _, s := range src {
290 pos, end := s.Offset, s.endOffset()
291 pos += blockPadding(+pos) // Round-up to nearest blockSize
292 if end != size {
293 end -= blockPadding(-end) // Round-down to nearest blockSize
294 }
295 if pos < end {
296 dst = append(dst, sparseEntry{Offset: pos, Length: end - pos})
297 }
298 }
299 return dst
300 }
301 302 // invertSparseEntries converts a sparse map from one form to the other.
303 // If the input is sparseHoles, then it will output sparseDatas and vice-versa.
304 // The input must have been already validated.
305 //
306 // This function mutates src and returns a normalized map where:
307 // - adjacent fragments are coalesced together
308 // - only the last fragment may be empty
309 // - the endOffset of the last fragment is the total size
310 func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry {
311 dst := src[:0]
312 var pre sparseEntry
313 for _, cur := range src {
314 if cur.Length == 0 {
315 continue // Skip empty fragments
316 }
317 pre.Length = cur.Offset - pre.Offset
318 if pre.Length > 0 {
319 dst = append(dst, pre) // Only add non-empty fragments
320 }
321 pre.Offset = cur.endOffset()
322 }
323 pre.Length = size - pre.Offset // Possibly the only empty fragment
324 return append(dst, pre)
325 }
326 327 // fileState tracks the number of logical (includes sparse holes) and physical
328 // (actual in tar archive) bytes remaining for the current file.
329 //
330 // Invariant: logicalRemaining >= physicalRemaining
331 type fileState interface {
332 logicalRemaining() int64
333 physicalRemaining() int64
334 }
335 336 // allowedFormats determines which formats can be used.
337 // The value returned is the logical OR of multiple possible formats.
338 // If the value is FormatUnknown, then the input Header cannot be encoded
339 // and an error is returned explaining why.
340 //
341 // As a by-product of checking the fields, this function returns paxHdrs, which
342 // contain all fields that could not be directly encoded.
343 // A value receiver ensures that this method does not mutate the source Header.
344 func (h Header) allowedFormats() (format Format, paxHdrs map[string][]byte, err error) {
345 format = FormatUSTAR | FormatPAX | FormatGNU
346 paxHdrs = map[string][]byte{}
347 348 var whyNoUSTAR, whyNoPAX, whyNoGNU []byte
349 var preferPAX bool // Prefer PAX over USTAR
350 verifyString := func(s []byte, size int, name, paxKey []byte) {
351 // NUL-terminator is optional for path and linkpath.
352 // Technically, it is required for uname and gname,
353 // but neither GNU nor BSD tar checks for it.
354 tooLong := len(s) > size
355 allowLongGNU := paxKey == paxPath || paxKey == paxLinkpath
356 if hasNUL(s) || (tooLong && !allowLongGNU) {
357 whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%q", name, s)
358 format.mustNotBe(FormatGNU)
359 }
360 if !isASCII(s) || tooLong {
361 canSplitUSTAR := paxKey == paxPath
362 if _, _, ok := splitUSTARPath(s); !canSplitUSTAR || !ok {
363 whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%q", name, s)
364 format.mustNotBe(FormatUSTAR)
365 }
366 if paxKey == paxNone {
367 whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%q", name, s)
368 format.mustNotBe(FormatPAX)
369 } else {
370 paxHdrs[paxKey] = s
371 }
372 }
373 if v, ok := h.PAXRecords[paxKey]; ok && v == s {
374 paxHdrs[paxKey] = v
375 }
376 }
377 verifyNumeric := func(n int64, size int, name, paxKey []byte) {
378 if !fitsInBase256(size, n) {
379 whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%d", name, n)
380 format.mustNotBe(FormatGNU)
381 }
382 if !fitsInOctal(size, n) {
383 whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%d", name, n)
384 format.mustNotBe(FormatUSTAR)
385 if paxKey == paxNone {
386 whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%d", name, n)
387 format.mustNotBe(FormatPAX)
388 } else {
389 paxHdrs[paxKey] = strconv.FormatInt(n, 10)
390 }
391 }
392 if v, ok := h.PAXRecords[paxKey]; ok && v == strconv.FormatInt(n, 10) {
393 paxHdrs[paxKey] = v
394 }
395 }
396 verifyTime := func(ts time.Time, size int, name, paxKey []byte) {
397 if ts.IsZero() {
398 return // Always okay
399 }
400 if !fitsInBase256(size, ts.Unix()) {
401 whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%v", name, ts)
402 format.mustNotBe(FormatGNU)
403 }
404 isMtime := paxKey == paxMtime
405 fitsOctal := fitsInOctal(size, ts.Unix())
406 if (isMtime && !fitsOctal) || !isMtime {
407 whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%v", name, ts)
408 format.mustNotBe(FormatUSTAR)
409 }
410 needsNano := ts.Nanosecond() != 0
411 if !isMtime || !fitsOctal || needsNano {
412 preferPAX = true // USTAR may truncate sub-second measurements
413 if paxKey == paxNone {
414 whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%v", name, ts)
415 format.mustNotBe(FormatPAX)
416 } else {
417 paxHdrs[paxKey] = formatPAXTime(ts)
418 }
419 }
420 if v, ok := h.PAXRecords[paxKey]; ok && v == formatPAXTime(ts) {
421 paxHdrs[paxKey] = v
422 }
423 }
424 425 // Check basic fields.
426 var blk block
427 v7 := blk.toV7()
428 ustar := blk.toUSTAR()
429 gnu := blk.toGNU()
430 verifyString(h.Name, len(v7.name()), "Name", paxPath)
431 verifyString(h.Linkname, len(v7.linkName()), "Linkname", paxLinkpath)
432 verifyString(h.Uname, len(ustar.userName()), "Uname", paxUname)
433 verifyString(h.Gname, len(ustar.groupName()), "Gname", paxGname)
434 verifyNumeric(h.Mode, len(v7.mode()), "Mode", paxNone)
435 verifyNumeric(int64(h.Uid), len(v7.uid()), "Uid", paxUid)
436 verifyNumeric(int64(h.Gid), len(v7.gid()), "Gid", paxGid)
437 verifyNumeric(h.Size, len(v7.size()), "Size", paxSize)
438 verifyNumeric(h.Devmajor, len(ustar.devMajor()), "Devmajor", paxNone)
439 verifyNumeric(h.Devminor, len(ustar.devMinor()), "Devminor", paxNone)
440 verifyTime(h.ModTime, len(v7.modTime()), "ModTime", paxMtime)
441 verifyTime(h.AccessTime, len(gnu.accessTime()), "AccessTime", paxAtime)
442 verifyTime(h.ChangeTime, len(gnu.changeTime()), "ChangeTime", paxCtime)
443 444 // Check for header-only types.
445 var whyOnlyPAX, whyOnlyGNU []byte
446 switch h.Typeflag {
447 case TypeReg, TypeChar, TypeBlock, TypeFifo, TypeGNUSparse:
448 // Exclude TypeLink and TypeSymlink, since they may reference directories.
449 if bytes.HasSuffix(h.Name, "/") {
450 return FormatUnknown, nil, headerError{"filename may not have trailing slash"}
451 }
452 case TypeXHeader, TypeGNULongName, TypeGNULongLink:
453 return FormatUnknown, nil, headerError{"cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers"}
454 case TypeXGlobalHeader:
455 if len(h.Linkname) != 0 || h.Size != 0 || h.Mode != 0 || h.Uid != 0 || h.Gid != 0 ||
456 len(h.Uname) != 0 || len(h.Gname) != 0 || !h.ModTime.IsZero() || !h.AccessTime.IsZero() ||
457 !h.ChangeTime.IsZero() || h.Devmajor != 0 || h.Devminor != 0 {
458 return FormatUnknown, nil, headerError{"only PAXRecords should be set for TypeXGlobalHeader"}
459 }
460 whyOnlyPAX = "only PAX supports TypeXGlobalHeader"
461 format.mayOnlyBe(FormatPAX)
462 }
463 if !isHeaderOnlyType(h.Typeflag) && h.Size < 0 {
464 return FormatUnknown, nil, headerError{"negative size on header-only type"}
465 }
466 467 // Check PAX records.
468 if len(h.Xattrs) > 0 {
469 for k, v := range h.Xattrs {
470 paxHdrs[paxSchilyXattr+k] = v
471 }
472 whyOnlyPAX = "only PAX supports Xattrs"
473 format.mayOnlyBe(FormatPAX)
474 }
475 if len(h.PAXRecords) > 0 {
476 for k, v := range h.PAXRecords {
477 switch _, exists := paxHdrs[k]; {
478 case exists:
479 continue // Do not overwrite existing records
480 case h.Typeflag == TypeXGlobalHeader:
481 paxHdrs[k] = v // Copy all records
482 case !basicKeys[k] && !bytes.HasPrefix(k, paxGNUSparse):
483 paxHdrs[k] = v // Ignore local records that may conflict
484 }
485 }
486 whyOnlyPAX = "only PAX supports PAXRecords"
487 format.mayOnlyBe(FormatPAX)
488 }
489 for k, v := range paxHdrs {
490 if !validPAXRecord(k, v) {
491 return FormatUnknown, nil, headerError{fmt.Sprintf("invalid PAX record: %q", k+" = "+v)}
492 }
493 }
494 495 // TODO(dsnet): Re-enable this when adding sparse support.
496 // See https://golang.org/issue/22735
497 /*
498 // Check sparse files.
499 if len(h.SparseHoles) > 0 || h.Typeflag == TypeGNUSparse {
500 if isHeaderOnlyType(h.Typeflag) {
501 return FormatUnknown, nil, headerError{"header-only type cannot be sparse"}
502 }
503 if !validateSparseEntries(h.SparseHoles, h.Size) {
504 return FormatUnknown, nil, headerError{"invalid sparse holes"}
505 }
506 if h.Typeflag == TypeGNUSparse {
507 whyOnlyGNU = "only GNU supports TypeGNUSparse"
508 format.mayOnlyBe(FormatGNU)
509 } else {
510 whyNoGNU = "GNU supports sparse files only with TypeGNUSparse"
511 format.mustNotBe(FormatGNU)
512 }
513 whyNoUSTAR = "USTAR does not support sparse files"
514 format.mustNotBe(FormatUSTAR)
515 }
516 */
517 518 // Check desired format.
519 if wantFormat := h.Format; wantFormat != FormatUnknown {
520 if wantFormat.has(FormatPAX) && !preferPAX {
521 wantFormat.mayBe(FormatUSTAR) // PAX implies USTAR allowed too
522 }
523 format.mayOnlyBe(wantFormat) // Set union of formats allowed and format wanted
524 }
525 if format == FormatUnknown {
526 switch h.Format {
527 case FormatUSTAR:
528 err = headerError{"Format specifies USTAR", whyNoUSTAR, whyOnlyPAX, whyOnlyGNU}
529 case FormatPAX:
530 err = headerError{"Format specifies PAX", whyNoPAX, whyOnlyGNU}
531 case FormatGNU:
532 err = headerError{"Format specifies GNU", whyNoGNU, whyOnlyPAX}
533 default:
534 err = headerError{whyNoUSTAR, whyNoPAX, whyNoGNU, whyOnlyPAX, whyOnlyGNU}
535 }
536 }
537 return format, paxHdrs, err
538 }
539 540 // FileInfo returns an fs.FileInfo for the Header.
541 func (h *Header) FileInfo() fs.FileInfo {
542 return headerFileInfo{h}
543 }
544 545 // headerFileInfo implements fs.FileInfo.
546 type headerFileInfo struct {
547 h *Header
548 }
549 550 func (fi headerFileInfo) Size() int64 { return fi.h.Size }
551 func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
552 func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
553 func (fi headerFileInfo) Sys() any { return fi.h }
554 555 // Name returns the base name of the file.
556 func (fi headerFileInfo) Name() []byte {
557 if fi.IsDir() {
558 return path.Base(path.Clean(fi.h.Name))
559 }
560 return path.Base(fi.h.Name)
561 }
562 563 // Mode returns the permission and mode bits for the headerFileInfo.
564 func (fi headerFileInfo) Mode() (mode fs.FileMode) {
565 // Set file permission bits.
566 mode = fs.FileMode(fi.h.Mode).Perm()
567 568 // Set setuid, setgid and sticky bits.
569 if fi.h.Mode&c_ISUID != 0 {
570 mode |= fs.ModeSetuid
571 }
572 if fi.h.Mode&c_ISGID != 0 {
573 mode |= fs.ModeSetgid
574 }
575 if fi.h.Mode&c_ISVTX != 0 {
576 mode |= fs.ModeSticky
577 }
578 579 // Set file mode bits; clear perm, setuid, setgid, and sticky bits.
580 switch m := fs.FileMode(fi.h.Mode) &^ 07777; m {
581 case c_ISDIR:
582 mode |= fs.ModeDir
583 case c_ISFIFO:
584 mode |= fs.ModeNamedPipe
585 case c_ISLNK:
586 mode |= fs.ModeSymlink
587 case c_ISBLK:
588 mode |= fs.ModeDevice
589 case c_ISCHR:
590 mode |= fs.ModeDevice
591 mode |= fs.ModeCharDevice
592 case c_ISSOCK:
593 mode |= fs.ModeSocket
594 }
595 596 switch fi.h.Typeflag {
597 case TypeSymlink:
598 mode |= fs.ModeSymlink
599 case TypeChar:
600 mode |= fs.ModeDevice
601 mode |= fs.ModeCharDevice
602 case TypeBlock:
603 mode |= fs.ModeDevice
604 case TypeDir:
605 mode |= fs.ModeDir
606 case TypeFifo:
607 mode |= fs.ModeNamedPipe
608 }
609 610 return mode
611 }
612 613 func (fi headerFileInfo) String() string {
614 return fs.FormatFileInfo(fi)
615 }
616 617 // sysStat, if non-nil, populates h from system-dependent fields of fi.
618 var sysStat func(fi fs.FileInfo, h *Header, doNameLookups bool) error
619 620 const (
621 // Mode constants from the USTAR spec:
622 // See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06
623 c_ISUID = 04000 // Set uid
624 c_ISGID = 02000 // Set gid
625 c_ISVTX = 01000 // Save text (sticky bit)
626 627 // Common Unix mode constants; these are not defined in any common tar standard.
628 // Header.FileInfo understands these, but FileInfoHeader will never produce these.
629 c_ISDIR = 040000 // Directory
630 c_ISFIFO = 010000 // FIFO
631 c_ISREG = 0100000 // Regular file
632 c_ISLNK = 0120000 // Symbolic link
633 c_ISBLK = 060000 // Block special file
634 c_ISCHR = 020000 // Character special file
635 c_ISSOCK = 0140000 // Socket
636 )
637 638 // FileInfoHeader creates a partially-populated [Header] from fi.
639 // If fi describes a symlink, FileInfoHeader records link as the link target.
640 // If fi describes a directory, a slash is appended to the name.
641 //
642 // Since fs.FileInfo's Name method only returns the base name of
643 // the file it describes, it may be necessary to modify Header.Name
644 // to provide the full path name of the file.
645 //
646 // If fi implements [FileInfoNames]
647 // Header.Gname and Header.Uname
648 // are provided by the methods of the interface.
649 func FileInfoHeader(fi fs.FileInfo, link []byte) (*Header, error) {
650 if fi == nil {
651 return nil, errors.New("archive/tar: FileInfo is nil")
652 }
653 fm := fi.Mode()
654 h := &Header{
655 Name: fi.Name(),
656 ModTime: fi.ModTime(),
657 Mode: int64(fm.Perm()), // or'd with c_IS* constants later
658 }
659 switch {
660 case fm.IsRegular():
661 h.Typeflag = TypeReg
662 h.Size = fi.Size()
663 case fi.IsDir():
664 h.Typeflag = TypeDir
665 h.Name += "/"
666 case fm&fs.ModeSymlink != 0:
667 h.Typeflag = TypeSymlink
668 h.Linkname = link
669 case fm&fs.ModeDevice != 0:
670 if fm&fs.ModeCharDevice != 0 {
671 h.Typeflag = TypeChar
672 } else {
673 h.Typeflag = TypeBlock
674 }
675 case fm&fs.ModeNamedPipe != 0:
676 h.Typeflag = TypeFifo
677 case fm&fs.ModeSocket != 0:
678 return nil, fmt.Errorf("archive/tar: sockets not supported")
679 default:
680 return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
681 }
682 if fm&fs.ModeSetuid != 0 {
683 h.Mode |= c_ISUID
684 }
685 if fm&fs.ModeSetgid != 0 {
686 h.Mode |= c_ISGID
687 }
688 if fm&fs.ModeSticky != 0 {
689 h.Mode |= c_ISVTX
690 }
691 // If possible, populate additional fields from OS-specific
692 // FileInfo fields.
693 if sys, ok := fi.Sys().(*Header); ok {
694 // This FileInfo came from a Header (not the OS). Use the
695 // original Header to populate all remaining fields.
696 h.Uid = sys.Uid
697 h.Gid = sys.Gid
698 h.Uname = sys.Uname
699 h.Gname = sys.Gname
700 h.AccessTime = sys.AccessTime
701 h.ChangeTime = sys.ChangeTime
702 h.Xattrs = maps.Clone(sys.Xattrs)
703 if sys.Typeflag == TypeLink {
704 // hard link
705 h.Typeflag = TypeLink
706 h.Size = 0
707 h.Linkname = sys.Linkname
708 }
709 h.PAXRecords = maps.Clone(sys.PAXRecords)
710 }
711 var doNameLookups = true
712 if iface, ok := fi.(FileInfoNames); ok {
713 doNameLookups = false
714 var err error
715 h.Gname, err = iface.Gname()
716 if err != nil {
717 return nil, err
718 }
719 h.Uname, err = iface.Uname()
720 if err != nil {
721 return nil, err
722 }
723 }
724 if sysStat != nil {
725 return h, sysStat(fi, h, doNameLookups)
726 }
727 return h, nil
728 }
729 730 // FileInfoNames extends [fs.FileInfo].
731 // Passing an instance of this to [FileInfoHeader] permits the caller
732 // to avoid a system-dependent name lookup by specifying the Uname and Gname directly.
733 type FileInfoNames interface {
734 fs.FileInfo
735 // Uname should give a user name.
736 Uname() (string, error)
737 // Gname should give a group name.
738 Gname() (string, error)
739 }
740 741 // isHeaderOnlyType checks if the given type flag is of the type that has no
742 // data section even if a size is specified.
743 func isHeaderOnlyType(flag byte) bool {
744 switch flag {
745 case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo:
746 return true
747 default:
748 return false
749 }
750 }
751