blob: 80173f3d4e625264753d1984596543de0b4d9cd1 [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"
Nan Zhang674dd932018-01-26 18:30:36 -080034 "unicode"
Dan Willemsen017d8932016-08-04 15:43:03 -070035
Colin Crossf83c1502017-11-10 13:11:02 -080036 "github.com/google/blueprint/pathtools"
37
Jeff Gastona2976952017-08-22 17:51:25 -070038 "android/soong/jar"
Dan Willemsen017d8932016-08-04 15:43:03 -070039 "android/soong/third_party/zip"
Colin Cross2fe66872015-03-30 17:20:39 -070040)
41
Dan Willemsen017d8932016-08-04 15:43:03 -070042// Block size used during parallel compression of a single file.
43const parallelBlockSize = 1 * 1024 * 1024 // 1MB
44
45// Minimum file size to use parallel compression. It requires more
46// flate.Writer allocations, since we can't change the dictionary
47// during Reset
48const minParallelFileSize = parallelBlockSize * 6
49
50// Size of the ZIP compression window (32KB)
51const windowSize = 32 * 1024
52
53type nopCloser struct {
54 io.Writer
55}
56
57func (nopCloser) Close() error {
58 return nil
59}
60
Jeff Gastoncef50b92017-08-23 15:41:35 -070061type byteReaderCloser struct {
Colin Cross635acc92017-09-12 22:50:46 -070062 *bytes.Reader
Jeff Gastoncef50b92017-08-23 15:41:35 -070063 io.Closer
64}
65
Nan Zhang9067b042017-03-17 14:04:43 -070066type pathMapping struct {
67 dest, src string
Nan Zhangf281bd82017-04-25 16:47:45 -070068 zipMethod uint16
69}
70
Jeff Gastonc3bdc972017-10-12 12:18:19 -070071type FileArg struct {
72 PathPrefixInZip, SourcePrefixToStrip string
73 SourceFiles []string
Colin Crossb7c69112018-09-18 16:51:43 -070074 JunkPaths bool
Jeff Gastonc3bdc972017-10-12 12:18:19 -070075 GlobDir string
76}
77
Colin Crossfe945b42018-09-27 15:00:07 -070078type FileArgsBuilder struct {
79 state FileArg
80 err error
81 fs pathtools.FileSystem
82
83 fileArgs []FileArg
84}
85
86func NewFileArgsBuilder() *FileArgsBuilder {
87 return &FileArgsBuilder{
88 fs: pathtools.OsFs,
89 }
90}
91
92func (b *FileArgsBuilder) JunkPaths(v bool) *FileArgsBuilder {
93 b.state.JunkPaths = v
94 b.state.SourcePrefixToStrip = ""
95 return b
96}
97
98func (b *FileArgsBuilder) SourcePrefixToStrip(prefixToStrip string) *FileArgsBuilder {
99 b.state.JunkPaths = false
100 b.state.SourcePrefixToStrip = prefixToStrip
101 return b
102}
103
104func (b *FileArgsBuilder) PathPrefixInZip(rootPrefix string) *FileArgsBuilder {
105 b.state.PathPrefixInZip = rootPrefix
106 return b
107}
108
109func (b *FileArgsBuilder) File(name string) *FileArgsBuilder {
110 if b.err != nil {
111 return b
112 }
113
114 arg := b.state
115 arg.SourceFiles = []string{name}
116 b.fileArgs = append(b.fileArgs, arg)
117 return b
118}
119
120func (b *FileArgsBuilder) Dir(name string) *FileArgsBuilder {
121 if b.err != nil {
122 return b
123 }
124
125 arg := b.state
126 arg.GlobDir = name
127 b.fileArgs = append(b.fileArgs, arg)
128 return b
129}
130
131func (b *FileArgsBuilder) List(name string) *FileArgsBuilder {
132 if b.err != nil {
133 return b
134 }
135
136 f, err := b.fs.Open(name)
137 if err != nil {
138 b.err = err
139 return b
140 }
141 defer f.Close()
142
143 list, err := ioutil.ReadAll(f)
144 if err != nil {
145 b.err = err
146 return b
147 }
148
149 arg := b.state
150 arg.SourceFiles = strings.Split(string(list), "\n")
151 b.fileArgs = append(b.fileArgs, arg)
152 return b
153}
154
155func (b *FileArgsBuilder) Error() error {
156 if b == nil {
157 return nil
158 }
159 return b.err
160}
161
162func (b *FileArgsBuilder) FileArgs() []FileArg {
163 if b == nil {
164 return nil
165 }
166 return b.fileArgs
167}
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700168
169type ZipWriter struct {
Colin Crosse5580972017-08-30 17:40:21 -0700170 time time.Time
171 createdFiles map[string]string
172 createdDirs map[string]string
173 directories bool
Colin Crosse19c7932015-04-24 15:08:38 -0700174
Dan Willemsen017d8932016-08-04 15:43:03 -0700175 errors chan error
176 writeOps chan chan *zipEntry
177
Jeff Gaston175f34c2017-08-17 21:43:21 -0700178 cpuRateLimiter *CPURateLimiter
179 memoryRateLimiter *MemoryRateLimiter
Dan Willemsen017d8932016-08-04 15:43:03 -0700180
181 compressorPool sync.Pool
182 compLevel int
183}
184
185type zipEntry struct {
186 fh *zip.FileHeader
187
188 // List of delayed io.Reader
189 futureReaders chan chan io.Reader
Jeff Gaston175f34c2017-08-17 21:43:21 -0700190
191 // Only used for passing into the MemoryRateLimiter to ensure we
192 // release as much memory as much as we request
193 allocatedSize int64
Colin Cross2fe66872015-03-30 17:20:39 -0700194}
195
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700196type ZipArgs struct {
Colin Crossfe945b42018-09-27 15:00:07 -0700197 FileArgs []FileArg
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700198 OutputFilePath string
199 CpuProfileFilePath string
200 TraceFilePath string
201 EmulateJar bool
202 AddDirectoryEntriesToZip bool
203 CompressionLevel int
204 ManifestSourcePath string
205 NumParallelJobs int
206 NonDeflatedFiles map[string]bool
Colin Crossf83c1502017-11-10 13:11:02 -0800207 WriteIfChanged bool
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700208}
Colin Cross2fe66872015-03-30 17:20:39 -0700209
Nan Zhang674dd932018-01-26 18:30:36 -0800210const NOQUOTE = '\x00'
211
212func ReadRespFile(bytes []byte) []string {
213 var args []string
214 var arg []rune
215
216 isEscaping := false
217 quotingStart := NOQUOTE
218 for _, c := range string(bytes) {
219 switch {
220 case isEscaping:
221 if quotingStart == '"' {
222 if !(c == '"' || c == '\\') {
223 // '\"' or '\\' will be escaped under double quoting.
224 arg = append(arg, '\\')
225 }
226 }
227 arg = append(arg, c)
228 isEscaping = false
229 case c == '\\' && quotingStart != '\'':
230 isEscaping = true
231 case quotingStart == NOQUOTE && (c == '\'' || c == '"'):
232 quotingStart = c
233 case quotingStart != NOQUOTE && c == quotingStart:
234 quotingStart = NOQUOTE
235 case quotingStart == NOQUOTE && unicode.IsSpace(c):
236 // Current character is a space outside quotes
237 if len(arg) != 0 {
238 args = append(args, string(arg))
239 }
240 arg = arg[:0]
241 default:
242 arg = append(arg, c)
243 }
244 }
245
246 if len(arg) != 0 {
247 args = append(args, string(arg))
248 }
249
250 return args
251}
252
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700253func Run(args ZipArgs) (err error) {
254 if args.CpuProfileFilePath != "" {
255 f, err := os.Create(args.CpuProfileFilePath)
Dan Willemsen017d8932016-08-04 15:43:03 -0700256 if err != nil {
257 fmt.Fprintln(os.Stderr, err.Error())
258 os.Exit(1)
259 }
260 defer f.Close()
261 pprof.StartCPUProfile(f)
262 defer pprof.StopCPUProfile()
263 }
264
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700265 if args.TraceFilePath != "" {
266 f, err := os.Create(args.TraceFilePath)
Dan Willemsen017d8932016-08-04 15:43:03 -0700267 if err != nil {
268 fmt.Fprintln(os.Stderr, err.Error())
269 os.Exit(1)
270 }
271 defer f.Close()
272 err = trace.Start(f)
273 if err != nil {
274 fmt.Fprintln(os.Stderr, err.Error())
275 os.Exit(1)
276 }
277 defer trace.Stop()
278 }
279
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700280 if args.OutputFilePath == "" {
281 return fmt.Errorf("output file path must be nonempty")
Colin Cross2fe66872015-03-30 17:20:39 -0700282 }
283
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700284 if args.EmulateJar {
285 args.AddDirectoryEntriesToZip = true
Jeff Gaston8edbb3a2017-08-22 20:05:28 -0700286 }
287
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700288 w := &ZipWriter{
Colin Crossc7feeff2018-09-26 21:36:44 +0000289 time: jar.DefaultTime,
290 createdDirs: make(map[string]string),
291 createdFiles: make(map[string]string),
292 directories: args.AddDirectoryEntriesToZip,
293 compLevel: args.CompressionLevel,
Colin Cross2fe66872015-03-30 17:20:39 -0700294 }
Nan Zhang9067b042017-03-17 14:04:43 -0700295 pathMappings := []pathMapping{}
Nan Zhang9067b042017-03-17 14:04:43 -0700296
Colin Crossd3216292018-09-14 15:06:31 -0700297 noCompression := args.CompressionLevel == 0
298
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700299 for _, fa := range args.FileArgs {
Colin Cross7f33b812018-09-26 21:36:22 +0000300 srcs := fa.SourceFiles
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700301 if fa.GlobDir != "" {
Colin Cross7f33b812018-09-26 21:36:22 +0000302 srcs = append(srcs, recursiveGlobFiles(fa.GlobDir)...)
Colin Cross7b10cf12017-08-30 14:12:21 -0700303 }
304 for _, src := range srcs {
Colin Crossb7c69112018-09-18 16:51:43 -0700305 err := fillPathPairs(fa, src, &pathMappings, args.NonDeflatedFiles, noCompression)
Colin Crossd3216292018-09-14 15:06:31 -0700306 if err != nil {
Nan Zhang9067b042017-03-17 14:04:43 -0700307 log.Fatal(err)
308 }
309 }
310 }
311
Colin Crossf83c1502017-11-10 13:11:02 -0800312 buf := &bytes.Buffer{}
313 var out io.Writer = buf
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700314
Colin Crossf83c1502017-11-10 13:11:02 -0800315 if !args.WriteIfChanged {
316 f, err := os.Create(args.OutputFilePath)
317 if err != nil {
318 return err
319 }
320
321 defer f.Close()
322 defer func() {
323 if err != nil {
324 os.Remove(args.OutputFilePath)
325 }
326 }()
327
328 out = f
329 }
330
331 err = w.write(out, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.NumParallelJobs)
332 if err != nil {
333 return err
334 }
335
336 if args.WriteIfChanged {
337 err := pathtools.WriteFileIfChanged(args.OutputFilePath, buf.Bytes(), 0666)
338 if err != nil {
339 return err
340 }
341 }
342
343 return nil
Colin Cross2fe66872015-03-30 17:20:39 -0700344}
345
Colin Crossb7c69112018-09-18 16:51:43 -0700346func fillPathPairs(fa FileArg, src string, pathMappings *[]pathMapping,
Colin Crossd3216292018-09-14 15:06:31 -0700347 nonDeflatedFiles map[string]bool, noCompression bool) error {
348
Nan Zhang9067b042017-03-17 14:04:43 -0700349 src = strings.TrimSpace(src)
350 if src == "" {
351 return nil
352 }
353 src = filepath.Clean(src)
Colin Crossb7c69112018-09-18 16:51:43 -0700354 var dest string
355
356 if fa.JunkPaths {
357 dest = filepath.Base(src)
358 } else {
359 var err error
360 dest, err = filepath.Rel(fa.SourcePrefixToStrip, src)
361 if err != nil {
362 return err
363 }
Nan Zhang9067b042017-03-17 14:04:43 -0700364 }
Colin Crossb7c69112018-09-18 16:51:43 -0700365 dest = filepath.Join(fa.PathPrefixInZip, dest)
Nan Zhang9067b042017-03-17 14:04:43 -0700366
Nan Zhangf281bd82017-04-25 16:47:45 -0700367 zipMethod := zip.Deflate
Colin Crossd3216292018-09-14 15:06:31 -0700368 if _, found := nonDeflatedFiles[dest]; found || noCompression {
Nan Zhangf281bd82017-04-25 16:47:45 -0700369 zipMethod = zip.Store
370 }
371 *pathMappings = append(*pathMappings,
372 pathMapping{dest: dest, src: src, zipMethod: zipMethod})
Nan Zhang9067b042017-03-17 14:04:43 -0700373
374 return nil
375}
376
Jeff Gastona2976952017-08-22 17:51:25 -0700377func jarSort(mappings []pathMapping) {
378 less := func(i int, j int) (smaller bool) {
379 return jar.EntryNamesLess(mappings[i].dest, mappings[j].dest)
380 }
381 sort.SliceStable(mappings, less)
382}
383
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700384type readerSeekerCloser interface {
385 io.Reader
386 io.ReaderAt
387 io.Closer
388 io.Seeker
389}
390
Colin Crossf83c1502017-11-10 13:11:02 -0800391func (z *ZipWriter) write(f io.Writer, pathMappings []pathMapping, manifest string, emulateJar bool, parallelJobs int) error {
Dan Willemsen017d8932016-08-04 15:43:03 -0700392 z.errors = make(chan error)
393 defer close(z.errors)
Colin Cross2fe66872015-03-30 17:20:39 -0700394
Dan Willemsen017d8932016-08-04 15:43:03 -0700395 // This channel size can be essentially unlimited -- it's used as a fifo
396 // queue decouple the CPU and IO loads. Directories don't require any
397 // compression time, but still cost some IO. Similar with small files that
398 // can be very fast to compress. Some files that are more difficult to
399 // compress won't take a corresponding longer time writing out.
400 //
401 // The optimum size here depends on your CPU and IO characteristics, and
402 // the the layout of your zip file. 1000 was chosen mostly at random as
403 // something that worked reasonably well for a test file.
404 //
405 // The RateLimit object will put the upper bounds on the number of
406 // parallel compressions and outstanding buffers.
407 z.writeOps = make(chan chan *zipEntry, 1000)
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700408 z.cpuRateLimiter = NewCPURateLimiter(int64(parallelJobs))
Jeff Gaston175f34c2017-08-17 21:43:21 -0700409 z.memoryRateLimiter = NewMemoryRateLimiter(0)
410 defer func() {
411 z.cpuRateLimiter.Stop()
412 z.memoryRateLimiter.Stop()
413 }()
Jeff Gastona2976952017-08-22 17:51:25 -0700414
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700415 if manifest != "" && !emulateJar {
Colin Cross635acc92017-09-12 22:50:46 -0700416 return errors.New("must specify --jar when specifying a manifest via -m")
Jeff Gastona2976952017-08-22 17:51:25 -0700417 }
418
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700419 if emulateJar {
Colin Cross635acc92017-09-12 22:50:46 -0700420 // manifest may be empty, in which case addManifest will fill in a default
421 pathMappings = append(pathMappings, pathMapping{jar.ManifestFile, manifest, zip.Deflate})
422
Jeff Gastona2976952017-08-22 17:51:25 -0700423 jarSort(pathMappings)
424 }
425
Dan Willemsen017d8932016-08-04 15:43:03 -0700426 go func() {
427 var err error
428 defer close(z.writeOps)
429
Nan Zhang9067b042017-03-17 14:04:43 -0700430 for _, ele := range pathMappings {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700431 if emulateJar && ele.dest == jar.ManifestFile {
Jeff Gastoncef50b92017-08-23 15:41:35 -0700432 err = z.addManifest(ele.dest, ele.src, ele.zipMethod)
433 } else {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700434 err = z.addFile(ele.dest, ele.src, ele.zipMethod, emulateJar)
Jeff Gastoncef50b92017-08-23 15:41:35 -0700435 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700436 if err != nil {
437 z.errors <- err
438 return
439 }
440 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700441 }()
442
443 zipw := zip.NewWriter(f)
444
445 var currentWriteOpChan chan *zipEntry
446 var currentWriter io.WriteCloser
447 var currentReaders chan chan io.Reader
448 var currentReader chan io.Reader
449 var done bool
450
451 for !done {
452 var writeOpsChan chan chan *zipEntry
453 var writeOpChan chan *zipEntry
454 var readersChan chan chan io.Reader
455
456 if currentReader != nil {
457 // Only read and process errors
458 } else if currentReaders != nil {
459 readersChan = currentReaders
460 } else if currentWriteOpChan != nil {
461 writeOpChan = currentWriteOpChan
462 } else {
463 writeOpsChan = z.writeOps
464 }
465
466 select {
467 case writeOp, ok := <-writeOpsChan:
468 if !ok {
469 done = true
470 }
471
472 currentWriteOpChan = writeOp
473
474 case op := <-writeOpChan:
475 currentWriteOpChan = nil
476
Colin Crossf83c1502017-11-10 13:11:02 -0800477 var err error
Dan Willemsen017d8932016-08-04 15:43:03 -0700478 if op.fh.Method == zip.Deflate {
479 currentWriter, err = zipw.CreateCompressedHeader(op.fh)
480 } else {
481 var zw io.Writer
Jeff Gastonc5eb66d2017-08-24 14:11:27 -0700482
483 op.fh.CompressedSize64 = op.fh.UncompressedSize64
484
485 zw, err = zipw.CreateHeaderAndroid(op.fh)
Dan Willemsen017d8932016-08-04 15:43:03 -0700486 currentWriter = nopCloser{zw}
487 }
488 if err != nil {
489 return err
490 }
491
492 currentReaders = op.futureReaders
493 if op.futureReaders == nil {
494 currentWriter.Close()
495 currentWriter = nil
496 }
Jeff Gaston175f34c2017-08-17 21:43:21 -0700497 z.memoryRateLimiter.Finish(op.allocatedSize)
Dan Willemsen017d8932016-08-04 15:43:03 -0700498
499 case futureReader, ok := <-readersChan:
500 if !ok {
501 // Done with reading
502 currentWriter.Close()
503 currentWriter = nil
504 currentReaders = nil
505 }
506
507 currentReader = futureReader
508
509 case reader := <-currentReader:
Colin Crossf83c1502017-11-10 13:11:02 -0800510 _, err := io.Copy(currentWriter, reader)
Dan Willemsen017d8932016-08-04 15:43:03 -0700511 if err != nil {
512 return err
513 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700514
515 currentReader = nil
516
Colin Crossf83c1502017-11-10 13:11:02 -0800517 case err := <-z.errors:
Colin Cross2fe66872015-03-30 17:20:39 -0700518 return err
519 }
520 }
521
Dan Willemsen017d8932016-08-04 15:43:03 -0700522 // One last chance to catch an error
523 select {
Colin Crossf83c1502017-11-10 13:11:02 -0800524 case err := <-z.errors:
Dan Willemsen017d8932016-08-04 15:43:03 -0700525 return err
526 default:
527 zipw.Close()
528 return nil
Colin Cross2fe66872015-03-30 17:20:39 -0700529 }
Colin Cross2fe66872015-03-30 17:20:39 -0700530}
531
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700532// imports (possibly with compression) <src> into the zip at sub-path <dest>
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700533func (z *ZipWriter) addFile(dest, src string, method uint16, emulateJar bool) error {
Dan Willemsen017d8932016-08-04 15:43:03 -0700534 var fileSize int64
Dan Willemsen10462b32017-03-15 19:02:51 -0700535 var executable bool
Dan Willemsen017d8932016-08-04 15:43:03 -0700536
Colin Crossc7feeff2018-09-26 21:36:44 +0000537 if s, err := os.Lstat(src); err != nil {
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700538 return err
539 } else if s.IsDir() {
Colin Cross957cc4e2015-04-24 15:10:32 -0700540 if z.directories {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700541 return z.writeDirectory(dest, src, emulateJar)
Colin Cross957cc4e2015-04-24 15:10:32 -0700542 }
543 return nil
Dan Willemsen017d8932016-08-04 15:43:03 -0700544 } else {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700545 if err := z.writeDirectory(filepath.Dir(dest), src, emulateJar); err != nil {
Colin Crosse5580972017-08-30 17:40:21 -0700546 return err
547 }
548
549 if prev, exists := z.createdDirs[dest]; exists {
550 return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
551 }
552 if prev, exists := z.createdFiles[dest]; exists {
553 return fmt.Errorf("destination %q has two files %q and %q", dest, prev, src)
554 }
555
556 z.createdFiles[dest] = src
557
558 if s.Mode()&os.ModeSymlink != 0 {
559 return z.writeSymlink(dest, src)
560 } else if !s.Mode().IsRegular() {
561 return fmt.Errorf("%s is not a file, directory, or symlink", src)
562 }
563
Dan Willemsen017d8932016-08-04 15:43:03 -0700564 fileSize = s.Size()
Dan Willemsen10462b32017-03-15 19:02:51 -0700565 executable = s.Mode()&0100 != 0
Colin Cross957cc4e2015-04-24 15:10:32 -0700566 }
567
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700568 r, err := os.Open(src)
569 if err != nil {
570 return err
571 }
572
573 header := &zip.FileHeader{
574 Name: dest,
575 Method: method,
576 UncompressedSize64: uint64(fileSize),
577 }
578
579 if executable {
580 header.SetMode(0700)
581 }
582
583 return z.writeFileContents(header, r)
584}
585
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700586func (z *ZipWriter) addManifest(dest string, src string, method uint16) error {
Colin Crosse5580972017-08-30 17:40:21 -0700587 if prev, exists := z.createdDirs[dest]; exists {
588 return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
589 }
590 if prev, exists := z.createdFiles[dest]; exists {
591 return fmt.Errorf("destination %q has two files %q and %q", dest, prev, src)
592 }
593
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700594 if err := z.writeDirectory(filepath.Dir(dest), src, true); err != nil {
Colin Cross635acc92017-09-12 22:50:46 -0700595 return err
Jeff Gastoncef50b92017-08-23 15:41:35 -0700596 }
597
Colin Cross635acc92017-09-12 22:50:46 -0700598 fh, buf, err := jar.ManifestFileContents(src)
599 if err != nil {
600 return err
Jeff Gastoncef50b92017-08-23 15:41:35 -0700601 }
602
Colin Cross635acc92017-09-12 22:50:46 -0700603 reader := &byteReaderCloser{bytes.NewReader(buf), ioutil.NopCloser(nil)}
604
605 return z.writeFileContents(fh, reader)
Jeff Gastoncef50b92017-08-23 15:41:35 -0700606}
607
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700608func (z *ZipWriter) writeFileContents(header *zip.FileHeader, r readerSeekerCloser) (err error) {
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700609
610 header.SetModTime(z.time)
611
Dan Willemsen017d8932016-08-04 15:43:03 -0700612 compressChan := make(chan *zipEntry, 1)
613 z.writeOps <- compressChan
614
615 // Pre-fill a zipEntry, it will be sent in the compressChan once
616 // we're sure about the Method and CRC.
617 ze := &zipEntry{
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700618 fh: header,
Dan Willemsen10462b32017-03-15 19:02:51 -0700619 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700620
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700621 ze.allocatedSize = int64(header.UncompressedSize64)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700622 z.cpuRateLimiter.Request()
623 z.memoryRateLimiter.Request(ze.allocatedSize)
Dan Willemsen017d8932016-08-04 15:43:03 -0700624
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700625 fileSize := int64(header.UncompressedSize64)
626 if fileSize == 0 {
627 fileSize = int64(header.UncompressedSize)
628 }
629
630 if header.Method == zip.Deflate && fileSize >= minParallelFileSize {
Dan Willemsen017d8932016-08-04 15:43:03 -0700631 wg := new(sync.WaitGroup)
632
633 // Allocate enough buffer to hold all readers. We'll limit
634 // this based on actual buffer sizes in RateLimit.
635 ze.futureReaders = make(chan chan io.Reader, (fileSize/parallelBlockSize)+1)
636
637 // Calculate the CRC in the background, since reading the entire
638 // file could take a while.
639 //
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700640 // We could split this up into chunks as well, but it's faster
Dan Willemsen017d8932016-08-04 15:43:03 -0700641 // than the compression. Due to the Go Zip API, we also need to
642 // know the result before we can begin writing the compressed
643 // data out to the zipfile.
644 wg.Add(1)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700645 go z.crcFile(r, ze, compressChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700646
647 for start := int64(0); start < fileSize; start += parallelBlockSize {
648 sr := io.NewSectionReader(r, start, parallelBlockSize)
649 resultChan := make(chan io.Reader, 1)
650 ze.futureReaders <- resultChan
651
Jeff Gaston175f34c2017-08-17 21:43:21 -0700652 z.cpuRateLimiter.Request()
Dan Willemsen017d8932016-08-04 15:43:03 -0700653
654 last := !(start+parallelBlockSize < fileSize)
655 var dict []byte
656 if start >= windowSize {
657 dict, err = ioutil.ReadAll(io.NewSectionReader(r, start-windowSize, windowSize))
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700658 if err != nil {
659 return err
660 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700661 }
662
663 wg.Add(1)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700664 go z.compressPartialFile(sr, dict, last, resultChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700665 }
666
667 close(ze.futureReaders)
668
669 // Close the file handle after all readers are done
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700670 go func(wg *sync.WaitGroup, closer io.Closer) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700671 wg.Wait()
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700672 closer.Close()
Dan Willemsen017d8932016-08-04 15:43:03 -0700673 }(wg, r)
674 } else {
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700675 go func() {
676 z.compressWholeFile(ze, r, compressChan)
677 r.Close()
678 }()
Dan Willemsen017d8932016-08-04 15:43:03 -0700679 }
680
681 return nil
682}
683
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700684func (z *ZipWriter) crcFile(r io.Reader, ze *zipEntry, resultChan chan *zipEntry, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700685 defer wg.Done()
Jeff Gaston175f34c2017-08-17 21:43:21 -0700686 defer z.cpuRateLimiter.Finish()
Dan Willemsen017d8932016-08-04 15:43:03 -0700687
688 crc := crc32.NewIEEE()
689 _, err := io.Copy(crc, r)
690 if err != nil {
691 z.errors <- err
692 return
693 }
694
695 ze.fh.CRC32 = crc.Sum32()
696 resultChan <- ze
697 close(resultChan)
698}
699
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700700func (z *ZipWriter) compressPartialFile(r io.Reader, dict []byte, last bool, resultChan chan io.Reader, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700701 defer wg.Done()
702
703 result, err := z.compressBlock(r, dict, last)
704 if err != nil {
705 z.errors <- err
706 return
707 }
708
Jeff Gaston175f34c2017-08-17 21:43:21 -0700709 z.cpuRateLimiter.Finish()
710
Dan Willemsen017d8932016-08-04 15:43:03 -0700711 resultChan <- result
712}
713
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700714func (z *ZipWriter) compressBlock(r io.Reader, dict []byte, last bool) (*bytes.Buffer, error) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700715 buf := new(bytes.Buffer)
716 var fw *flate.Writer
717 var err error
718 if len(dict) > 0 {
719 // There's no way to Reset a Writer with a new dictionary, so
720 // don't use the Pool
721 fw, err = flate.NewWriterDict(buf, z.compLevel, dict)
722 } else {
723 var ok bool
724 if fw, ok = z.compressorPool.Get().(*flate.Writer); ok {
725 fw.Reset(buf)
726 } else {
727 fw, err = flate.NewWriter(buf, z.compLevel)
728 }
729 defer z.compressorPool.Put(fw)
730 }
731 if err != nil {
732 return nil, err
733 }
734
735 _, err = io.Copy(fw, r)
736 if err != nil {
737 return nil, err
738 }
739 if last {
740 fw.Close()
741 } else {
742 fw.Flush()
743 }
744
745 return buf, nil
746}
747
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700748func (z *ZipWriter) compressWholeFile(ze *zipEntry, r io.ReadSeeker, compressChan chan *zipEntry) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700749
Dan Willemsen017d8932016-08-04 15:43:03 -0700750 crc := crc32.NewIEEE()
Dan Willemsena8b55022017-03-15 21:49:26 -0700751 _, err := io.Copy(crc, r)
Colin Cross2fe66872015-03-30 17:20:39 -0700752 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700753 z.errors <- err
754 return
Colin Cross2fe66872015-03-30 17:20:39 -0700755 }
756
Dan Willemsena8b55022017-03-15 21:49:26 -0700757 ze.fh.CRC32 = crc.Sum32()
Colin Cross2fe66872015-03-30 17:20:39 -0700758
Dan Willemsen017d8932016-08-04 15:43:03 -0700759 _, err = r.Seek(0, 0)
Colin Cross2fe66872015-03-30 17:20:39 -0700760 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700761 z.errors <- err
762 return
Colin Cross2fe66872015-03-30 17:20:39 -0700763 }
764
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700765 readFile := func(reader io.ReadSeeker) ([]byte, error) {
766 _, err := reader.Seek(0, 0)
Nan Zhangf281bd82017-04-25 16:47:45 -0700767 if err != nil {
768 return nil, err
769 }
770
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700771 buf, err := ioutil.ReadAll(reader)
Nan Zhangf281bd82017-04-25 16:47:45 -0700772 if err != nil {
773 return nil, err
774 }
775
776 return buf, nil
777 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700778
Dan Willemsena8b55022017-03-15 21:49:26 -0700779 ze.futureReaders = make(chan chan io.Reader, 1)
Dan Willemsen017d8932016-08-04 15:43:03 -0700780 futureReader := make(chan io.Reader, 1)
781 ze.futureReaders <- futureReader
782 close(ze.futureReaders)
783
Nan Zhangf281bd82017-04-25 16:47:45 -0700784 if ze.fh.Method == zip.Deflate {
785 compressed, err := z.compressBlock(r, nil, true)
786 if err != nil {
787 z.errors <- err
788 return
789 }
790 if uint64(compressed.Len()) < ze.fh.UncompressedSize64 {
791 futureReader <- compressed
Nan Zhangf281bd82017-04-25 16:47:45 -0700792 } else {
793 buf, err := readFile(r)
794 if err != nil {
795 z.errors <- err
796 return
797 }
798 ze.fh.Method = zip.Store
799 futureReader <- bytes.NewReader(buf)
Nan Zhangf281bd82017-04-25 16:47:45 -0700800 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700801 } else {
Nan Zhangf281bd82017-04-25 16:47:45 -0700802 buf, err := readFile(r)
Dan Willemsen017d8932016-08-04 15:43:03 -0700803 if err != nil {
804 z.errors <- err
805 return
806 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700807 ze.fh.Method = zip.Store
808 futureReader <- bytes.NewReader(buf)
Dan Willemsen017d8932016-08-04 15:43:03 -0700809 }
Nan Zhangf281bd82017-04-25 16:47:45 -0700810
Jeff Gaston175f34c2017-08-17 21:43:21 -0700811 z.cpuRateLimiter.Finish()
812
Dan Willemsen017d8932016-08-04 15:43:03 -0700813 close(futureReader)
814
815 compressChan <- ze
816 close(compressChan)
Colin Cross2fe66872015-03-30 17:20:39 -0700817}
Colin Crosse19c7932015-04-24 15:08:38 -0700818
Colin Crosse5580972017-08-30 17:40:21 -0700819// writeDirectory annotates that dir is a directory created for the src file or directory, and adds
820// the directory entry to the zip file if directories are enabled.
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700821func (z *ZipWriter) writeDirectory(dir string, src string, emulateJar bool) error {
Jeff Gaston2d174132017-08-15 18:05:56 -0700822 // clean the input
Colin Crosse5580972017-08-30 17:40:21 -0700823 dir = filepath.Clean(dir)
Jeff Gaston2d174132017-08-15 18:05:56 -0700824
825 // discover any uncreated directories in the path
826 zipDirs := []string{}
Colin Crosse5580972017-08-30 17:40:21 -0700827 for dir != "" && dir != "." {
828 if _, exists := z.createdDirs[dir]; exists {
829 break
830 }
Jeff Gaston2d174132017-08-15 18:05:56 -0700831
Colin Crosse5580972017-08-30 17:40:21 -0700832 if prev, exists := z.createdFiles[dir]; exists {
833 return fmt.Errorf("destination %q is both a directory %q and a file %q", dir, src, prev)
834 }
835
836 z.createdDirs[dir] = src
Jeff Gaston2d174132017-08-15 18:05:56 -0700837 // parent directories precede their children
Colin Crosse5580972017-08-30 17:40:21 -0700838 zipDirs = append([]string{dir}, zipDirs...)
Jeff Gaston2d174132017-08-15 18:05:56 -0700839
Colin Crosse5580972017-08-30 17:40:21 -0700840 dir = filepath.Dir(dir)
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700841 }
842
Colin Crosse5580972017-08-30 17:40:21 -0700843 if z.directories {
844 // make a directory entry for each uncreated directory
845 for _, cleanDir := range zipDirs {
Colin Cross635acc92017-09-12 22:50:46 -0700846 var dirHeader *zip.FileHeader
Colin Crosse19c7932015-04-24 15:08:38 -0700847
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700848 if emulateJar && cleanDir+"/" == jar.MetaDir {
Colin Cross635acc92017-09-12 22:50:46 -0700849 dirHeader = jar.MetaDirFileHeader()
850 } else {
851 dirHeader = &zip.FileHeader{
852 Name: cleanDir + "/",
853 }
854 dirHeader.SetMode(0700 | os.ModeDir)
Colin Crosse5580972017-08-30 17:40:21 -0700855 }
Jeff Gaston8edbb3a2017-08-22 20:05:28 -0700856
Colin Cross635acc92017-09-12 22:50:46 -0700857 dirHeader.SetModTime(z.time)
858
Colin Crosse5580972017-08-30 17:40:21 -0700859 ze := make(chan *zipEntry, 1)
860 ze <- &zipEntry{
861 fh: dirHeader,
862 }
863 close(ze)
864 z.writeOps <- ze
Colin Crosse19c7932015-04-24 15:08:38 -0700865 }
Colin Crosse19c7932015-04-24 15:08:38 -0700866 }
867
868 return nil
869}
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700870
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700871func (z *ZipWriter) writeSymlink(rel, file string) error {
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700872 fileHeader := &zip.FileHeader{
873 Name: rel,
874 }
875 fileHeader.SetModTime(z.time)
Colin Cross297d9bc2018-06-22 16:37:47 -0700876 fileHeader.SetMode(0777 | os.ModeSymlink)
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700877
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700878 dest, err := os.Readlink(file)
879 if err != nil {
880 return err
881 }
882
Colin Cross297d9bc2018-06-22 16:37:47 -0700883 fileHeader.UncompressedSize64 = uint64(len(dest))
884 fileHeader.CRC32 = crc32.ChecksumIEEE([]byte(dest))
885
Dan Willemsen017d8932016-08-04 15:43:03 -0700886 ze := make(chan *zipEntry, 1)
887 futureReaders := make(chan chan io.Reader, 1)
888 futureReader := make(chan io.Reader, 1)
889 futureReaders <- futureReader
890 close(futureReaders)
891 futureReader <- bytes.NewBufferString(dest)
892 close(futureReader)
893
Dan Willemsen017d8932016-08-04 15:43:03 -0700894 ze <- &zipEntry{
895 fh: fileHeader,
896 futureReaders: futureReaders,
897 }
898 close(ze)
899 z.writeOps <- ze
900
901 return nil
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700902}
Colin Cross7b10cf12017-08-30 14:12:21 -0700903
904func recursiveGlobFiles(path string) []string {
905 var files []string
906 filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
907 if !info.IsDir() {
908 files = append(files, path)
909 }
910 return nil
911 })
912
913 return files
914}