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