blob: 95520fe318dd36c9c509ff5698e06ffd647dc3da [file] [log] [blame]
Colin Cross2fe66872015-03-30 17:20:39 -07001// Copyright 2015 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Jeff Gaston11b5c512017-10-12 12:19:14 -070015package zip
Colin Cross2fe66872015-03-30 17:20:39 -070016
17import (
Dan Willemsen017d8932016-08-04 15:43:03 -070018 "bytes"
19 "compress/flate"
Jeff Gastona2976952017-08-22 17:51:25 -070020 "errors"
Colin Cross2fe66872015-03-30 17:20:39 -070021 "fmt"
Dan Willemsen017d8932016-08-04 15:43:03 -070022 "hash/crc32"
Colin Cross2fe66872015-03-30 17:20:39 -070023 "io"
24 "io/ioutil"
Nan Zhang9067b042017-03-17 14:04:43 -070025 "log"
Colin Cross2fe66872015-03-30 17:20:39 -070026 "os"
27 "path/filepath"
Dan Willemsen017d8932016-08-04 15:43:03 -070028 "runtime/pprof"
29 "runtime/trace"
Jeff Gastona2976952017-08-22 17:51:25 -070030 "sort"
Colin Cross2fe66872015-03-30 17:20:39 -070031 "strings"
Dan Willemsen017d8932016-08-04 15:43:03 -070032 "sync"
Colin Cross2fe66872015-03-30 17:20:39 -070033 "time"
Dan Willemsen017d8932016-08-04 15:43:03 -070034
Jeff Gastona2976952017-08-22 17:51:25 -070035 "android/soong/jar"
Dan Willemsen017d8932016-08-04 15:43:03 -070036 "android/soong/third_party/zip"
Colin Cross2fe66872015-03-30 17:20:39 -070037)
38
Dan Willemsen017d8932016-08-04 15:43:03 -070039// Block size used during parallel compression of a single file.
40const parallelBlockSize = 1 * 1024 * 1024 // 1MB
41
42// Minimum file size to use parallel compression. It requires more
43// flate.Writer allocations, since we can't change the dictionary
44// during Reset
45const minParallelFileSize = parallelBlockSize * 6
46
47// Size of the ZIP compression window (32KB)
48const windowSize = 32 * 1024
49
50type nopCloser struct {
51 io.Writer
52}
53
54func (nopCloser) Close() error {
55 return nil
56}
57
Jeff Gastoncef50b92017-08-23 15:41:35 -070058type byteReaderCloser struct {
Colin Cross635acc92017-09-12 22:50:46 -070059 *bytes.Reader
Jeff Gastoncef50b92017-08-23 15:41:35 -070060 io.Closer
61}
62
Nan Zhang9067b042017-03-17 14:04:43 -070063type pathMapping struct {
64 dest, src string
Nan Zhangf281bd82017-04-25 16:47:45 -070065 zipMethod uint16
66}
67
68type uniqueSet map[string]bool
69
70func (u *uniqueSet) String() string {
71 return `""`
72}
73
74func (u *uniqueSet) Set(s string) error {
75 if _, found := (*u)[s]; found {
76 return fmt.Errorf("File %q was specified twice as a file to not deflate", s)
77 } else {
78 (*u)[s] = true
79 }
80
81 return nil
Colin Cross2fe66872015-03-30 17:20:39 -070082}
83
Jeff Gastonc3bdc972017-10-12 12:18:19 -070084type FileArg struct {
85 PathPrefixInZip, SourcePrefixToStrip string
86 SourceFiles []string
87 GlobDir string
88}
89
90type FileArgs []FileArg
91
92type ZipWriter struct {
Colin Crosse5580972017-08-30 17:40:21 -070093 time time.Time
94 createdFiles map[string]string
95 createdDirs map[string]string
96 directories bool
Colin Crosse19c7932015-04-24 15:08:38 -070097
Dan Willemsen017d8932016-08-04 15:43:03 -070098 errors chan error
99 writeOps chan chan *zipEntry
100
Jeff Gaston175f34c2017-08-17 21:43:21 -0700101 cpuRateLimiter *CPURateLimiter
102 memoryRateLimiter *MemoryRateLimiter
Dan Willemsen017d8932016-08-04 15:43:03 -0700103
104 compressorPool sync.Pool
105 compLevel int
106}
107
108type zipEntry struct {
109 fh *zip.FileHeader
110
111 // List of delayed io.Reader
112 futureReaders chan chan io.Reader
Jeff Gaston175f34c2017-08-17 21:43:21 -0700113
114 // Only used for passing into the MemoryRateLimiter to ensure we
115 // release as much memory as much as we request
116 allocatedSize int64
Colin Cross2fe66872015-03-30 17:20:39 -0700117}
118
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700119type ZipArgs struct {
120 FileArgs FileArgs
121 OutputFilePath string
122 CpuProfileFilePath string
123 TraceFilePath string
124 EmulateJar bool
125 AddDirectoryEntriesToZip bool
126 CompressionLevel int
127 ManifestSourcePath string
128 NumParallelJobs int
129 NonDeflatedFiles map[string]bool
130}
Colin Cross2fe66872015-03-30 17:20:39 -0700131
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700132func Run(args ZipArgs) (err error) {
133 if args.CpuProfileFilePath != "" {
134 f, err := os.Create(args.CpuProfileFilePath)
Dan Willemsen017d8932016-08-04 15:43:03 -0700135 if err != nil {
136 fmt.Fprintln(os.Stderr, err.Error())
137 os.Exit(1)
138 }
139 defer f.Close()
140 pprof.StartCPUProfile(f)
141 defer pprof.StopCPUProfile()
142 }
143
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700144 if args.TraceFilePath != "" {
145 f, err := os.Create(args.TraceFilePath)
Dan Willemsen017d8932016-08-04 15:43:03 -0700146 if err != nil {
147 fmt.Fprintln(os.Stderr, err.Error())
148 os.Exit(1)
149 }
150 defer f.Close()
151 err = trace.Start(f)
152 if err != nil {
153 fmt.Fprintln(os.Stderr, err.Error())
154 os.Exit(1)
155 }
156 defer trace.Stop()
157 }
158
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700159 if args.OutputFilePath == "" {
160 return fmt.Errorf("output file path must be nonempty")
Colin Cross2fe66872015-03-30 17:20:39 -0700161 }
162
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700163 if args.EmulateJar {
164 args.AddDirectoryEntriesToZip = true
Jeff Gaston8edbb3a2017-08-22 20:05:28 -0700165 }
166
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700167 w := &ZipWriter{
Colin Cross635acc92017-09-12 22:50:46 -0700168 time: jar.DefaultTime,
Colin Crosse5580972017-08-30 17:40:21 -0700169 createdDirs: make(map[string]string),
170 createdFiles: make(map[string]string),
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700171 directories: args.AddDirectoryEntriesToZip,
172 compLevel: args.CompressionLevel,
Colin Cross2fe66872015-03-30 17:20:39 -0700173 }
Nan Zhang9067b042017-03-17 14:04:43 -0700174 pathMappings := []pathMapping{}
Nan Zhang9067b042017-03-17 14:04:43 -0700175
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700176 for _, fa := range args.FileArgs {
177 srcs := fa.SourceFiles
178 if fa.GlobDir != "" {
179 srcs = append(srcs, recursiveGlobFiles(fa.GlobDir)...)
Colin Cross7b10cf12017-08-30 14:12:21 -0700180 }
181 for _, src := range srcs {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700182 if err := fillPathPairs(fa.PathPrefixInZip,
183 fa.SourcePrefixToStrip, src, &pathMappings, args.NonDeflatedFiles); err != nil {
Nan Zhang9067b042017-03-17 14:04:43 -0700184 log.Fatal(err)
185 }
186 }
187 }
188
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700189 return w.write(args.OutputFilePath, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.NumParallelJobs)
190
Colin Cross2fe66872015-03-30 17:20:39 -0700191}
192
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700193func fillPathPairs(prefix, rel, src string, pathMappings *[]pathMapping, nonDeflatedFiles map[string]bool) error {
Nan Zhang9067b042017-03-17 14:04:43 -0700194 src = strings.TrimSpace(src)
195 if src == "" {
196 return nil
197 }
198 src = filepath.Clean(src)
199 dest, err := filepath.Rel(rel, src)
200 if err != nil {
201 return err
202 }
203 dest = filepath.Join(prefix, dest)
204
Nan Zhangf281bd82017-04-25 16:47:45 -0700205 zipMethod := zip.Deflate
206 if _, found := nonDeflatedFiles[dest]; found {
207 zipMethod = zip.Store
208 }
209 *pathMappings = append(*pathMappings,
210 pathMapping{dest: dest, src: src, zipMethod: zipMethod})
Nan Zhang9067b042017-03-17 14:04:43 -0700211
212 return nil
213}
214
Jeff Gastona2976952017-08-22 17:51:25 -0700215func jarSort(mappings []pathMapping) {
216 less := func(i int, j int) (smaller bool) {
217 return jar.EntryNamesLess(mappings[i].dest, mappings[j].dest)
218 }
219 sort.SliceStable(mappings, less)
220}
221
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700222type readerSeekerCloser interface {
223 io.Reader
224 io.ReaderAt
225 io.Closer
226 io.Seeker
227}
228
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700229func (z *ZipWriter) write(out string, pathMappings []pathMapping, manifest string, emulateJar bool, parallelJobs int) error {
Colin Cross2fe66872015-03-30 17:20:39 -0700230 f, err := os.Create(out)
231 if err != nil {
232 return err
233 }
234
235 defer f.Close()
236 defer func() {
237 if err != nil {
238 os.Remove(out)
239 }
240 }()
241
Dan Willemsen017d8932016-08-04 15:43:03 -0700242 z.errors = make(chan error)
243 defer close(z.errors)
Colin Cross2fe66872015-03-30 17:20:39 -0700244
Dan Willemsen017d8932016-08-04 15:43:03 -0700245 // This channel size can be essentially unlimited -- it's used as a fifo
246 // queue decouple the CPU and IO loads. Directories don't require any
247 // compression time, but still cost some IO. Similar with small files that
248 // can be very fast to compress. Some files that are more difficult to
249 // compress won't take a corresponding longer time writing out.
250 //
251 // The optimum size here depends on your CPU and IO characteristics, and
252 // the the layout of your zip file. 1000 was chosen mostly at random as
253 // something that worked reasonably well for a test file.
254 //
255 // The RateLimit object will put the upper bounds on the number of
256 // parallel compressions and outstanding buffers.
257 z.writeOps = make(chan chan *zipEntry, 1000)
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700258 z.cpuRateLimiter = NewCPURateLimiter(int64(parallelJobs))
Jeff Gaston175f34c2017-08-17 21:43:21 -0700259 z.memoryRateLimiter = NewMemoryRateLimiter(0)
260 defer func() {
261 z.cpuRateLimiter.Stop()
262 z.memoryRateLimiter.Stop()
263 }()
Jeff Gastona2976952017-08-22 17:51:25 -0700264
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700265 if manifest != "" && !emulateJar {
Colin Cross635acc92017-09-12 22:50:46 -0700266 return errors.New("must specify --jar when specifying a manifest via -m")
Jeff Gastona2976952017-08-22 17:51:25 -0700267 }
268
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700269 if emulateJar {
Colin Cross635acc92017-09-12 22:50:46 -0700270 // manifest may be empty, in which case addManifest will fill in a default
271 pathMappings = append(pathMappings, pathMapping{jar.ManifestFile, manifest, zip.Deflate})
272
Jeff Gastona2976952017-08-22 17:51:25 -0700273 jarSort(pathMappings)
274 }
275
Dan Willemsen017d8932016-08-04 15:43:03 -0700276 go func() {
277 var err error
278 defer close(z.writeOps)
279
Nan Zhang9067b042017-03-17 14:04:43 -0700280 for _, ele := range pathMappings {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700281 if emulateJar && ele.dest == jar.ManifestFile {
Jeff Gastoncef50b92017-08-23 15:41:35 -0700282 err = z.addManifest(ele.dest, ele.src, ele.zipMethod)
283 } else {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700284 err = z.addFile(ele.dest, ele.src, ele.zipMethod, emulateJar)
Jeff Gastoncef50b92017-08-23 15:41:35 -0700285 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700286 if err != nil {
287 z.errors <- err
288 return
289 }
290 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700291 }()
292
293 zipw := zip.NewWriter(f)
294
295 var currentWriteOpChan chan *zipEntry
296 var currentWriter io.WriteCloser
297 var currentReaders chan chan io.Reader
298 var currentReader chan io.Reader
299 var done bool
300
301 for !done {
302 var writeOpsChan chan chan *zipEntry
303 var writeOpChan chan *zipEntry
304 var readersChan chan chan io.Reader
305
306 if currentReader != nil {
307 // Only read and process errors
308 } else if currentReaders != nil {
309 readersChan = currentReaders
310 } else if currentWriteOpChan != nil {
311 writeOpChan = currentWriteOpChan
312 } else {
313 writeOpsChan = z.writeOps
314 }
315
316 select {
317 case writeOp, ok := <-writeOpsChan:
318 if !ok {
319 done = true
320 }
321
322 currentWriteOpChan = writeOp
323
324 case op := <-writeOpChan:
325 currentWriteOpChan = nil
326
327 if op.fh.Method == zip.Deflate {
328 currentWriter, err = zipw.CreateCompressedHeader(op.fh)
329 } else {
330 var zw io.Writer
Jeff Gastonc5eb66d2017-08-24 14:11:27 -0700331
332 op.fh.CompressedSize64 = op.fh.UncompressedSize64
333
334 zw, err = zipw.CreateHeaderAndroid(op.fh)
Dan Willemsen017d8932016-08-04 15:43:03 -0700335 currentWriter = nopCloser{zw}
336 }
337 if err != nil {
338 return err
339 }
340
341 currentReaders = op.futureReaders
342 if op.futureReaders == nil {
343 currentWriter.Close()
344 currentWriter = nil
345 }
Jeff Gaston175f34c2017-08-17 21:43:21 -0700346 z.memoryRateLimiter.Finish(op.allocatedSize)
Dan Willemsen017d8932016-08-04 15:43:03 -0700347
348 case futureReader, ok := <-readersChan:
349 if !ok {
350 // Done with reading
351 currentWriter.Close()
352 currentWriter = nil
353 currentReaders = nil
354 }
355
356 currentReader = futureReader
357
358 case reader := <-currentReader:
Jeff Gaston175f34c2017-08-17 21:43:21 -0700359 _, err = io.Copy(currentWriter, reader)
Dan Willemsen017d8932016-08-04 15:43:03 -0700360 if err != nil {
361 return err
362 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700363
364 currentReader = nil
365
366 case err = <-z.errors:
Colin Cross2fe66872015-03-30 17:20:39 -0700367 return err
368 }
369 }
370
Dan Willemsen017d8932016-08-04 15:43:03 -0700371 // One last chance to catch an error
372 select {
373 case err = <-z.errors:
374 return err
375 default:
376 zipw.Close()
377 return nil
Colin Cross2fe66872015-03-30 17:20:39 -0700378 }
Colin Cross2fe66872015-03-30 17:20:39 -0700379}
380
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700381// imports (possibly with compression) <src> into the zip at sub-path <dest>
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700382func (z *ZipWriter) addFile(dest, src string, method uint16, emulateJar bool) error {
Dan Willemsen017d8932016-08-04 15:43:03 -0700383 var fileSize int64
Dan Willemsen10462b32017-03-15 19:02:51 -0700384 var executable bool
Dan Willemsen017d8932016-08-04 15:43:03 -0700385
Nan Zhang9067b042017-03-17 14:04:43 -0700386 if s, err := os.Lstat(src); err != nil {
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700387 return err
388 } else if s.IsDir() {
Colin Cross957cc4e2015-04-24 15:10:32 -0700389 if z.directories {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700390 return z.writeDirectory(dest, src, emulateJar)
Colin Cross957cc4e2015-04-24 15:10:32 -0700391 }
392 return nil
Dan Willemsen017d8932016-08-04 15:43:03 -0700393 } else {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700394 if err := z.writeDirectory(filepath.Dir(dest), src, emulateJar); err != nil {
Colin Crosse5580972017-08-30 17:40:21 -0700395 return err
396 }
397
398 if prev, exists := z.createdDirs[dest]; exists {
399 return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
400 }
401 if prev, exists := z.createdFiles[dest]; exists {
402 return fmt.Errorf("destination %q has two files %q and %q", dest, prev, src)
403 }
404
405 z.createdFiles[dest] = src
406
407 if s.Mode()&os.ModeSymlink != 0 {
408 return z.writeSymlink(dest, src)
409 } else if !s.Mode().IsRegular() {
410 return fmt.Errorf("%s is not a file, directory, or symlink", src)
411 }
412
Dan Willemsen017d8932016-08-04 15:43:03 -0700413 fileSize = s.Size()
Dan Willemsen10462b32017-03-15 19:02:51 -0700414 executable = s.Mode()&0100 != 0
Colin Cross957cc4e2015-04-24 15:10:32 -0700415 }
416
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700417 r, err := os.Open(src)
418 if err != nil {
419 return err
420 }
421
422 header := &zip.FileHeader{
423 Name: dest,
424 Method: method,
425 UncompressedSize64: uint64(fileSize),
426 }
427
428 if executable {
429 header.SetMode(0700)
430 }
431
432 return z.writeFileContents(header, r)
433}
434
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700435func (z *ZipWriter) addManifest(dest string, src string, method uint16) error {
Colin Crosse5580972017-08-30 17:40:21 -0700436 if prev, exists := z.createdDirs[dest]; exists {
437 return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
438 }
439 if prev, exists := z.createdFiles[dest]; exists {
440 return fmt.Errorf("destination %q has two files %q and %q", dest, prev, src)
441 }
442
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700443 if err := z.writeDirectory(filepath.Dir(dest), src, true); err != nil {
Colin Cross635acc92017-09-12 22:50:46 -0700444 return err
Jeff Gastoncef50b92017-08-23 15:41:35 -0700445 }
446
Colin Cross635acc92017-09-12 22:50:46 -0700447 fh, buf, err := jar.ManifestFileContents(src)
448 if err != nil {
449 return err
Jeff Gastoncef50b92017-08-23 15:41:35 -0700450 }
451
Colin Cross635acc92017-09-12 22:50:46 -0700452 reader := &byteReaderCloser{bytes.NewReader(buf), ioutil.NopCloser(nil)}
453
454 return z.writeFileContents(fh, reader)
Jeff Gastoncef50b92017-08-23 15:41:35 -0700455}
456
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700457func (z *ZipWriter) writeFileContents(header *zip.FileHeader, r readerSeekerCloser) (err error) {
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700458
459 header.SetModTime(z.time)
460
Dan Willemsen017d8932016-08-04 15:43:03 -0700461 compressChan := make(chan *zipEntry, 1)
462 z.writeOps <- compressChan
463
464 // Pre-fill a zipEntry, it will be sent in the compressChan once
465 // we're sure about the Method and CRC.
466 ze := &zipEntry{
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700467 fh: header,
Dan Willemsen10462b32017-03-15 19:02:51 -0700468 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700469
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700470 ze.allocatedSize = int64(header.UncompressedSize64)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700471 z.cpuRateLimiter.Request()
472 z.memoryRateLimiter.Request(ze.allocatedSize)
Dan Willemsen017d8932016-08-04 15:43:03 -0700473
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700474 fileSize := int64(header.UncompressedSize64)
475 if fileSize == 0 {
476 fileSize = int64(header.UncompressedSize)
477 }
478
479 if header.Method == zip.Deflate && fileSize >= minParallelFileSize {
Dan Willemsen017d8932016-08-04 15:43:03 -0700480 wg := new(sync.WaitGroup)
481
482 // Allocate enough buffer to hold all readers. We'll limit
483 // this based on actual buffer sizes in RateLimit.
484 ze.futureReaders = make(chan chan io.Reader, (fileSize/parallelBlockSize)+1)
485
486 // Calculate the CRC in the background, since reading the entire
487 // file could take a while.
488 //
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700489 // We could split this up into chunks as well, but it's faster
Dan Willemsen017d8932016-08-04 15:43:03 -0700490 // than the compression. Due to the Go Zip API, we also need to
491 // know the result before we can begin writing the compressed
492 // data out to the zipfile.
493 wg.Add(1)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700494 go z.crcFile(r, ze, compressChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700495
496 for start := int64(0); start < fileSize; start += parallelBlockSize {
497 sr := io.NewSectionReader(r, start, parallelBlockSize)
498 resultChan := make(chan io.Reader, 1)
499 ze.futureReaders <- resultChan
500
Jeff Gaston175f34c2017-08-17 21:43:21 -0700501 z.cpuRateLimiter.Request()
Dan Willemsen017d8932016-08-04 15:43:03 -0700502
503 last := !(start+parallelBlockSize < fileSize)
504 var dict []byte
505 if start >= windowSize {
506 dict, err = ioutil.ReadAll(io.NewSectionReader(r, start-windowSize, windowSize))
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700507 if err != nil {
508 return err
509 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700510 }
511
512 wg.Add(1)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700513 go z.compressPartialFile(sr, dict, last, resultChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700514 }
515
516 close(ze.futureReaders)
517
518 // Close the file handle after all readers are done
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700519 go func(wg *sync.WaitGroup, closer io.Closer) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700520 wg.Wait()
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700521 closer.Close()
Dan Willemsen017d8932016-08-04 15:43:03 -0700522 }(wg, r)
523 } else {
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700524 go func() {
525 z.compressWholeFile(ze, r, compressChan)
526 r.Close()
527 }()
Dan Willemsen017d8932016-08-04 15:43:03 -0700528 }
529
530 return nil
531}
532
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700533func (z *ZipWriter) crcFile(r io.Reader, ze *zipEntry, resultChan chan *zipEntry, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700534 defer wg.Done()
Jeff Gaston175f34c2017-08-17 21:43:21 -0700535 defer z.cpuRateLimiter.Finish()
Dan Willemsen017d8932016-08-04 15:43:03 -0700536
537 crc := crc32.NewIEEE()
538 _, err := io.Copy(crc, r)
539 if err != nil {
540 z.errors <- err
541 return
542 }
543
544 ze.fh.CRC32 = crc.Sum32()
545 resultChan <- ze
546 close(resultChan)
547}
548
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700549func (z *ZipWriter) compressPartialFile(r io.Reader, dict []byte, last bool, resultChan chan io.Reader, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700550 defer wg.Done()
551
552 result, err := z.compressBlock(r, dict, last)
553 if err != nil {
554 z.errors <- err
555 return
556 }
557
Jeff Gaston175f34c2017-08-17 21:43:21 -0700558 z.cpuRateLimiter.Finish()
559
Dan Willemsen017d8932016-08-04 15:43:03 -0700560 resultChan <- result
561}
562
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700563func (z *ZipWriter) compressBlock(r io.Reader, dict []byte, last bool) (*bytes.Buffer, error) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700564 buf := new(bytes.Buffer)
565 var fw *flate.Writer
566 var err error
567 if len(dict) > 0 {
568 // There's no way to Reset a Writer with a new dictionary, so
569 // don't use the Pool
570 fw, err = flate.NewWriterDict(buf, z.compLevel, dict)
571 } else {
572 var ok bool
573 if fw, ok = z.compressorPool.Get().(*flate.Writer); ok {
574 fw.Reset(buf)
575 } else {
576 fw, err = flate.NewWriter(buf, z.compLevel)
577 }
578 defer z.compressorPool.Put(fw)
579 }
580 if err != nil {
581 return nil, err
582 }
583
584 _, err = io.Copy(fw, r)
585 if err != nil {
586 return nil, err
587 }
588 if last {
589 fw.Close()
590 } else {
591 fw.Flush()
592 }
593
594 return buf, nil
595}
596
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700597func (z *ZipWriter) compressWholeFile(ze *zipEntry, r io.ReadSeeker, compressChan chan *zipEntry) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700598
Dan Willemsen017d8932016-08-04 15:43:03 -0700599 crc := crc32.NewIEEE()
Dan Willemsena8b55022017-03-15 21:49:26 -0700600 _, err := io.Copy(crc, r)
Colin Cross2fe66872015-03-30 17:20:39 -0700601 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700602 z.errors <- err
603 return
Colin Cross2fe66872015-03-30 17:20:39 -0700604 }
605
Dan Willemsena8b55022017-03-15 21:49:26 -0700606 ze.fh.CRC32 = crc.Sum32()
Colin Cross2fe66872015-03-30 17:20:39 -0700607
Dan Willemsen017d8932016-08-04 15:43:03 -0700608 _, err = r.Seek(0, 0)
Colin Cross2fe66872015-03-30 17:20:39 -0700609 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700610 z.errors <- err
611 return
Colin Cross2fe66872015-03-30 17:20:39 -0700612 }
613
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700614 readFile := func(reader io.ReadSeeker) ([]byte, error) {
615 _, err := reader.Seek(0, 0)
Nan Zhangf281bd82017-04-25 16:47:45 -0700616 if err != nil {
617 return nil, err
618 }
619
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700620 buf, err := ioutil.ReadAll(reader)
Nan Zhangf281bd82017-04-25 16:47:45 -0700621 if err != nil {
622 return nil, err
623 }
624
625 return buf, nil
626 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700627
Dan Willemsena8b55022017-03-15 21:49:26 -0700628 ze.futureReaders = make(chan chan io.Reader, 1)
Dan Willemsen017d8932016-08-04 15:43:03 -0700629 futureReader := make(chan io.Reader, 1)
630 ze.futureReaders <- futureReader
631 close(ze.futureReaders)
632
Nan Zhangf281bd82017-04-25 16:47:45 -0700633 if ze.fh.Method == zip.Deflate {
634 compressed, err := z.compressBlock(r, nil, true)
635 if err != nil {
636 z.errors <- err
637 return
638 }
639 if uint64(compressed.Len()) < ze.fh.UncompressedSize64 {
640 futureReader <- compressed
Nan Zhangf281bd82017-04-25 16:47:45 -0700641 } else {
642 buf, err := readFile(r)
643 if err != nil {
644 z.errors <- err
645 return
646 }
647 ze.fh.Method = zip.Store
648 futureReader <- bytes.NewReader(buf)
Nan Zhangf281bd82017-04-25 16:47:45 -0700649 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700650 } else {
Nan Zhangf281bd82017-04-25 16:47:45 -0700651 buf, err := readFile(r)
Dan Willemsen017d8932016-08-04 15:43:03 -0700652 if err != nil {
653 z.errors <- err
654 return
655 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700656 ze.fh.Method = zip.Store
657 futureReader <- bytes.NewReader(buf)
Dan Willemsen017d8932016-08-04 15:43:03 -0700658 }
Nan Zhangf281bd82017-04-25 16:47:45 -0700659
Jeff Gaston175f34c2017-08-17 21:43:21 -0700660 z.cpuRateLimiter.Finish()
661
Dan Willemsen017d8932016-08-04 15:43:03 -0700662 close(futureReader)
663
664 compressChan <- ze
665 close(compressChan)
Colin Cross2fe66872015-03-30 17:20:39 -0700666}
Colin Crosse19c7932015-04-24 15:08:38 -0700667
Colin Crosse5580972017-08-30 17:40:21 -0700668// writeDirectory annotates that dir is a directory created for the src file or directory, and adds
669// the directory entry to the zip file if directories are enabled.
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700670func (z *ZipWriter) writeDirectory(dir string, src string, emulateJar bool) error {
Jeff Gaston2d174132017-08-15 18:05:56 -0700671 // clean the input
Colin Crosse5580972017-08-30 17:40:21 -0700672 dir = filepath.Clean(dir)
Jeff Gaston2d174132017-08-15 18:05:56 -0700673
674 // discover any uncreated directories in the path
675 zipDirs := []string{}
Colin Crosse5580972017-08-30 17:40:21 -0700676 for dir != "" && dir != "." {
677 if _, exists := z.createdDirs[dir]; exists {
678 break
679 }
Jeff Gaston2d174132017-08-15 18:05:56 -0700680
Colin Crosse5580972017-08-30 17:40:21 -0700681 if prev, exists := z.createdFiles[dir]; exists {
682 return fmt.Errorf("destination %q is both a directory %q and a file %q", dir, src, prev)
683 }
684
685 z.createdDirs[dir] = src
Jeff Gaston2d174132017-08-15 18:05:56 -0700686 // parent directories precede their children
Colin Crosse5580972017-08-30 17:40:21 -0700687 zipDirs = append([]string{dir}, zipDirs...)
Jeff Gaston2d174132017-08-15 18:05:56 -0700688
Colin Crosse5580972017-08-30 17:40:21 -0700689 dir = filepath.Dir(dir)
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700690 }
691
Colin Crosse5580972017-08-30 17:40:21 -0700692 if z.directories {
693 // make a directory entry for each uncreated directory
694 for _, cleanDir := range zipDirs {
Colin Cross635acc92017-09-12 22:50:46 -0700695 var dirHeader *zip.FileHeader
Colin Crosse19c7932015-04-24 15:08:38 -0700696
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700697 if emulateJar && cleanDir+"/" == jar.MetaDir {
Colin Cross635acc92017-09-12 22:50:46 -0700698 dirHeader = jar.MetaDirFileHeader()
699 } else {
700 dirHeader = &zip.FileHeader{
701 Name: cleanDir + "/",
702 }
703 dirHeader.SetMode(0700 | os.ModeDir)
Colin Crosse5580972017-08-30 17:40:21 -0700704 }
Jeff Gaston8edbb3a2017-08-22 20:05:28 -0700705
Colin Cross635acc92017-09-12 22:50:46 -0700706 dirHeader.SetModTime(z.time)
707
Colin Crosse5580972017-08-30 17:40:21 -0700708 ze := make(chan *zipEntry, 1)
709 ze <- &zipEntry{
710 fh: dirHeader,
711 }
712 close(ze)
713 z.writeOps <- ze
Colin Crosse19c7932015-04-24 15:08:38 -0700714 }
Colin Crosse19c7932015-04-24 15:08:38 -0700715 }
716
717 return nil
718}
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700719
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700720func (z *ZipWriter) writeSymlink(rel, file string) error {
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700721 fileHeader := &zip.FileHeader{
722 Name: rel,
723 }
724 fileHeader.SetModTime(z.time)
725 fileHeader.SetMode(0700 | os.ModeSymlink)
726
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700727 dest, err := os.Readlink(file)
728 if err != nil {
729 return err
730 }
731
Dan Willemsen017d8932016-08-04 15:43:03 -0700732 ze := make(chan *zipEntry, 1)
733 futureReaders := make(chan chan io.Reader, 1)
734 futureReader := make(chan io.Reader, 1)
735 futureReaders <- futureReader
736 close(futureReaders)
737 futureReader <- bytes.NewBufferString(dest)
738 close(futureReader)
739
Dan Willemsen017d8932016-08-04 15:43:03 -0700740 ze <- &zipEntry{
741 fh: fileHeader,
742 futureReaders: futureReaders,
743 }
744 close(ze)
745 z.writeOps <- ze
746
747 return nil
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700748}
Colin Cross7b10cf12017-08-30 14:12:21 -0700749
750func recursiveGlobFiles(path string) []string {
751 var files []string
752 filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
753 if !info.IsDir() {
754 files = append(files, path)
755 }
756 return nil
757 })
758
759 return files
760}