clair/vendor/github.com/artyom/untar/untar.go
2016-09-28 15:24:38 +02:00

159 lines
4.2 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package untar provides helper function to easily extract contents of a tar
// stream to a file system directory.
//
// Useful for cases where you'd want a replacement for external call to `tar x`.
// It differs from `tar x` call by not setting proper times on symlinks itself.
// Extended attributes are not supported.
//
// It's tested on OS X and Linux amd64 and is enough to unpack linux root
// filesystem to a useable state.
package untar
import (
"archive/tar"
"fmt"
"io"
"os"
"path/filepath"
"sync"
"time"
"golang.org/x/sys/unix"
)
// Untar extracts each item from a tar stream and saves it into file system
// directory. It stops on first error it encounters; if extracted over existing
// file system tree, matching files would be overwritten. If destination
// directory does not exist, it will be created.
//
// Note that permissions on unpacked data would be set with current umask taken
// into account; if you expect to get exact permissions, call syscall.Umask(0)
// beforehand. This function does not call it itself as this changes umask
// process-wide, so it's safer to do this explicitly.
//
// Owner/group of extracted files are set only if run as root (os.Getuid() == 0)
// and are only set as numeric values, user/group names are not taken into
// account.
func Untar(f io.Reader, dst string) error {
isRoot := os.Getuid() == 0
tr := tar.NewReader(f)
for {
hdr, err := tr.Next()
switch err {
case nil:
case io.EOF:
return nil
default:
return err
}
name := filepath.Join(dst, filepath.Clean(hdr.Name))
mode := hdr.FileInfo().Mode()
ProcessHeader:
switch hdr.Typeflag {
case tar.TypeReg, tar.TypeRegA:
err = writeFile(name, mode, tr)
case tar.TypeDir:
err = os.MkdirAll(name, mode)
case tar.TypeLink:
err = os.Link(filepath.Join(dst, filepath.Clean(hdr.Linkname)), name)
case tar.TypeSymlink:
err = os.Symlink(filepath.Clean(hdr.Linkname), name)
case tar.TypeFifo:
err = unix.Mkfifo(name, syscallMode(mode))
case tar.TypeChar, tar.TypeBlock:
err = unix.Mknod(name, syscallMode(mode), devNo(hdr.Devmajor, hdr.Devminor))
case tar.TypeXGlobalHeader, tar.TypeXHeader:
continue
default:
return fmt.Errorf("unsupported header type flag for %[2]q: %#[1]x (%[1]q)", hdr.Typeflag, hdr.Name)
}
if err != nil {
if os.IsExist(err) {
// if file already exists, try to remove it and
// re-process  this is for everything except
// directories and regular files
if os.Remove(name) == nil {
goto ProcessHeader
}
}
return err
}
switch hdr.Typeflag {
case tar.TypeReg, tar.TypeRegA, tar.TypeDir, tar.TypeChar, tar.TypeBlock, tar.TypeFifo:
if !hdr.AccessTime.IsZero() || !hdr.ModTime.IsZero() {
now := time.Now()
atime, mtime := hdr.AccessTime, hdr.ModTime
// fix times that don't fit unix epoch
if atime.UnixNano() < 0 {
atime = now
}
if mtime.UnixNano() < 0 {
mtime = now
}
if err := os.Chtimes(name, atime, mtime); err != nil {
return err
}
}
if isRoot {
if err := os.Chown(name, hdr.Uid, hdr.Gid); err != nil {
return err
}
// group change resets special attributes like
// setgid, restore them
if mode&os.ModeSetgid != 0 || mode&os.ModeSetuid != 0 {
if err := os.Chmod(name, mode); err != nil {
return err
}
}
}
}
}
}
func writeFile(name string, fm os.FileMode, rd io.Reader) error {
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fm)
if err != nil {
return err
}
defer f.Close()
bufp := copyBufPool.Get().(*[]byte)
defer copyBufPool.Put(bufp)
if _, err := io.CopyBuffer(f, rd, *bufp); err != nil {
return err
}
return f.Close()
}
// syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
func syscallMode(i os.FileMode) (o uint32) {
o |= uint32(i.Perm())
if i&os.ModeSetuid != 0 {
o |= unix.S_ISUID
}
if i&os.ModeSetgid != 0 {
o |= unix.S_ISGID
}
if i&os.ModeSticky != 0 {
o |= unix.S_ISVTX
}
if i&os.ModeNamedPipe != 0 {
o |= unix.S_IFIFO
}
if i&os.ModeDevice != 0 {
switch i & os.ModeCharDevice {
case 0:
o |= unix.S_IFBLK
default:
o |= unix.S_IFCHR
}
}
return
}
var copyBufPool = sync.Pool{
New: func() interface{} {
b := make([]byte, 512*1024)
return &b
},
}