Declared map variable without initialization; writing to a nil map causes a panic. Use make() to initialize
var basicKeys = map[string]bool{
1// Copyright 2009 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45// Package tar implements access to tar archives.6//7// Tape archives (tar) are a file format for storing a sequence of files that8// 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 tar1213import (14 "errors"15 "fmt"16 "internal/godebug"17 "io/fs"18 "maps"19 "math"20 "path"21 "reflect"22 "strconv"23 "strings"24 "time"25)2627// BUG: Use of the Uid and Gid fields in Header could overflow on 32-bit28// architectures. If a large value is encountered when decoding, the result29// stored in Header will be the truncated version.3031var tarinsecurepath = godebug.New("tarinsecurepath")3233var (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)4445type headerError []string4647func (he headerError) Error() string {48 const prefix = "archive/tar: cannot encode header"49 var ss []string50 for _, s := range he {51 if s != "" {52 ss = append(ss, s)53 }54 }55 if len(ss) == 0 {56 return prefix57 }58 return fmt.Sprintf("%s: %v", prefix, strings.Join(ss, "; and "))59}6061// Type flags for Header.Typeflag.62const (63 // Type '0' indicates a regular file.64 TypeReg = '0'6566 // Deprecated: Use TypeReg instead.67 TypeRegA = '\x00'6869 // Type '1' to '6' are header-only flags and may not have a data body.70 TypeLink = '1' // Hard link71 TypeSymlink = '2' // Symbolic link72 TypeChar = '3' // Character device node73 TypeBlock = '4' // Block device node74 TypeDir = '5' // Directory75 TypeFifo = '6' // FIFO node7677 // Type '7' is reserved.78 TypeCont = '7'7980 // Type 'x' is used by the PAX format to store key-value records that81 // are only relevant to the next file.82 // This package transparently handles these types.83 TypeXHeader = 'x'8485 // Type 'g' is used by the PAX format to store key-value records that86 // 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'9091 // Type 'S' indicates a sparse file in the GNU format.92 TypeGNUSparse = 'S'9394 // Types 'L' and 'K' are used by the GNU format for a meta file95 // 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)100101// Keywords for PAX extended header records.102const (103 paxNone = "" // Indicates that no PAX key is suitable104 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 valid114 paxCharset = "charset" // Currently unused115 paxComment = "comment" // Currently unused116117 paxSchilyXattr = "SCHILY.xattr."118119 // 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)131132// 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.136var 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}140141// 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.WriteHeader146// should do so by creating a new Header and copying the fields147// that they are interested in preserving.148type Header struct {149 // Typeflag is the type of header entry.150 // The zero value is automatically promoted to either TypeReg or TypeDir151 // depending on the presence of a trailing slash in Name.152 Typeflag byte153154 Name string // Name of file entry155 Linkname string // Target name of link (valid for TypeLink or TypeSymlink)156157 Size int64 // Logical file size in bytes158 Mode int64 // Permission and mode bits159 Uid int // User ID of owner160 Gid int // Group ID of owner161 Uname string // User name of owner162 Gname string // Group name of owner163164 // If the Format is unspecified, then Writer.WriteHeader rounds ModTime165 // 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 time170 AccessTime time.Time // Access time (requires either PAX or GNU support)171 ChangeTime time.Time // Change time (requires either PAX or GNU support)172173 Devmajor int64 // Major device number (valid for TypeChar or TypeBlock)174 Devminor int64 // Minor device number (valid for TypeChar or TypeBlock)175176 // Xattrs stores extended attributes as PAX records under the177 // "SCHILY.xattr." namespace.178 //179 // The following are semantically equivalent:180 // h.Xattrs[key] = value181 // h.PAXRecords["SCHILY.xattr."+key] = value182 //183 // When Writer.WriteHeader is called, the contents of Xattrs will take184 // precedence over those in PAXRecords.185 //186 // Deprecated: Use PAXRecords instead.187 Xattrs map[string]string188189 // PAXRecords is a map of PAX extended header records.190 //191 // User-defined records should have keys of the following form:192 // VENDOR.keyword193 // Where VENDOR is some namespace in all uppercase, and keyword may194 // not contain the '=' character (e.g., "GOLANG.pkg.version").195 // The key and value should be non-empty UTF-8 strings.196 //197 // When Writer.WriteHeader is called, PAX records derived from the198 // other fields in Header take precedence over PAXRecords.199 PAXRecords map[string]string200201 // 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 Format211}212213// sparseEntry represents a Length-sized fragment at Offset in the file.214type sparseEntry struct{ Offset, Length int64 }215216func (s sparseEntry) endOffset() int64 { return s.Offset + s.Length }217218// 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 be220// converted to the other form and back. The various tar formats with sparse221// file support represent sparse files in the sparseDatas form. That is, they222// specify the fragments in the file that has data, and treat everything else as223// having zero bytes. As such, the encoding and decoding logic in this package224// deals with sparseDatas.225//226// However, the external API uses sparseHoles instead of sparseDatas because the227// zero value of sparseHoles logically represents a normal file (i.e., there are228// no holes in it). On the other hand, the zero value of sparseDatas implies229// 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..6239// {Offset: 18, Length: 3}, // Data fragment for 18..20240// }241// var sph sparseHoles = []sparseEntry{242// {Offset: 0, Length: 2}, // Hole fragment for 0..1243// {Offset: 7, Length: 11}, // Hole fragment for 7..17244// {Offset: 21, Length: 4}, // Hole fragment for 21..24245// }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"*4250type (251 sparseDatas []sparseEntry252 sparseHoles []sparseEntry253)254255// validateSparseEntries reports whether sp is a valid sparse map.256// It does not matter whether sp represents data fragments or hole fragments.257func validateSparseEntries(sp []sparseEntry, size int64) bool {258 // Validate all sparse entries. These are the same checks as performed by259 // the BSD tar utility.260 if size < 0 {261 return false262 }263 var pre sparseEntry264 for _, cur := range sp {265 switch {266 case cur.Offset < 0 || cur.Length < 0:267 return false // Negative values are never okay268 case cur.Offset > math.MaxInt64-cur.Length:269 return false // Integer overflow with large length270 case cur.endOffset() > size:271 return false // Region extends beyond the actual size272 case pre.endOffset() > cur.Offset:273 return false // Regions cannot overlap and must be in order274 }275 pre = cur276 }277 return true278}279280// alignSparseEntries mutates src and returns dst where each fragment's281// starting offset is aligned up to the nearest block edge, and each282// 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 entries285// with arbitrary offsets and lengths, the GNU tar utility can only handle286// offsets and lengths that are multiples of blockSize.287func 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 blockSize292 if end != size {293 end -= blockPadding(-end) // Round-down to nearest blockSize294 }295 if pos < end {296 dst = append(dst, sparseEntry{Offset: pos, Length: end - pos})297 }298 }299 return dst300}301302// 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 together308// - only the last fragment may be empty309// - the endOffset of the last fragment is the total size310func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry {311 dst := src[:0]312 var pre sparseEntry313 for _, cur := range src {314 if cur.Length == 0 {315 continue // Skip empty fragments316 }317 pre.Length = cur.Offset - pre.Offset318 if pre.Length > 0 {319 dst = append(dst, pre) // Only add non-empty fragments320 }321 pre.Offset = cur.endOffset()322 }323 pre.Length = size - pre.Offset // Possibly the only empty fragment324 return append(dst, pre)325}326327// fileState tracks the number of logical (includes sparse holes) and physical328// (actual in tar archive) bytes remaining for the current file.329//330// Invariant: logicalRemaining >= physicalRemaining331type fileState interface {332 logicalRemaining() int64333 physicalRemaining() int64334}335336// 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 encoded339// and an error is returned explaining why.340//341// As a by-product of checking the fields, this function returns paxHdrs, which342// contain all fields that could not be directly encoded.343// A value receiver ensures that this method does not mutate the source Header.344func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err error) {345 format = FormatUSTAR | FormatPAX | FormatGNU346 paxHdrs = make(map[string]string)347348 var whyNoUSTAR, whyNoPAX, whyNoGNU string349 var preferPAX bool // Prefer PAX over USTAR350 verifyString := func(s string, size int, name, paxKey string) {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) > size355 allowLongGNU := paxKey == paxPath || paxKey == paxLinkpath356 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 == paxPath362 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] = s371 }372 }373 if v, ok := h.PAXRecords[paxKey]; ok && v == s {374 paxHdrs[paxKey] = v375 }376 }377 verifyNumeric := func(n int64, size int, name, paxKey string) {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] = v394 }395 }396 verifyTime := func(ts time.Time, size int, name, paxKey string) {397 if ts.IsZero() {398 return // Always okay399 }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 == paxMtime405 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() != 0411 if !isMtime || !fitsOctal || needsNano {412 preferPAX = true // USTAR may truncate sub-second measurements413 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] = v422 }423 }424425 // Check basic fields.426 var blk block427 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)443444 // Check for header-only types.445 var whyOnlyPAX, whyOnlyGNU string446 switch h.Typeflag {447 case TypeReg, TypeChar, TypeBlock, TypeFifo, TypeGNUSparse:448 // Exclude TypeLink and TypeSymlink, since they may reference directories.449 if strings.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 h2 := Header{Name: h.Name, Typeflag: h.Typeflag, Xattrs: h.Xattrs, PAXRecords: h.PAXRecords, Format: h.Format}456 if !reflect.DeepEqual(h, h2) {457 return FormatUnknown, nil, headerError{"only PAXRecords should be set for TypeXGlobalHeader"}458 }459 whyOnlyPAX = "only PAX supports TypeXGlobalHeader"460 format.mayOnlyBe(FormatPAX)461 }462 if !isHeaderOnlyType(h.Typeflag) && h.Size < 0 {463 return FormatUnknown, nil, headerError{"negative size on header-only type"}464 }465466 // Check PAX records.467 if len(h.Xattrs) > 0 {468 for k, v := range h.Xattrs {469 paxHdrs[paxSchilyXattr+k] = v470 }471 whyOnlyPAX = "only PAX supports Xattrs"472 format.mayOnlyBe(FormatPAX)473 }474 if len(h.PAXRecords) > 0 {475 for k, v := range h.PAXRecords {476 switch _, exists := paxHdrs[k]; {477 case exists:478 continue // Do not overwrite existing records479 case h.Typeflag == TypeXGlobalHeader:480 paxHdrs[k] = v // Copy all records481 case !basicKeys[k] && !strings.HasPrefix(k, paxGNUSparse):482 paxHdrs[k] = v // Ignore local records that may conflict483 }484 }485 whyOnlyPAX = "only PAX supports PAXRecords"486 format.mayOnlyBe(FormatPAX)487 }488 for k, v := range paxHdrs {489 if !validPAXRecord(k, v) {490 return FormatUnknown, nil, headerError{fmt.Sprintf("invalid PAX record: %q", k+" = "+v)}491 }492 }493494 // TODO(dsnet): Re-enable this when adding sparse support.495 // See https://golang.org/issue/22735496 /*497 // Check sparse files.498 if len(h.SparseHoles) > 0 || h.Typeflag == TypeGNUSparse {499 if isHeaderOnlyType(h.Typeflag) {500 return FormatUnknown, nil, headerError{"header-only type cannot be sparse"}501 }502 if !validateSparseEntries(h.SparseHoles, h.Size) {503 return FormatUnknown, nil, headerError{"invalid sparse holes"}504 }505 if h.Typeflag == TypeGNUSparse {506 whyOnlyGNU = "only GNU supports TypeGNUSparse"507 format.mayOnlyBe(FormatGNU)508 } else {509 whyNoGNU = "GNU supports sparse files only with TypeGNUSparse"510 format.mustNotBe(FormatGNU)511 }512 whyNoUSTAR = "USTAR does not support sparse files"513 format.mustNotBe(FormatUSTAR)514 }515 */516517 // Check desired format.518 if wantFormat := h.Format; wantFormat != FormatUnknown {519 if wantFormat.has(FormatPAX) && !preferPAX {520 wantFormat.mayBe(FormatUSTAR) // PAX implies USTAR allowed too521 }522 format.mayOnlyBe(wantFormat) // Set union of formats allowed and format wanted523 }524 if format == FormatUnknown {525 switch h.Format {526 case FormatUSTAR:527 err = headerError{"Format specifies USTAR", whyNoUSTAR, whyOnlyPAX, whyOnlyGNU}528 case FormatPAX:529 err = headerError{"Format specifies PAX", whyNoPAX, whyOnlyGNU}530 case FormatGNU:531 err = headerError{"Format specifies GNU", whyNoGNU, whyOnlyPAX}532 default:533 err = headerError{whyNoUSTAR, whyNoPAX, whyNoGNU, whyOnlyPAX, whyOnlyGNU}534 }535 }536 return format, paxHdrs, err537}538539// FileInfo returns an fs.FileInfo for the Header.540func (h *Header) FileInfo() fs.FileInfo {541 return headerFileInfo{h}542}543544// headerFileInfo implements fs.FileInfo.545type headerFileInfo struct {546 h *Header547}548549func (fi headerFileInfo) Size() int64 { return fi.h.Size }550func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }551func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }552func (fi headerFileInfo) Sys() any { return fi.h }553554// Name returns the base name of the file.555func (fi headerFileInfo) Name() string {556 if fi.IsDir() {557 return path.Base(path.Clean(fi.h.Name))558 }559 return path.Base(fi.h.Name)560}561562// Mode returns the permission and mode bits for the headerFileInfo.563func (fi headerFileInfo) Mode() (mode fs.FileMode) {564 // Set file permission bits.565 mode = fs.FileMode(fi.h.Mode).Perm()566567 // Set setuid, setgid and sticky bits.568 if fi.h.Mode&c_ISUID != 0 {569 mode |= fs.ModeSetuid570 }571 if fi.h.Mode&c_ISGID != 0 {572 mode |= fs.ModeSetgid573 }574 if fi.h.Mode&c_ISVTX != 0 {575 mode |= fs.ModeSticky576 }577578 // Set file mode bits; clear perm, setuid, setgid, and sticky bits.579 switch m := fs.FileMode(fi.h.Mode) &^ 07777; m {580 case c_ISDIR:581 mode |= fs.ModeDir582 case c_ISFIFO:583 mode |= fs.ModeNamedPipe584 case c_ISLNK:585 mode |= fs.ModeSymlink586 case c_ISBLK:587 mode |= fs.ModeDevice588 case c_ISCHR:589 mode |= fs.ModeDevice590 mode |= fs.ModeCharDevice591 case c_ISSOCK:592 mode |= fs.ModeSocket593 }594595 switch fi.h.Typeflag {596 case TypeSymlink:597 mode |= fs.ModeSymlink598 case TypeChar:599 mode |= fs.ModeDevice600 mode |= fs.ModeCharDevice601 case TypeBlock:602 mode |= fs.ModeDevice603 case TypeDir:604 mode |= fs.ModeDir605 case TypeFifo:606 mode |= fs.ModeNamedPipe607 }608609 return mode610}611612func (fi headerFileInfo) String() string {613 return fs.FormatFileInfo(fi)614}615616// sysStat, if non-nil, populates h from system-dependent fields of fi.617var sysStat func(fi fs.FileInfo, h *Header, doNameLookups bool) error618619const (620 // Mode constants from the USTAR spec:621 // See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06622 c_ISUID = 04000 // Set uid623 c_ISGID = 02000 // Set gid624 c_ISVTX = 01000 // Save text (sticky bit)625626 // Common Unix mode constants; these are not defined in any common tar standard.627 // Header.FileInfo understands these, but FileInfoHeader will never produce these.628 c_ISDIR = 040000 // Directory629 c_ISFIFO = 010000 // FIFO630 c_ISREG = 0100000 // Regular file631 c_ISLNK = 0120000 // Symbolic link632 c_ISBLK = 060000 // Block special file633 c_ISCHR = 020000 // Character special file634 c_ISSOCK = 0140000 // Socket635)636637// FileInfoHeader creates a partially-populated [Header] from fi.638// If fi describes a symlink, FileInfoHeader records link as the link target.639// If fi describes a directory, a slash is appended to the name.640//641// Since fs.FileInfo's Name method only returns the base name of642// the file it describes, it may be necessary to modify Header.Name643// to provide the full path name of the file.644//645// If fi implements [FileInfoNames]646// Header.Gname and Header.Uname647// are provided by the methods of the interface.648func FileInfoHeader(fi fs.FileInfo, link string) (*Header, error) {649 if fi == nil {650 return nil, errors.New("archive/tar: FileInfo is nil")651 }652 fm := fi.Mode()653 h := &Header{654 Name: fi.Name(),655 ModTime: fi.ModTime(),656 Mode: int64(fm.Perm()), // or'd with c_IS* constants later657 }658 switch {659 case fm.IsRegular():660 h.Typeflag = TypeReg661 h.Size = fi.Size()662 case fi.IsDir():663 h.Typeflag = TypeDir664 h.Name += "/"665 case fm&fs.ModeSymlink != 0:666 h.Typeflag = TypeSymlink667 h.Linkname = link668 case fm&fs.ModeDevice != 0:669 if fm&fs.ModeCharDevice != 0 {670 h.Typeflag = TypeChar671 } else {672 h.Typeflag = TypeBlock673 }674 case fm&fs.ModeNamedPipe != 0:675 h.Typeflag = TypeFifo676 case fm&fs.ModeSocket != 0:677 return nil, fmt.Errorf("archive/tar: sockets not supported")678 default:679 return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)680 }681 if fm&fs.ModeSetuid != 0 {682 h.Mode |= c_ISUID683 }684 if fm&fs.ModeSetgid != 0 {685 h.Mode |= c_ISGID686 }687 if fm&fs.ModeSticky != 0 {688 h.Mode |= c_ISVTX689 }690 // If possible, populate additional fields from OS-specific691 // FileInfo fields.692 if sys, ok := fi.Sys().(*Header); ok {693 // This FileInfo came from a Header (not the OS). Use the694 // original Header to populate all remaining fields.695 h.Uid = sys.Uid696 h.Gid = sys.Gid697 h.Uname = sys.Uname698 h.Gname = sys.Gname699 h.AccessTime = sys.AccessTime700 h.ChangeTime = sys.ChangeTime701 h.Xattrs = maps.Clone(sys.Xattrs)702 if sys.Typeflag == TypeLink {703 // hard link704 h.Typeflag = TypeLink705 h.Size = 0706 h.Linkname = sys.Linkname707 }708 h.PAXRecords = maps.Clone(sys.PAXRecords)709 }710 var doNameLookups = true711 if iface, ok := fi.(FileInfoNames); ok {712 doNameLookups = false713 var err error714 h.Gname, err = iface.Gname()715 if err != nil {716 return nil, err717 }718 h.Uname, err = iface.Uname()719 if err != nil {720 return nil, err721 }722 }723 if sysStat != nil {724 return h, sysStat(fi, h, doNameLookups)725 }726 return h, nil727}728729// FileInfoNames extends [fs.FileInfo].730// Passing an instance of this to [FileInfoHeader] permits the caller731// to avoid a system-dependent name lookup by specifying the Uname and Gname directly.732type FileInfoNames interface {733 fs.FileInfo734 // Uname should give a user name.735 Uname() (string, error)736 // Gname should give a group name.737 Gname() (string, error)738}739740// isHeaderOnlyType checks if the given type flag is of the type that has no741// data section even if a size is specified.742func isHeaderOnlyType(flag byte) bool {743 switch flag {744 case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo:745 return true746 default:747 return false748 }749}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.