github.com/eun/go@v0.0.0-20170811110501-92cfd07a6cfd/src/archive/tar/common.go (about)

     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  // It aims to cover most of the variations, including those produced
     7  // by GNU and BSD tars.
     8  //
     9  // References:
    10  //   http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5
    11  //   http://www.gnu.org/software/tar/manual/html_node/Standard.html
    12  //   http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
    13  package tar
    14  
    15  import (
    16  	"errors"
    17  	"fmt"
    18  	"os"
    19  	"path"
    20  	"strconv"
    21  	"time"
    22  )
    23  
    24  // BUG: Use of the Uid and Gid fields in Header could overflow on 32-bit
    25  // architectures. If a large value is encountered when decoding, the result
    26  // stored in Header will be the truncated version.
    27  
    28  // Header type flags.
    29  const (
    30  	TypeReg           = '0'    // regular file
    31  	TypeRegA          = '\x00' // regular file
    32  	TypeLink          = '1'    // hard link
    33  	TypeSymlink       = '2'    // symbolic link
    34  	TypeChar          = '3'    // character device node
    35  	TypeBlock         = '4'    // block device node
    36  	TypeDir           = '5'    // directory
    37  	TypeFifo          = '6'    // fifo node
    38  	TypeCont          = '7'    // reserved
    39  	TypeXHeader       = 'x'    // extended header
    40  	TypeXGlobalHeader = 'g'    // global extended header
    41  	TypeGNULongName   = 'L'    // Next file has a long name
    42  	TypeGNULongLink   = 'K'    // Next file symlinks to a file w/ a long name
    43  	TypeGNUSparse     = 'S'    // sparse file
    44  )
    45  
    46  // A Header represents a single header in a tar archive.
    47  // Some fields may not be populated.
    48  type Header struct {
    49  	Name       string    // name of header file entry
    50  	Mode       int64     // permission and mode bits
    51  	Uid        int       // user id of owner
    52  	Gid        int       // group id of owner
    53  	Size       int64     // length in bytes
    54  	ModTime    time.Time // modified time
    55  	Typeflag   byte      // type of header entry
    56  	Linkname   string    // target name of link
    57  	Uname      string    // user name of owner
    58  	Gname      string    // group name of owner
    59  	Devmajor   int64     // major number of character or block device
    60  	Devminor   int64     // minor number of character or block device
    61  	AccessTime time.Time // access time
    62  	ChangeTime time.Time // status change time
    63  	Xattrs     map[string]string
    64  }
    65  
    66  // FileInfo returns an os.FileInfo for the Header.
    67  func (h *Header) FileInfo() os.FileInfo {
    68  	return headerFileInfo{h}
    69  }
    70  
    71  // allowedFormats determines which formats can be used. The value returned
    72  // is the logical OR of multiple possible formats. If the value is
    73  // formatUnknown, then the input Header cannot be encoded.
    74  //
    75  // As a by-product of checking the fields, this function returns paxHdrs, which
    76  // contain all fields that could not be directly encoded.
    77  func (h *Header) allowedFormats() (format int, paxHdrs map[string]string) {
    78  	format = formatUSTAR | formatPAX | formatGNU
    79  	paxHdrs = make(map[string]string)
    80  
    81  	verifyString := func(s string, size int, gnuLong bool, paxKey string) {
    82  		// NUL-terminator is optional for path and linkpath.
    83  		// Technically, it is required for uname and gname,
    84  		// but neither GNU nor BSD tar checks for it.
    85  		tooLong := len(s) > size
    86  		if !isASCII(s) || (tooLong && !gnuLong) {
    87  			// TODO(dsnet): GNU supports UTF-8 (without NUL) for strings.
    88  			format &^= formatGNU // No GNU
    89  		}
    90  		if !isASCII(s) || tooLong {
    91  			// TODO(dsnet): If the path is splittable, it is possible to still
    92  			// use the USTAR format.
    93  			format &^= formatUSTAR // No USTAR
    94  			if paxKey == paxNone {
    95  				format &^= formatPAX // No PAX
    96  			} else {
    97  				paxHdrs[paxKey] = s
    98  			}
    99  		}
   100  	}
   101  	verifyNumeric := func(n int64, size int, paxKey string) {
   102  		if !fitsInBase256(size, n) {
   103  			format &^= formatGNU // No GNU
   104  		}
   105  		if !fitsInOctal(size, n) {
   106  			format &^= formatUSTAR // No USTAR
   107  			if paxKey == paxNone {
   108  				format &^= formatPAX // No PAX
   109  			} else {
   110  				paxHdrs[paxKey] = strconv.FormatInt(n, 10)
   111  			}
   112  		}
   113  	}
   114  	verifyTime := func(ts time.Time, size int, ustarField bool, paxKey string) {
   115  		if ts.IsZero() {
   116  			return // Always okay
   117  		}
   118  		needsNano := ts.Nanosecond() != 0
   119  		if !fitsInBase256(size, ts.Unix()) || needsNano {
   120  			format &^= formatGNU // No GNU
   121  		}
   122  		if !fitsInOctal(size, ts.Unix()) || needsNano || !ustarField {
   123  			format &^= formatUSTAR // No USTAR
   124  			if paxKey == paxNone {
   125  				format &^= formatPAX // No PAX
   126  			} else {
   127  				// TODO(dsnet): Support PAX time here.
   128  				// paxHdrs[paxKey] = formatPAXTime(ts)
   129  			}
   130  		}
   131  	}
   132  
   133  	// TODO(dsnet): Add GNU long name support.
   134  	const supportGNULong = false
   135  
   136  	var blk block
   137  	var v7 = blk.V7()
   138  	var ustar = blk.USTAR()
   139  	verifyString(h.Name, len(v7.Name()), supportGNULong, paxPath)
   140  	verifyString(h.Linkname, len(v7.LinkName()), supportGNULong, paxLinkpath)
   141  	verifyString(h.Uname, len(ustar.UserName()), false, paxUname)
   142  	verifyString(h.Gname, len(ustar.GroupName()), false, paxGname)
   143  	verifyNumeric(h.Mode, len(v7.Mode()), paxNone)
   144  	verifyNumeric(int64(h.Uid), len(v7.UID()), paxUid)
   145  	verifyNumeric(int64(h.Gid), len(v7.GID()), paxGid)
   146  	verifyNumeric(h.Size, len(v7.Size()), paxSize)
   147  	verifyNumeric(h.Devmajor, len(ustar.DevMajor()), paxNone)
   148  	verifyNumeric(h.Devminor, len(ustar.DevMinor()), paxNone)
   149  	verifyTime(h.ModTime, len(v7.ModTime()), true, paxMtime)
   150  	// TODO(dsnet): Support atime and ctime fields.
   151  	// verifyTime(h.AccessTime, len(gnu.AccessTime()), false, paxAtime)
   152  	// verifyTime(h.ChangeTime, len(gnu.ChangeTime()), false, paxCtime)
   153  
   154  	if !isHeaderOnlyType(h.Typeflag) && h.Size < 0 {
   155  		return formatUnknown, nil
   156  	}
   157  	if len(h.Xattrs) > 0 {
   158  		for k, v := range h.Xattrs {
   159  			paxHdrs[paxXattr+k] = v
   160  		}
   161  		format &= formatPAX // PAX only
   162  	}
   163  	for k, v := range paxHdrs {
   164  		if !validPAXRecord(k, v) {
   165  			return formatUnknown, nil // Invalid PAX key
   166  		}
   167  	}
   168  	return format, paxHdrs
   169  }
   170  
   171  // headerFileInfo implements os.FileInfo.
   172  type headerFileInfo struct {
   173  	h *Header
   174  }
   175  
   176  func (fi headerFileInfo) Size() int64        { return fi.h.Size }
   177  func (fi headerFileInfo) IsDir() bool        { return fi.Mode().IsDir() }
   178  func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
   179  func (fi headerFileInfo) Sys() interface{}   { return fi.h }
   180  
   181  // Name returns the base name of the file.
   182  func (fi headerFileInfo) Name() string {
   183  	if fi.IsDir() {
   184  		return path.Base(path.Clean(fi.h.Name))
   185  	}
   186  	return path.Base(fi.h.Name)
   187  }
   188  
   189  // Mode returns the permission and mode bits for the headerFileInfo.
   190  func (fi headerFileInfo) Mode() (mode os.FileMode) {
   191  	// Set file permission bits.
   192  	mode = os.FileMode(fi.h.Mode).Perm()
   193  
   194  	// Set setuid, setgid and sticky bits.
   195  	if fi.h.Mode&c_ISUID != 0 {
   196  		// setuid
   197  		mode |= os.ModeSetuid
   198  	}
   199  	if fi.h.Mode&c_ISGID != 0 {
   200  		// setgid
   201  		mode |= os.ModeSetgid
   202  	}
   203  	if fi.h.Mode&c_ISVTX != 0 {
   204  		// sticky
   205  		mode |= os.ModeSticky
   206  	}
   207  
   208  	// Set file mode bits.
   209  	// clear perm, setuid, setgid and sticky bits.
   210  	m := os.FileMode(fi.h.Mode) &^ 07777
   211  	if m == c_ISDIR {
   212  		// directory
   213  		mode |= os.ModeDir
   214  	}
   215  	if m == c_ISFIFO {
   216  		// named pipe (FIFO)
   217  		mode |= os.ModeNamedPipe
   218  	}
   219  	if m == c_ISLNK {
   220  		// symbolic link
   221  		mode |= os.ModeSymlink
   222  	}
   223  	if m == c_ISBLK {
   224  		// device file
   225  		mode |= os.ModeDevice
   226  	}
   227  	if m == c_ISCHR {
   228  		// Unix character device
   229  		mode |= os.ModeDevice
   230  		mode |= os.ModeCharDevice
   231  	}
   232  	if m == c_ISSOCK {
   233  		// Unix domain socket
   234  		mode |= os.ModeSocket
   235  	}
   236  
   237  	switch fi.h.Typeflag {
   238  	case TypeSymlink:
   239  		// symbolic link
   240  		mode |= os.ModeSymlink
   241  	case TypeChar:
   242  		// character device node
   243  		mode |= os.ModeDevice
   244  		mode |= os.ModeCharDevice
   245  	case TypeBlock:
   246  		// block device node
   247  		mode |= os.ModeDevice
   248  	case TypeDir:
   249  		// directory
   250  		mode |= os.ModeDir
   251  	case TypeFifo:
   252  		// fifo node
   253  		mode |= os.ModeNamedPipe
   254  	}
   255  
   256  	return mode
   257  }
   258  
   259  // sysStat, if non-nil, populates h from system-dependent fields of fi.
   260  var sysStat func(fi os.FileInfo, h *Header) error
   261  
   262  const (
   263  	// Mode constants from the USTAR spec:
   264  	// See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06
   265  	c_ISUID = 04000 // Set uid
   266  	c_ISGID = 02000 // Set gid
   267  	c_ISVTX = 01000 // Save text (sticky bit)
   268  
   269  	// Common Unix mode constants; these are not defined in any common tar standard.
   270  	// Header.FileInfo understands these, but FileInfoHeader will never produce these.
   271  	c_ISDIR  = 040000  // Directory
   272  	c_ISFIFO = 010000  // FIFO
   273  	c_ISREG  = 0100000 // Regular file
   274  	c_ISLNK  = 0120000 // Symbolic link
   275  	c_ISBLK  = 060000  // Block special file
   276  	c_ISCHR  = 020000  // Character special file
   277  	c_ISSOCK = 0140000 // Socket
   278  )
   279  
   280  // Keywords for the PAX Extended Header
   281  const (
   282  	paxAtime    = "atime"
   283  	paxCharset  = "charset"
   284  	paxComment  = "comment"
   285  	paxCtime    = "ctime" // please note that ctime is not a valid pax header.
   286  	paxGid      = "gid"
   287  	paxGname    = "gname"
   288  	paxLinkpath = "linkpath"
   289  	paxMtime    = "mtime"
   290  	paxPath     = "path"
   291  	paxSize     = "size"
   292  	paxUid      = "uid"
   293  	paxUname    = "uname"
   294  	paxXattr    = "SCHILY.xattr."
   295  	paxNone     = ""
   296  )
   297  
   298  // FileInfoHeader creates a partially-populated Header from fi.
   299  // If fi describes a symlink, FileInfoHeader records link as the link target.
   300  // If fi describes a directory, a slash is appended to the name.
   301  // Because os.FileInfo's Name method returns only the base name of
   302  // the file it describes, it may be necessary to modify the Name field
   303  // of the returned header to provide the full path name of the file.
   304  func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
   305  	if fi == nil {
   306  		return nil, errors.New("tar: FileInfo is nil")
   307  	}
   308  	fm := fi.Mode()
   309  	h := &Header{
   310  		Name:    fi.Name(),
   311  		ModTime: fi.ModTime(),
   312  		Mode:    int64(fm.Perm()), // or'd with c_IS* constants later
   313  	}
   314  	switch {
   315  	case fm.IsRegular():
   316  		h.Typeflag = TypeReg
   317  		h.Size = fi.Size()
   318  	case fi.IsDir():
   319  		h.Typeflag = TypeDir
   320  		h.Name += "/"
   321  	case fm&os.ModeSymlink != 0:
   322  		h.Typeflag = TypeSymlink
   323  		h.Linkname = link
   324  	case fm&os.ModeDevice != 0:
   325  		if fm&os.ModeCharDevice != 0 {
   326  			h.Typeflag = TypeChar
   327  		} else {
   328  			h.Typeflag = TypeBlock
   329  		}
   330  	case fm&os.ModeNamedPipe != 0:
   331  		h.Typeflag = TypeFifo
   332  	case fm&os.ModeSocket != 0:
   333  		return nil, fmt.Errorf("archive/tar: sockets not supported")
   334  	default:
   335  		return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
   336  	}
   337  	if fm&os.ModeSetuid != 0 {
   338  		h.Mode |= c_ISUID
   339  	}
   340  	if fm&os.ModeSetgid != 0 {
   341  		h.Mode |= c_ISGID
   342  	}
   343  	if fm&os.ModeSticky != 0 {
   344  		h.Mode |= c_ISVTX
   345  	}
   346  	// If possible, populate additional fields from OS-specific
   347  	// FileInfo fields.
   348  	if sys, ok := fi.Sys().(*Header); ok {
   349  		// This FileInfo came from a Header (not the OS). Use the
   350  		// original Header to populate all remaining fields.
   351  		h.Uid = sys.Uid
   352  		h.Gid = sys.Gid
   353  		h.Uname = sys.Uname
   354  		h.Gname = sys.Gname
   355  		h.AccessTime = sys.AccessTime
   356  		h.ChangeTime = sys.ChangeTime
   357  		if sys.Xattrs != nil {
   358  			h.Xattrs = make(map[string]string)
   359  			for k, v := range sys.Xattrs {
   360  				h.Xattrs[k] = v
   361  			}
   362  		}
   363  		if sys.Typeflag == TypeLink {
   364  			// hard link
   365  			h.Typeflag = TypeLink
   366  			h.Size = 0
   367  			h.Linkname = sys.Linkname
   368  		}
   369  	}
   370  	if sysStat != nil {
   371  		return h, sysStat(fi, h)
   372  	}
   373  	return h, nil
   374  }
   375  
   376  // isHeaderOnlyType checks if the given type flag is of the type that has no
   377  // data section even if a size is specified.
   378  func isHeaderOnlyType(flag byte) bool {
   379  	switch flag {
   380  	case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo:
   381  		return true
   382  	default:
   383  		return false
   384  	}
   385  }