github.com/fcwu/docker@v1.4.2-0.20150115145920-2a69ca89f0df/pkg/tarsum/tarsum.go (about)

     1  package tarsum
     2  
     3  import (
     4  	"bytes"
     5  	"compress/gzip"
     6  	"crypto/sha256"
     7  	"encoding/hex"
     8  	"hash"
     9  	"io"
    10  	"strings"
    11  
    12  	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
    13  )
    14  
    15  const (
    16  	buf8K  = 8 * 1024
    17  	buf16K = 16 * 1024
    18  	buf32K = 32 * 1024
    19  )
    20  
    21  // NewTarSum creates a new interface for calculating a fixed time checksum of a
    22  // tar archive.
    23  //
    24  // This is used for calculating checksums of layers of an image, in some cases
    25  // including the byte payload of the image's json metadata as well, and for
    26  // calculating the checksums for buildcache.
    27  func NewTarSum(r io.Reader, dc bool, v Version) (TarSum, error) {
    28  	return NewTarSumHash(r, dc, v, DefaultTHash)
    29  }
    30  
    31  // Create a new TarSum, providing a THash to use rather than the DefaultTHash
    32  func NewTarSumHash(r io.Reader, dc bool, v Version, tHash THash) (TarSum, error) {
    33  	headerSelector, err := getTarHeaderSelector(v)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	ts := &tarSum{Reader: r, DisableCompression: dc, tarSumVersion: v, headerSelector: headerSelector, tHash: tHash}
    38  	err = ts.initTarSum()
    39  	return ts, err
    40  }
    41  
    42  // TarSum is the generic interface for calculating fixed time
    43  // checksums of a tar archive
    44  type TarSum interface {
    45  	io.Reader
    46  	GetSums() FileInfoSums
    47  	Sum([]byte) string
    48  	Version() Version
    49  	Hash() THash
    50  }
    51  
    52  // tarSum struct is the structure for a Version0 checksum calculation
    53  type tarSum struct {
    54  	io.Reader
    55  	tarR               *tar.Reader
    56  	tarW               *tar.Writer
    57  	writer             writeCloseFlusher
    58  	bufTar             *bytes.Buffer
    59  	bufWriter          *bytes.Buffer
    60  	bufData            []byte
    61  	h                  hash.Hash
    62  	tHash              THash
    63  	sums               FileInfoSums
    64  	fileCounter        int64
    65  	currentFile        string
    66  	finished           bool
    67  	first              bool
    68  	DisableCompression bool              // false by default. When false, the output gzip compressed.
    69  	tarSumVersion      Version           // this field is not exported so it can not be mutated during use
    70  	headerSelector     tarHeaderSelector // handles selecting and ordering headers for files in the archive
    71  }
    72  
    73  func (ts tarSum) Hash() THash {
    74  	return ts.tHash
    75  }
    76  
    77  func (ts tarSum) Version() Version {
    78  	return ts.tarSumVersion
    79  }
    80  
    81  // A hash.Hash type generator and its name
    82  type THash interface {
    83  	Hash() hash.Hash
    84  	Name() string
    85  }
    86  
    87  // Convenience method for creating a THash
    88  func NewTHash(name string, h func() hash.Hash) THash {
    89  	return simpleTHash{n: name, h: h}
    90  }
    91  
    92  // TarSum default is "sha256"
    93  var DefaultTHash = NewTHash("sha256", sha256.New)
    94  
    95  type simpleTHash struct {
    96  	n string
    97  	h func() hash.Hash
    98  }
    99  
   100  func (sth simpleTHash) Name() string    { return sth.n }
   101  func (sth simpleTHash) Hash() hash.Hash { return sth.h() }
   102  
   103  func (ts *tarSum) encodeHeader(h *tar.Header) error {
   104  	for _, elem := range ts.headerSelector.selectHeaders(h) {
   105  		if _, err := ts.h.Write([]byte(elem[0] + elem[1])); err != nil {
   106  			return err
   107  		}
   108  	}
   109  	return nil
   110  }
   111  
   112  func (ts *tarSum) initTarSum() error {
   113  	ts.bufTar = bytes.NewBuffer([]byte{})
   114  	ts.bufWriter = bytes.NewBuffer([]byte{})
   115  	ts.tarR = tar.NewReader(ts.Reader)
   116  	ts.tarW = tar.NewWriter(ts.bufTar)
   117  	if !ts.DisableCompression {
   118  		ts.writer = gzip.NewWriter(ts.bufWriter)
   119  	} else {
   120  		ts.writer = &nopCloseFlusher{Writer: ts.bufWriter}
   121  	}
   122  	if ts.tHash == nil {
   123  		ts.tHash = DefaultTHash
   124  	}
   125  	ts.h = ts.tHash.Hash()
   126  	ts.h.Reset()
   127  	ts.first = true
   128  	ts.sums = FileInfoSums{}
   129  	return nil
   130  }
   131  
   132  func (ts *tarSum) Read(buf []byte) (int, error) {
   133  	if ts.finished {
   134  		return ts.bufWriter.Read(buf)
   135  	}
   136  	if len(ts.bufData) < len(buf) {
   137  		switch {
   138  		case len(buf) <= buf8K:
   139  			ts.bufData = make([]byte, buf8K)
   140  		case len(buf) <= buf16K:
   141  			ts.bufData = make([]byte, buf16K)
   142  		case len(buf) <= buf32K:
   143  			ts.bufData = make([]byte, buf32K)
   144  		default:
   145  			ts.bufData = make([]byte, len(buf))
   146  		}
   147  	}
   148  	buf2 := ts.bufData[:len(buf)]
   149  
   150  	n, err := ts.tarR.Read(buf2)
   151  	if err != nil {
   152  		if err == io.EOF {
   153  			if _, err := ts.h.Write(buf2[:n]); err != nil {
   154  				return 0, err
   155  			}
   156  			if !ts.first {
   157  				ts.sums = append(ts.sums, fileInfoSum{name: ts.currentFile, sum: hex.EncodeToString(ts.h.Sum(nil)), pos: ts.fileCounter})
   158  				ts.fileCounter++
   159  				ts.h.Reset()
   160  			} else {
   161  				ts.first = false
   162  			}
   163  
   164  			currentHeader, err := ts.tarR.Next()
   165  			if err != nil {
   166  				if err == io.EOF {
   167  					if err := ts.tarW.Close(); err != nil {
   168  						return 0, err
   169  					}
   170  					if _, err := io.Copy(ts.writer, ts.bufTar); err != nil {
   171  						return 0, err
   172  					}
   173  					if err := ts.writer.Close(); err != nil {
   174  						return 0, err
   175  					}
   176  					ts.finished = true
   177  					return n, nil
   178  				}
   179  				return n, err
   180  			}
   181  			ts.currentFile = strings.TrimSuffix(strings.TrimPrefix(currentHeader.Name, "./"), "/")
   182  			if err := ts.encodeHeader(currentHeader); err != nil {
   183  				return 0, err
   184  			}
   185  			if err := ts.tarW.WriteHeader(currentHeader); err != nil {
   186  				return 0, err
   187  			}
   188  			if _, err := ts.tarW.Write(buf2[:n]); err != nil {
   189  				return 0, err
   190  			}
   191  			ts.tarW.Flush()
   192  			if _, err := io.Copy(ts.writer, ts.bufTar); err != nil {
   193  				return 0, err
   194  			}
   195  			ts.writer.Flush()
   196  
   197  			return ts.bufWriter.Read(buf)
   198  		}
   199  		return n, err
   200  	}
   201  
   202  	// Filling the hash buffer
   203  	if _, err = ts.h.Write(buf2[:n]); err != nil {
   204  		return 0, err
   205  	}
   206  
   207  	// Filling the tar writter
   208  	if _, err = ts.tarW.Write(buf2[:n]); err != nil {
   209  		return 0, err
   210  	}
   211  	ts.tarW.Flush()
   212  
   213  	// Filling the output writer
   214  	if _, err = io.Copy(ts.writer, ts.bufTar); err != nil {
   215  		return 0, err
   216  	}
   217  	ts.writer.Flush()
   218  
   219  	return ts.bufWriter.Read(buf)
   220  }
   221  
   222  func (ts *tarSum) Sum(extra []byte) string {
   223  	ts.sums.SortBySums()
   224  	h := ts.tHash.Hash()
   225  	if extra != nil {
   226  		h.Write(extra)
   227  	}
   228  	for _, fis := range ts.sums {
   229  		h.Write([]byte(fis.Sum()))
   230  	}
   231  	checksum := ts.Version().String() + "+" + ts.tHash.Name() + ":" + hex.EncodeToString(h.Sum(nil))
   232  	return checksum
   233  }
   234  
   235  func (ts *tarSum) GetSums() FileInfoSums {
   236  	return ts.sums
   237  }