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 }