mirror of
https://github.com/vbatts/tar-split.git
synced 2024-11-18 14:28:38 +00:00
2c3c708698
The Reader and Writer have hard-coded constants regarding the offsets and lengths of certain fields in the tar format sprinkled all over. This makes it harder to verify that the offsets are correct since a reviewer would need to search for them throughout the code. Instead, all information about the layout of header fields should be centralized in one single file. This has the advantage of being both centralized, and also acting as a form of documentation about the header struct format. This method was chosen over using "encoding/binary" since that method would cause an allocation of a header struct every time binary.Read was called. This method causes zero allocations and its logic is no longer than if structs were declared. Updates #12594 Change-Id: Ic7a0565d2a2cd95d955547ace3b6dea2b57fab34 Reviewed-on: https://go-review.googlesource.com/14669 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
305 lines
7.7 KiB
Go
305 lines
7.7 KiB
Go
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package tar implements access to tar archives.
|
|
// It aims to cover most of the variations, including those produced
|
|
// by GNU and BSD tars.
|
|
//
|
|
// References:
|
|
// http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5
|
|
// http://www.gnu.org/software/tar/manual/html_node/Standard.html
|
|
// http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
|
|
package tar
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"time"
|
|
)
|
|
|
|
// Header type flags.
|
|
const (
|
|
TypeReg = '0' // regular file
|
|
TypeRegA = '\x00' // regular file
|
|
TypeLink = '1' // hard link
|
|
TypeSymlink = '2' // symbolic link
|
|
TypeChar = '3' // character device node
|
|
TypeBlock = '4' // block device node
|
|
TypeDir = '5' // directory
|
|
TypeFifo = '6' // fifo node
|
|
TypeCont = '7' // reserved
|
|
TypeXHeader = 'x' // extended header
|
|
TypeXGlobalHeader = 'g' // global extended header
|
|
TypeGNULongName = 'L' // Next file has a long name
|
|
TypeGNULongLink = 'K' // Next file symlinks to a file w/ a long name
|
|
TypeGNUSparse = 'S' // sparse file
|
|
)
|
|
|
|
// A Header represents a single header in a tar archive.
|
|
// Some fields may not be populated.
|
|
type Header struct {
|
|
Name string // name of header file entry
|
|
Mode int64 // permission and mode bits
|
|
Uid int // user id of owner
|
|
Gid int // group id of owner
|
|
Size int64 // length in bytes
|
|
ModTime time.Time // modified time
|
|
Typeflag byte // type of header entry
|
|
Linkname string // target name of link
|
|
Uname string // user name of owner
|
|
Gname string // group name of owner
|
|
Devmajor int64 // major number of character or block device
|
|
Devminor int64 // minor number of character or block device
|
|
AccessTime time.Time // access time
|
|
ChangeTime time.Time // status change time
|
|
Xattrs map[string]string
|
|
}
|
|
|
|
// FileInfo returns an os.FileInfo for the Header.
|
|
func (h *Header) FileInfo() os.FileInfo {
|
|
return headerFileInfo{h}
|
|
}
|
|
|
|
// headerFileInfo implements os.FileInfo.
|
|
type headerFileInfo struct {
|
|
h *Header
|
|
}
|
|
|
|
func (fi headerFileInfo) Size() int64 { return fi.h.Size }
|
|
func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
|
func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
|
|
func (fi headerFileInfo) Sys() interface{} { return fi.h }
|
|
|
|
// Name returns the base name of the file.
|
|
func (fi headerFileInfo) Name() string {
|
|
if fi.IsDir() {
|
|
return path.Base(path.Clean(fi.h.Name))
|
|
}
|
|
return path.Base(fi.h.Name)
|
|
}
|
|
|
|
// Mode returns the permission and mode bits for the headerFileInfo.
|
|
func (fi headerFileInfo) Mode() (mode os.FileMode) {
|
|
// Set file permission bits.
|
|
mode = os.FileMode(fi.h.Mode).Perm()
|
|
|
|
// Set setuid, setgid and sticky bits.
|
|
if fi.h.Mode&c_ISUID != 0 {
|
|
// setuid
|
|
mode |= os.ModeSetuid
|
|
}
|
|
if fi.h.Mode&c_ISGID != 0 {
|
|
// setgid
|
|
mode |= os.ModeSetgid
|
|
}
|
|
if fi.h.Mode&c_ISVTX != 0 {
|
|
// sticky
|
|
mode |= os.ModeSticky
|
|
}
|
|
|
|
// Set file mode bits.
|
|
// clear perm, setuid, setgid and sticky bits.
|
|
m := os.FileMode(fi.h.Mode) &^ 07777
|
|
if m == c_ISDIR {
|
|
// directory
|
|
mode |= os.ModeDir
|
|
}
|
|
if m == c_ISFIFO {
|
|
// named pipe (FIFO)
|
|
mode |= os.ModeNamedPipe
|
|
}
|
|
if m == c_ISLNK {
|
|
// symbolic link
|
|
mode |= os.ModeSymlink
|
|
}
|
|
if m == c_ISBLK {
|
|
// device file
|
|
mode |= os.ModeDevice
|
|
}
|
|
if m == c_ISCHR {
|
|
// Unix character device
|
|
mode |= os.ModeDevice
|
|
mode |= os.ModeCharDevice
|
|
}
|
|
if m == c_ISSOCK {
|
|
// Unix domain socket
|
|
mode |= os.ModeSocket
|
|
}
|
|
|
|
switch fi.h.Typeflag {
|
|
case TypeSymlink:
|
|
// symbolic link
|
|
mode |= os.ModeSymlink
|
|
case TypeChar:
|
|
// character device node
|
|
mode |= os.ModeDevice
|
|
mode |= os.ModeCharDevice
|
|
case TypeBlock:
|
|
// block device node
|
|
mode |= os.ModeDevice
|
|
case TypeDir:
|
|
// directory
|
|
mode |= os.ModeDir
|
|
case TypeFifo:
|
|
// fifo node
|
|
mode |= os.ModeNamedPipe
|
|
}
|
|
|
|
return mode
|
|
}
|
|
|
|
// sysStat, if non-nil, populates h from system-dependent fields of fi.
|
|
var sysStat func(fi os.FileInfo, h *Header) error
|
|
|
|
// Mode constants from the tar spec.
|
|
const (
|
|
c_ISUID = 04000 // Set uid
|
|
c_ISGID = 02000 // Set gid
|
|
c_ISVTX = 01000 // Save text (sticky bit)
|
|
c_ISDIR = 040000 // Directory
|
|
c_ISFIFO = 010000 // FIFO
|
|
c_ISREG = 0100000 // Regular file
|
|
c_ISLNK = 0120000 // Symbolic link
|
|
c_ISBLK = 060000 // Block special file
|
|
c_ISCHR = 020000 // Character special file
|
|
c_ISSOCK = 0140000 // Socket
|
|
)
|
|
|
|
// Keywords for the PAX Extended Header
|
|
const (
|
|
paxAtime = "atime"
|
|
paxCharset = "charset"
|
|
paxComment = "comment"
|
|
paxCtime = "ctime" // please note that ctime is not a valid pax header.
|
|
paxGid = "gid"
|
|
paxGname = "gname"
|
|
paxLinkpath = "linkpath"
|
|
paxMtime = "mtime"
|
|
paxPath = "path"
|
|
paxSize = "size"
|
|
paxUid = "uid"
|
|
paxUname = "uname"
|
|
paxXattr = "SCHILY.xattr."
|
|
paxNone = ""
|
|
)
|
|
|
|
// FileInfoHeader creates a partially-populated Header from fi.
|
|
// If fi describes a symlink, FileInfoHeader records link as the link target.
|
|
// If fi describes a directory, a slash is appended to the name.
|
|
// Because os.FileInfo's Name method returns only the base name of
|
|
// the file it describes, it may be necessary to modify the Name field
|
|
// of the returned header to provide the full path name of the file.
|
|
func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
|
|
if fi == nil {
|
|
return nil, errors.New("tar: FileInfo is nil")
|
|
}
|
|
fm := fi.Mode()
|
|
h := &Header{
|
|
Name: fi.Name(),
|
|
ModTime: fi.ModTime(),
|
|
Mode: int64(fm.Perm()), // or'd with c_IS* constants later
|
|
}
|
|
switch {
|
|
case fm.IsRegular():
|
|
h.Mode |= c_ISREG
|
|
h.Typeflag = TypeReg
|
|
h.Size = fi.Size()
|
|
case fi.IsDir():
|
|
h.Typeflag = TypeDir
|
|
h.Mode |= c_ISDIR
|
|
h.Name += "/"
|
|
case fm&os.ModeSymlink != 0:
|
|
h.Typeflag = TypeSymlink
|
|
h.Mode |= c_ISLNK
|
|
h.Linkname = link
|
|
case fm&os.ModeDevice != 0:
|
|
if fm&os.ModeCharDevice != 0 {
|
|
h.Mode |= c_ISCHR
|
|
h.Typeflag = TypeChar
|
|
} else {
|
|
h.Mode |= c_ISBLK
|
|
h.Typeflag = TypeBlock
|
|
}
|
|
case fm&os.ModeNamedPipe != 0:
|
|
h.Typeflag = TypeFifo
|
|
h.Mode |= c_ISFIFO
|
|
case fm&os.ModeSocket != 0:
|
|
h.Mode |= c_ISSOCK
|
|
default:
|
|
return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
|
|
}
|
|
if fm&os.ModeSetuid != 0 {
|
|
h.Mode |= c_ISUID
|
|
}
|
|
if fm&os.ModeSetgid != 0 {
|
|
h.Mode |= c_ISGID
|
|
}
|
|
if fm&os.ModeSticky != 0 {
|
|
h.Mode |= c_ISVTX
|
|
}
|
|
// If possible, populate additional fields from OS-specific
|
|
// FileInfo fields.
|
|
if sys, ok := fi.Sys().(*Header); ok {
|
|
// This FileInfo came from a Header (not the OS). Use the
|
|
// original Header to populate all remaining fields.
|
|
h.Uid = sys.Uid
|
|
h.Gid = sys.Gid
|
|
h.Uname = sys.Uname
|
|
h.Gname = sys.Gname
|
|
h.AccessTime = sys.AccessTime
|
|
h.ChangeTime = sys.ChangeTime
|
|
if sys.Xattrs != nil {
|
|
h.Xattrs = make(map[string]string)
|
|
for k, v := range sys.Xattrs {
|
|
h.Xattrs[k] = v
|
|
}
|
|
}
|
|
if sys.Typeflag == TypeLink {
|
|
// hard link
|
|
h.Typeflag = TypeLink
|
|
h.Size = 0
|
|
h.Linkname = sys.Linkname
|
|
}
|
|
}
|
|
if sysStat != nil {
|
|
return h, sysStat(fi, h)
|
|
}
|
|
return h, nil
|
|
}
|
|
|
|
func isASCII(s string) bool {
|
|
for _, c := range s {
|
|
if c >= 0x80 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func toASCII(s string) string {
|
|
if isASCII(s) {
|
|
return s
|
|
}
|
|
var buf bytes.Buffer
|
|
for _, c := range s {
|
|
if c < 0x80 {
|
|
buf.WriteByte(byte(c))
|
|
}
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
// isHeaderOnlyType checks if the given type flag is of the type that has no
|
|
// data section even if a size is specified.
|
|
func isHeaderOnlyType(flag byte) bool {
|
|
switch flag {
|
|
case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|