123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- package asm
- import (
- "bytes"
- "fmt"
- "hash"
- "hash/crc64"
- "io"
- "sync"
- "github.com/vbatts/tar-split/tar/storage"
- )
- // NewOutputTarStream returns an io.ReadCloser that is an assembled tar archive
- // stream.
- //
- // It takes a storage.FileGetter, for mapping the file payloads that are to be read in,
- // and a storage.Unpacker, which has access to the rawbytes and file order
- // metadata. With the combination of these two items, a precise assembled Tar
- // archive is possible.
- func NewOutputTarStream(fg storage.FileGetter, up storage.Unpacker) io.ReadCloser {
- // ... Since these are interfaces, this is possible, so let's not have a nil pointer
- if fg == nil || up == nil {
- return nil
- }
- pr, pw := io.Pipe()
- go func() {
- err := WriteOutputTarStream(fg, up, pw)
- if err != nil {
- pw.CloseWithError(err)
- } else {
- pw.Close()
- }
- }()
- return pr
- }
- // WriteOutputTarStream writes assembled tar archive to a writer.
- func WriteOutputTarStream(fg storage.FileGetter, up storage.Unpacker, w io.Writer) error {
- // ... Since these are interfaces, this is possible, so let's not have a nil pointer
- if fg == nil || up == nil {
- return nil
- }
- var copyBuffer []byte
- var crcHash hash.Hash
- var crcSum []byte
- var multiWriter io.Writer
- for {
- entry, err := up.Next()
- if err != nil {
- if err == io.EOF {
- return nil
- }
- return err
- }
- switch entry.Type {
- case storage.SegmentType:
- if _, err := w.Write(entry.Payload); err != nil {
- return err
- }
- case storage.FileType:
- if entry.Size == 0 {
- continue
- }
- fh, err := fg.Get(entry.GetName())
- if err != nil {
- return err
- }
- if crcHash == nil {
- crcHash = crc64.New(storage.CRCTable)
- crcSum = make([]byte, 8)
- multiWriter = io.MultiWriter(w, crcHash)
- copyBuffer = byteBufferPool.Get().([]byte)
- defer byteBufferPool.Put(copyBuffer)
- } else {
- crcHash.Reset()
- }
- if _, err := copyWithBuffer(multiWriter, fh, copyBuffer); err != nil {
- fh.Close()
- return err
- }
- if !bytes.Equal(crcHash.Sum(crcSum[:0]), entry.Payload) {
- // I would rather this be a comparable ErrInvalidChecksum or such,
- // but since it's coming through the PipeReader, the context of
- // _which_ file would be lost...
- fh.Close()
- return fmt.Errorf("file integrity checksum failed for %q", entry.GetName())
- }
- fh.Close()
- }
- }
- }
- var byteBufferPool = &sync.Pool{
- New: func() interface{} {
- return make([]byte, 32*1024)
- },
- }
- // copyWithBuffer is taken from stdlib io.Copy implementation
- // https://github.com/golang/go/blob/go1.5.1/src/io/io.go#L367
- func copyWithBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) {
- for {
- nr, er := src.Read(buf)
- if nr > 0 {
- nw, ew := dst.Write(buf[0:nr])
- if nw > 0 {
- written += int64(nw)
- }
- if ew != nil {
- err = ew
- break
- }
- if nr != nw {
- err = io.ErrShortWrite
- break
- }
- }
- if er == io.EOF {
- break
- }
- if er != nil {
- err = er
- break
- }
- }
- return written, err
- }
|