github.com/qxnw/lib4go@v0.0.0-20180426074627-c80c7e84b925/archiver/tar.go (about)

     1  package archiver
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  )
    13  
    14  // Tar is for Tar format12312111
    15  var Tar tarFormat
    16  
    17  func init() {
    18  	RegisterFormat("Tar", Tar)
    19  }
    20  
    21  type tarFormat struct{}
    22  
    23  func (tarFormat) Match(filename string) bool {
    24  	return strings.HasSuffix(strings.ToLower(filename), ".tar") || isTar(filename)
    25  }
    26  
    27  const tarBlockSize int = 512
    28  
    29  // isTar checks the file has the Tar format header by reading its beginning
    30  // block.
    31  func isTar(tarPath string) bool {
    32  	f, err := os.Open(tarPath)
    33  	if err != nil {
    34  		return false
    35  	}
    36  	defer f.Close()
    37  
    38  	buf := make([]byte, tarBlockSize)
    39  	if _, err = io.ReadFull(f, buf); err != nil {
    40  		return false
    41  	}
    42  
    43  	return hasTarHeader(buf)
    44  }
    45  
    46  // hasTarHeader checks passed bytes has a valid tar header or not. buf must
    47  // contain at least 512 bytes and if not, it always returns false.
    48  func hasTarHeader(buf []byte) bool {
    49  	if len(buf) < tarBlockSize {
    50  		return false
    51  	}
    52  
    53  	b := buf[148:156]
    54  	b = bytes.Trim(b, " \x00") // clean up all spaces and null bytes
    55  	if len(b) == 0 {
    56  		return false // unknown format
    57  	}
    58  	hdrSum, err := strconv.ParseUint(string(b), 8, 64)
    59  	if err != nil {
    60  		return false
    61  	}
    62  
    63  	// According to the go official archive/tar, Sun tar uses signed byte
    64  	// values so this calcs both signed and unsigned
    65  	var usum uint64
    66  	var sum int64
    67  	for i, c := range buf {
    68  		if 148 <= i && i < 156 {
    69  			c = ' ' // checksum field itself is counted as branks
    70  		}
    71  		usum += uint64(uint8(c))
    72  		sum += int64(int8(c))
    73  	}
    74  
    75  	if hdrSum != usum && int64(hdrSum) != sum {
    76  		return false // invalid checksum
    77  	}
    78  
    79  	return true
    80  }
    81  
    82  // Write outputs a .tar file to a Writer containing the
    83  // contents of files listed in filePaths. File paths can
    84  // be those of regular files or directories. Regular
    85  // files are stored at the 'root' of the archive, and
    86  // directories are recursively added.
    87  func (tarFormat) Write(output io.Writer, filePaths []string) error {
    88  	return writeTar(filePaths, output, "")
    89  }
    90  
    91  // Make creates a .tar file at tarPath containing the
    92  // contents of files listed in filePaths. File paths can
    93  // be those of regular files or directories. Regular
    94  // files are stored at the 'root' of the archive, and
    95  // directories are recursively added.
    96  func (tarFormat) Make(tarPath string, filePaths []string) error {
    97  	out, err := os.Create(tarPath)
    98  	if err != nil {
    99  		return fmt.Errorf("error creating %s: %v", tarPath, err)
   100  	}
   101  	defer out.Close()
   102  
   103  	return writeTar(filePaths, out, tarPath)
   104  }
   105  
   106  func writeTar(filePaths []string, output io.Writer, dest string) error {
   107  	tarWriter := tar.NewWriter(output)
   108  	defer tarWriter.Close()
   109  
   110  	return tarball(filePaths, tarWriter, dest)
   111  }
   112  
   113  // tarball writes all files listed in filePaths into tarWriter, which is
   114  // writing into a file located at dest.
   115  func tarball(filePaths []string, tarWriter *tar.Writer, dest string) error {
   116  	for _, fpath := range filePaths {
   117  		err := tarFile(tarWriter, fpath, dest)
   118  		if err != nil {
   119  			return err
   120  		}
   121  	}
   122  	return nil
   123  }
   124  
   125  // tarFile writes the file at source into tarWriter. It does so
   126  // recursively for directories.
   127  func tarFile(tarWriter *tar.Writer, source, dest string) error {
   128  	sourceInfo, err := os.Stat(source)
   129  	if err != nil {
   130  		return fmt.Errorf("%s: stat: %v", source, err)
   131  	}
   132  
   133  	var baseDir string
   134  	if sourceInfo.IsDir() {
   135  		baseDir = filepath.Base(source)
   136  	}
   137  
   138  	return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
   139  		if err != nil {
   140  			return fmt.Errorf("error walking to %s: %v", path, err)
   141  		}
   142  
   143  		header, err := tar.FileInfoHeader(info, path)
   144  		if err != nil {
   145  			return fmt.Errorf("%s: making header: %v", path, err)
   146  		}
   147  
   148  		if baseDir != "" {
   149  			header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))
   150  		}
   151  
   152  		if header.Name == dest {
   153  			// our new tar file is inside the directory being archived; skip it
   154  			return nil
   155  		}
   156  
   157  		if info.IsDir() {
   158  			header.Name += "/"
   159  		}
   160  
   161  		err = tarWriter.WriteHeader(header)
   162  		if err != nil {
   163  			return fmt.Errorf("%s: writing header: %v", path, err)
   164  		}
   165  
   166  		if info.IsDir() {
   167  			return nil
   168  		}
   169  
   170  		if header.Typeflag == tar.TypeReg {
   171  			file, err := os.Open(path)
   172  			if err != nil {
   173  				return fmt.Errorf("%s: open: %v", path, err)
   174  			}
   175  			defer file.Close()
   176  
   177  			_, err = io.CopyN(tarWriter, file, info.Size())
   178  			if err != nil && err != io.EOF {
   179  				return fmt.Errorf("%s: copying contents: %v", path, err)
   180  			}
   181  		}
   182  		return nil
   183  	})
   184  }
   185  
   186  // Read untars a .tar file read from a Reader and puts
   187  // the contents into destination.
   188  func (tarFormat) Read(input io.Reader, destination string) error {
   189  	return untar(tar.NewReader(input), destination)
   190  }
   191  
   192  // Open untars source and puts the contents into destination.
   193  func (tarFormat) Open(source, destination string) error {
   194  	f, err := os.Open(source)
   195  	if err != nil {
   196  		return fmt.Errorf("%s: failed to open archive: %v", source, err)
   197  	}
   198  	defer f.Close()
   199  
   200  	return Tar.Read(f, destination)
   201  }
   202  
   203  // untar un-tarballs the contents of tr into destination.
   204  func untar(tr *tar.Reader, destination string) error {
   205  	for {
   206  		header, err := tr.Next()
   207  		if err == io.EOF {
   208  			break
   209  		} else if err != nil {
   210  			return err
   211  		}
   212  
   213  		if err := untarFile(tr, header, destination); err != nil {
   214  			return err
   215  		}
   216  	}
   217  	return nil
   218  }
   219  
   220  // untarFile untars a single file from tr with header header into destination.
   221  func untarFile(tr *tar.Reader, header *tar.Header, destination string) error {
   222  	switch header.Typeflag {
   223  	case tar.TypeDir:
   224  		return mkdir(filepath.Join(destination, header.Name))
   225  	case tar.TypeReg, tar.TypeRegA, tar.TypeChar, tar.TypeBlock, tar.TypeFifo:
   226  		return writeNewFile(filepath.Join(destination, header.Name), tr, header.FileInfo().Mode())
   227  	case tar.TypeSymlink:
   228  		return writeNewSymbolicLink(filepath.Join(destination, header.Name), header.Linkname)
   229  	case tar.TypeLink:
   230  		return writeNewHardLink(filepath.Join(destination, header.Name), filepath.Join(destination, header.Linkname))
   231  	default:
   232  		return fmt.Errorf("%s: unknown type flag: %c", header.Name, header.Typeflag)
   233  	}
   234  }