blob: e7de6f8ccc9440add376cff5867aea741fb9e2ef [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"
25 "os"
26 "path/filepath"
Jeff Gastona2976952017-08-22 17:51:25 -070027 "sort"
Colin Cross2fe66872015-03-30 17:20:39 -070028 "strings"
Dan Willemsen017d8932016-08-04 15:43:03 -070029 "sync"
Colin Cross2fe66872015-03-30 17:20:39 -070030 "time"
Nan Zhang674dd932018-01-26 18:30:36 -080031 "unicode"
Dan Willemsen017d8932016-08-04 15:43:03 -070032
Colin Crossf83c1502017-11-10 13:11:02 -080033 "github.com/google/blueprint/pathtools"
34
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
Jeff Gastonc3bdc972017-10-12 12:18:19 -070068type FileArg struct {
69 PathPrefixInZip, SourcePrefixToStrip string
70 SourceFiles []string
Colin Crossb7c69112018-09-18 16:51:43 -070071 JunkPaths bool
Jeff Gastonc3bdc972017-10-12 12:18:19 -070072 GlobDir string
73}
74
Colin Crossfe945b42018-09-27 15:00:07 -070075type FileArgsBuilder struct {
76 state FileArg
77 err error
78 fs pathtools.FileSystem
79
80 fileArgs []FileArg
81}
82
83func NewFileArgsBuilder() *FileArgsBuilder {
84 return &FileArgsBuilder{
85 fs: pathtools.OsFs,
86 }
87}
88
89func (b *FileArgsBuilder) JunkPaths(v bool) *FileArgsBuilder {
90 b.state.JunkPaths = v
91 b.state.SourcePrefixToStrip = ""
92 return b
93}
94
95func (b *FileArgsBuilder) SourcePrefixToStrip(prefixToStrip string) *FileArgsBuilder {
96 b.state.JunkPaths = false
97 b.state.SourcePrefixToStrip = prefixToStrip
98 return b
99}
100
101func (b *FileArgsBuilder) PathPrefixInZip(rootPrefix string) *FileArgsBuilder {
102 b.state.PathPrefixInZip = rootPrefix
103 return b
104}
105
106func (b *FileArgsBuilder) File(name string) *FileArgsBuilder {
107 if b.err != nil {
108 return b
109 }
110
111 arg := b.state
112 arg.SourceFiles = []string{name}
113 b.fileArgs = append(b.fileArgs, arg)
114 return b
115}
116
117func (b *FileArgsBuilder) Dir(name string) *FileArgsBuilder {
118 if b.err != nil {
119 return b
120 }
121
122 arg := b.state
123 arg.GlobDir = name
124 b.fileArgs = append(b.fileArgs, arg)
125 return b
126}
127
128func (b *FileArgsBuilder) List(name string) *FileArgsBuilder {
129 if b.err != nil {
130 return b
131 }
132
133 f, err := b.fs.Open(name)
134 if err != nil {
135 b.err = err
136 return b
137 }
138 defer f.Close()
139
140 list, err := ioutil.ReadAll(f)
141 if err != nil {
142 b.err = err
143 return b
144 }
145
146 arg := b.state
147 arg.SourceFiles = strings.Split(string(list), "\n")
148 b.fileArgs = append(b.fileArgs, arg)
149 return b
150}
151
152func (b *FileArgsBuilder) Error() error {
153 if b == nil {
154 return nil
155 }
156 return b.err
157}
158
159func (b *FileArgsBuilder) FileArgs() []FileArg {
160 if b == nil {
161 return nil
162 }
163 return b.fileArgs
164}
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700165
166type ZipWriter struct {
Colin Crosse5580972017-08-30 17:40:21 -0700167 time time.Time
168 createdFiles map[string]string
169 createdDirs map[string]string
170 directories bool
Colin Crosse19c7932015-04-24 15:08:38 -0700171
Dan Willemsen017d8932016-08-04 15:43:03 -0700172 errors chan error
173 writeOps chan chan *zipEntry
174
Jeff Gaston175f34c2017-08-17 21:43:21 -0700175 cpuRateLimiter *CPURateLimiter
176 memoryRateLimiter *MemoryRateLimiter
Dan Willemsen017d8932016-08-04 15:43:03 -0700177
178 compressorPool sync.Pool
179 compLevel int
Colin Cross05518bc2018-09-27 15:06:19 -0700180
181 fs pathtools.FileSystem
Dan Willemsen017d8932016-08-04 15:43:03 -0700182}
183
184type zipEntry struct {
185 fh *zip.FileHeader
186
187 // List of delayed io.Reader
188 futureReaders chan chan io.Reader
Jeff Gaston175f34c2017-08-17 21:43:21 -0700189
190 // Only used for passing into the MemoryRateLimiter to ensure we
191 // release as much memory as much as we request
192 allocatedSize int64
Colin Cross2fe66872015-03-30 17:20:39 -0700193}
194
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700195type ZipArgs struct {
Colin Crossfe945b42018-09-27 15:00:07 -0700196 FileArgs []FileArg
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700197 OutputFilePath string
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700198 EmulateJar bool
199 AddDirectoryEntriesToZip bool
200 CompressionLevel int
201 ManifestSourcePath string
202 NumParallelJobs int
203 NonDeflatedFiles map[string]bool
Colin Crossf83c1502017-11-10 13:11:02 -0800204 WriteIfChanged bool
Colin Cross05518bc2018-09-27 15:06:19 -0700205 Filesystem pathtools.FileSystem
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700206}
Colin Cross2fe66872015-03-30 17:20:39 -0700207
Nan Zhang674dd932018-01-26 18:30:36 -0800208const NOQUOTE = '\x00'
209
210func ReadRespFile(bytes []byte) []string {
211 var args []string
212 var arg []rune
213
214 isEscaping := false
215 quotingStart := NOQUOTE
216 for _, c := range string(bytes) {
217 switch {
218 case isEscaping:
219 if quotingStart == '"' {
220 if !(c == '"' || c == '\\') {
221 // '\"' or '\\' will be escaped under double quoting.
222 arg = append(arg, '\\')
223 }
224 }
225 arg = append(arg, c)
226 isEscaping = false
227 case c == '\\' && quotingStart != '\'':
228 isEscaping = true
229 case quotingStart == NOQUOTE && (c == '\'' || c == '"'):
230 quotingStart = c
231 case quotingStart != NOQUOTE && c == quotingStart:
232 quotingStart = NOQUOTE
233 case quotingStart == NOQUOTE && unicode.IsSpace(c):
234 // Current character is a space outside quotes
235 if len(arg) != 0 {
236 args = append(args, string(arg))
237 }
238 arg = arg[:0]
239 default:
240 arg = append(arg, c)
241 }
242 }
243
244 if len(arg) != 0 {
245 args = append(args, string(arg))
246 }
247
248 return args
249}
250
Colin Cross05518bc2018-09-27 15:06:19 -0700251func ZipTo(args ZipArgs, w io.Writer) error {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700252 if args.EmulateJar {
253 args.AddDirectoryEntriesToZip = true
Jeff Gaston8edbb3a2017-08-22 20:05:28 -0700254 }
255
Colin Cross05518bc2018-09-27 15:06:19 -0700256 z := &ZipWriter{
Colin Crossc7feeff2018-09-26 21:36:44 +0000257 time: jar.DefaultTime,
258 createdDirs: make(map[string]string),
259 createdFiles: make(map[string]string),
260 directories: args.AddDirectoryEntriesToZip,
261 compLevel: args.CompressionLevel,
Colin Cross05518bc2018-09-27 15:06:19 -0700262 fs: args.Filesystem,
Colin Cross2fe66872015-03-30 17:20:39 -0700263 }
Colin Cross05518bc2018-09-27 15:06:19 -0700264
265 if z.fs == nil {
266 z.fs = pathtools.OsFs
267 }
268
Nan Zhang9067b042017-03-17 14:04:43 -0700269 pathMappings := []pathMapping{}
Nan Zhang9067b042017-03-17 14:04:43 -0700270
Colin Crossd3216292018-09-14 15:06:31 -0700271 noCompression := args.CompressionLevel == 0
272
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700273 for _, fa := range args.FileArgs {
Colin Cross7f33b812018-09-26 21:36:22 +0000274 srcs := fa.SourceFiles
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700275 if fa.GlobDir != "" {
Colin Cross7f33b812018-09-26 21:36:22 +0000276 srcs = append(srcs, recursiveGlobFiles(fa.GlobDir)...)
Colin Cross7b10cf12017-08-30 14:12:21 -0700277 }
278 for _, src := range srcs {
Colin Crossb7c69112018-09-18 16:51:43 -0700279 err := fillPathPairs(fa, src, &pathMappings, args.NonDeflatedFiles, noCompression)
Colin Crossd3216292018-09-14 15:06:31 -0700280 if err != nil {
Colin Cross05518bc2018-09-27 15:06:19 -0700281 return err
Nan Zhang9067b042017-03-17 14:04:43 -0700282 }
283 }
284 }
285
Colin Cross05518bc2018-09-27 15:06:19 -0700286 return z.write(w, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.NumParallelJobs)
287}
288
289func Zip(args ZipArgs) error {
290 if args.OutputFilePath == "" {
291 return fmt.Errorf("output file path must be nonempty")
292 }
293
Colin Crossf83c1502017-11-10 13:11:02 -0800294 buf := &bytes.Buffer{}
295 var out io.Writer = buf
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700296
Colin Crossf83c1502017-11-10 13:11:02 -0800297 if !args.WriteIfChanged {
298 f, err := os.Create(args.OutputFilePath)
299 if err != nil {
300 return err
301 }
302
303 defer f.Close()
304 defer func() {
305 if err != nil {
306 os.Remove(args.OutputFilePath)
307 }
308 }()
309
310 out = f
311 }
312
Colin Cross05518bc2018-09-27 15:06:19 -0700313 err := ZipTo(args, out)
Colin Crossf83c1502017-11-10 13:11:02 -0800314 if err != nil {
315 return err
316 }
317
318 if args.WriteIfChanged {
319 err := pathtools.WriteFileIfChanged(args.OutputFilePath, buf.Bytes(), 0666)
320 if err != nil {
321 return err
322 }
323 }
324
325 return nil
Colin Cross2fe66872015-03-30 17:20:39 -0700326}
327
Colin Crossb7c69112018-09-18 16:51:43 -0700328func fillPathPairs(fa FileArg, src string, pathMappings *[]pathMapping,
Colin Crossd3216292018-09-14 15:06:31 -0700329 nonDeflatedFiles map[string]bool, noCompression bool) error {
330
Nan Zhang9067b042017-03-17 14:04:43 -0700331 src = strings.TrimSpace(src)
332 if src == "" {
333 return nil
334 }
335 src = filepath.Clean(src)
Colin Crossb7c69112018-09-18 16:51:43 -0700336 var dest string
337
338 if fa.JunkPaths {
339 dest = filepath.Base(src)
340 } else {
341 var err error
342 dest, err = filepath.Rel(fa.SourcePrefixToStrip, src)
343 if err != nil {
344 return err
345 }
Nan Zhang9067b042017-03-17 14:04:43 -0700346 }
Colin Crossb7c69112018-09-18 16:51:43 -0700347 dest = filepath.Join(fa.PathPrefixInZip, dest)
Nan Zhang9067b042017-03-17 14:04:43 -0700348
Nan Zhangf281bd82017-04-25 16:47:45 -0700349 zipMethod := zip.Deflate
Colin Crossd3216292018-09-14 15:06:31 -0700350 if _, found := nonDeflatedFiles[dest]; found || noCompression {
Nan Zhangf281bd82017-04-25 16:47:45 -0700351 zipMethod = zip.Store
352 }
353 *pathMappings = append(*pathMappings,
354 pathMapping{dest: dest, src: src, zipMethod: zipMethod})
Nan Zhang9067b042017-03-17 14:04:43 -0700355
356 return nil
357}
358
Jeff Gastona2976952017-08-22 17:51:25 -0700359func jarSort(mappings []pathMapping) {
360 less := func(i int, j int) (smaller bool) {
361 return jar.EntryNamesLess(mappings[i].dest, mappings[j].dest)
362 }
363 sort.SliceStable(mappings, less)
364}
365
Colin Crossf83c1502017-11-10 13:11:02 -0800366func (z *ZipWriter) write(f io.Writer, pathMappings []pathMapping, manifest string, emulateJar bool, parallelJobs int) error {
Dan Willemsen017d8932016-08-04 15:43:03 -0700367 z.errors = make(chan error)
368 defer close(z.errors)
Colin Cross2fe66872015-03-30 17:20:39 -0700369
Dan Willemsen017d8932016-08-04 15:43:03 -0700370 // This channel size can be essentially unlimited -- it's used as a fifo
371 // queue decouple the CPU and IO loads. Directories don't require any
372 // compression time, but still cost some IO. Similar with small files that
373 // can be very fast to compress. Some files that are more difficult to
374 // compress won't take a corresponding longer time writing out.
375 //
376 // The optimum size here depends on your CPU and IO characteristics, and
377 // the the layout of your zip file. 1000 was chosen mostly at random as
378 // something that worked reasonably well for a test file.
379 //
380 // The RateLimit object will put the upper bounds on the number of
381 // parallel compressions and outstanding buffers.
382 z.writeOps = make(chan chan *zipEntry, 1000)
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700383 z.cpuRateLimiter = NewCPURateLimiter(int64(parallelJobs))
Jeff Gaston175f34c2017-08-17 21:43:21 -0700384 z.memoryRateLimiter = NewMemoryRateLimiter(0)
385 defer func() {
386 z.cpuRateLimiter.Stop()
387 z.memoryRateLimiter.Stop()
388 }()
Jeff Gastona2976952017-08-22 17:51:25 -0700389
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700390 if manifest != "" && !emulateJar {
Colin Cross635acc92017-09-12 22:50:46 -0700391 return errors.New("must specify --jar when specifying a manifest via -m")
Jeff Gastona2976952017-08-22 17:51:25 -0700392 }
393
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700394 if emulateJar {
Colin Cross635acc92017-09-12 22:50:46 -0700395 // manifest may be empty, in which case addManifest will fill in a default
396 pathMappings = append(pathMappings, pathMapping{jar.ManifestFile, manifest, zip.Deflate})
397
Jeff Gastona2976952017-08-22 17:51:25 -0700398 jarSort(pathMappings)
399 }
400
Dan Willemsen017d8932016-08-04 15:43:03 -0700401 go func() {
402 var err error
403 defer close(z.writeOps)
404
Nan Zhang9067b042017-03-17 14:04:43 -0700405 for _, ele := range pathMappings {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700406 if emulateJar && ele.dest == jar.ManifestFile {
Jeff Gastoncef50b92017-08-23 15:41:35 -0700407 err = z.addManifest(ele.dest, ele.src, ele.zipMethod)
408 } else {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700409 err = z.addFile(ele.dest, ele.src, ele.zipMethod, emulateJar)
Jeff Gastoncef50b92017-08-23 15:41:35 -0700410 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700411 if err != nil {
412 z.errors <- err
413 return
414 }
415 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700416 }()
417
418 zipw := zip.NewWriter(f)
419
420 var currentWriteOpChan chan *zipEntry
421 var currentWriter io.WriteCloser
422 var currentReaders chan chan io.Reader
423 var currentReader chan io.Reader
424 var done bool
425
426 for !done {
427 var writeOpsChan chan chan *zipEntry
428 var writeOpChan chan *zipEntry
429 var readersChan chan chan io.Reader
430
431 if currentReader != nil {
432 // Only read and process errors
433 } else if currentReaders != nil {
434 readersChan = currentReaders
435 } else if currentWriteOpChan != nil {
436 writeOpChan = currentWriteOpChan
437 } else {
438 writeOpsChan = z.writeOps
439 }
440
441 select {
442 case writeOp, ok := <-writeOpsChan:
443 if !ok {
444 done = true
445 }
446
447 currentWriteOpChan = writeOp
448
449 case op := <-writeOpChan:
450 currentWriteOpChan = nil
451
Colin Crossf83c1502017-11-10 13:11:02 -0800452 var err error
Dan Willemsen017d8932016-08-04 15:43:03 -0700453 if op.fh.Method == zip.Deflate {
454 currentWriter, err = zipw.CreateCompressedHeader(op.fh)
455 } else {
456 var zw io.Writer
Jeff Gastonc5eb66d2017-08-24 14:11:27 -0700457
458 op.fh.CompressedSize64 = op.fh.UncompressedSize64
459
460 zw, err = zipw.CreateHeaderAndroid(op.fh)
Dan Willemsen017d8932016-08-04 15:43:03 -0700461 currentWriter = nopCloser{zw}
462 }
463 if err != nil {
464 return err
465 }
466
467 currentReaders = op.futureReaders
468 if op.futureReaders == nil {
469 currentWriter.Close()
470 currentWriter = nil
471 }
Jeff Gaston175f34c2017-08-17 21:43:21 -0700472 z.memoryRateLimiter.Finish(op.allocatedSize)
Dan Willemsen017d8932016-08-04 15:43:03 -0700473
474 case futureReader, ok := <-readersChan:
475 if !ok {
476 // Done with reading
477 currentWriter.Close()
478 currentWriter = nil
479 currentReaders = nil
480 }
481
482 currentReader = futureReader
483
484 case reader := <-currentReader:
Colin Crossf83c1502017-11-10 13:11:02 -0800485 _, err := io.Copy(currentWriter, reader)
Dan Willemsen017d8932016-08-04 15:43:03 -0700486 if err != nil {
487 return err
488 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700489
490 currentReader = nil
491
Colin Crossf83c1502017-11-10 13:11:02 -0800492 case err := <-z.errors:
Colin Cross2fe66872015-03-30 17:20:39 -0700493 return err
494 }
495 }
496
Dan Willemsen017d8932016-08-04 15:43:03 -0700497 // One last chance to catch an error
498 select {
Colin Crossf83c1502017-11-10 13:11:02 -0800499 case err := <-z.errors:
Dan Willemsen017d8932016-08-04 15:43:03 -0700500 return err
501 default:
502 zipw.Close()
503 return nil
Colin Cross2fe66872015-03-30 17:20:39 -0700504 }
Colin Cross2fe66872015-03-30 17:20:39 -0700505}
506
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700507// imports (possibly with compression) <src> into the zip at sub-path <dest>
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700508func (z *ZipWriter) addFile(dest, src string, method uint16, emulateJar bool) error {
Dan Willemsen017d8932016-08-04 15:43:03 -0700509 var fileSize int64
Dan Willemsen10462b32017-03-15 19:02:51 -0700510 var executable bool
Dan Willemsen017d8932016-08-04 15:43:03 -0700511
Colin Cross05518bc2018-09-27 15:06:19 -0700512 if s, err := z.fs.Lstat(src); err != nil {
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700513 return err
514 } else if s.IsDir() {
Colin Cross957cc4e2015-04-24 15:10:32 -0700515 if z.directories {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700516 return z.writeDirectory(dest, src, emulateJar)
Colin Cross957cc4e2015-04-24 15:10:32 -0700517 }
518 return nil
Dan Willemsen017d8932016-08-04 15:43:03 -0700519 } else {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700520 if err := z.writeDirectory(filepath.Dir(dest), src, emulateJar); err != nil {
Colin Crosse5580972017-08-30 17:40:21 -0700521 return err
522 }
523
524 if prev, exists := z.createdDirs[dest]; exists {
525 return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
526 }
527 if prev, exists := z.createdFiles[dest]; exists {
528 return fmt.Errorf("destination %q has two files %q and %q", dest, prev, src)
529 }
530
531 z.createdFiles[dest] = src
532
533 if s.Mode()&os.ModeSymlink != 0 {
534 return z.writeSymlink(dest, src)
535 } else if !s.Mode().IsRegular() {
536 return fmt.Errorf("%s is not a file, directory, or symlink", src)
537 }
538
Dan Willemsen017d8932016-08-04 15:43:03 -0700539 fileSize = s.Size()
Dan Willemsen10462b32017-03-15 19:02:51 -0700540 executable = s.Mode()&0100 != 0
Colin Cross957cc4e2015-04-24 15:10:32 -0700541 }
542
Colin Cross05518bc2018-09-27 15:06:19 -0700543 r, err := z.fs.Open(src)
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700544 if err != nil {
545 return err
546 }
547
548 header := &zip.FileHeader{
549 Name: dest,
550 Method: method,
551 UncompressedSize64: uint64(fileSize),
552 }
553
554 if executable {
555 header.SetMode(0700)
556 }
557
558 return z.writeFileContents(header, r)
559}
560
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700561func (z *ZipWriter) addManifest(dest string, src string, method uint16) error {
Colin Crosse5580972017-08-30 17:40:21 -0700562 if prev, exists := z.createdDirs[dest]; exists {
563 return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
564 }
565 if prev, exists := z.createdFiles[dest]; exists {
566 return fmt.Errorf("destination %q has two files %q and %q", dest, prev, src)
567 }
568
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700569 if err := z.writeDirectory(filepath.Dir(dest), src, true); err != nil {
Colin Cross635acc92017-09-12 22:50:46 -0700570 return err
Jeff Gastoncef50b92017-08-23 15:41:35 -0700571 }
572
Colin Cross05518bc2018-09-27 15:06:19 -0700573 var contents []byte
574 if src != "" {
575 f, err := z.fs.Open(src)
576 if err != nil {
577 return err
578 }
579
580 contents, err = ioutil.ReadAll(f)
581 f.Close()
582 if err != nil {
583 return err
584 }
585 }
586
587 fh, buf, err := jar.ManifestFileContents(contents)
Colin Cross635acc92017-09-12 22:50:46 -0700588 if err != nil {
589 return err
Jeff Gastoncef50b92017-08-23 15:41:35 -0700590 }
591
Colin Cross635acc92017-09-12 22:50:46 -0700592 reader := &byteReaderCloser{bytes.NewReader(buf), ioutil.NopCloser(nil)}
593
594 return z.writeFileContents(fh, reader)
Jeff Gastoncef50b92017-08-23 15:41:35 -0700595}
596
Colin Cross05518bc2018-09-27 15:06:19 -0700597func (z *ZipWriter) writeFileContents(header *zip.FileHeader, r pathtools.ReaderAtSeekerCloser) (err error) {
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700598
599 header.SetModTime(z.time)
600
Dan Willemsen017d8932016-08-04 15:43:03 -0700601 compressChan := make(chan *zipEntry, 1)
602 z.writeOps <- compressChan
603
604 // Pre-fill a zipEntry, it will be sent in the compressChan once
605 // we're sure about the Method and CRC.
606 ze := &zipEntry{
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700607 fh: header,
Dan Willemsen10462b32017-03-15 19:02:51 -0700608 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700609
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700610 ze.allocatedSize = int64(header.UncompressedSize64)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700611 z.cpuRateLimiter.Request()
612 z.memoryRateLimiter.Request(ze.allocatedSize)
Dan Willemsen017d8932016-08-04 15:43:03 -0700613
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700614 fileSize := int64(header.UncompressedSize64)
615 if fileSize == 0 {
616 fileSize = int64(header.UncompressedSize)
617 }
618
619 if header.Method == zip.Deflate && fileSize >= minParallelFileSize {
Dan Willemsen017d8932016-08-04 15:43:03 -0700620 wg := new(sync.WaitGroup)
621
622 // Allocate enough buffer to hold all readers. We'll limit
623 // this based on actual buffer sizes in RateLimit.
624 ze.futureReaders = make(chan chan io.Reader, (fileSize/parallelBlockSize)+1)
625
626 // Calculate the CRC in the background, since reading the entire
627 // file could take a while.
628 //
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700629 // We could split this up into chunks as well, but it's faster
Dan Willemsen017d8932016-08-04 15:43:03 -0700630 // than the compression. Due to the Go Zip API, we also need to
631 // know the result before we can begin writing the compressed
632 // data out to the zipfile.
633 wg.Add(1)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700634 go z.crcFile(r, ze, compressChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700635
636 for start := int64(0); start < fileSize; start += parallelBlockSize {
637 sr := io.NewSectionReader(r, start, parallelBlockSize)
638 resultChan := make(chan io.Reader, 1)
639 ze.futureReaders <- resultChan
640
Jeff Gaston175f34c2017-08-17 21:43:21 -0700641 z.cpuRateLimiter.Request()
Dan Willemsen017d8932016-08-04 15:43:03 -0700642
643 last := !(start+parallelBlockSize < fileSize)
644 var dict []byte
645 if start >= windowSize {
646 dict, err = ioutil.ReadAll(io.NewSectionReader(r, start-windowSize, windowSize))
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700647 if err != nil {
648 return err
649 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700650 }
651
652 wg.Add(1)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700653 go z.compressPartialFile(sr, dict, last, resultChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700654 }
655
656 close(ze.futureReaders)
657
658 // Close the file handle after all readers are done
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700659 go func(wg *sync.WaitGroup, closer io.Closer) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700660 wg.Wait()
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700661 closer.Close()
Dan Willemsen017d8932016-08-04 15:43:03 -0700662 }(wg, r)
663 } else {
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700664 go func() {
665 z.compressWholeFile(ze, r, compressChan)
666 r.Close()
667 }()
Dan Willemsen017d8932016-08-04 15:43:03 -0700668 }
669
670 return nil
671}
672
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700673func (z *ZipWriter) crcFile(r io.Reader, ze *zipEntry, resultChan chan *zipEntry, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700674 defer wg.Done()
Jeff Gaston175f34c2017-08-17 21:43:21 -0700675 defer z.cpuRateLimiter.Finish()
Dan Willemsen017d8932016-08-04 15:43:03 -0700676
677 crc := crc32.NewIEEE()
678 _, err := io.Copy(crc, r)
679 if err != nil {
680 z.errors <- err
681 return
682 }
683
684 ze.fh.CRC32 = crc.Sum32()
685 resultChan <- ze
686 close(resultChan)
687}
688
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700689func (z *ZipWriter) compressPartialFile(r io.Reader, dict []byte, last bool, resultChan chan io.Reader, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700690 defer wg.Done()
691
692 result, err := z.compressBlock(r, dict, last)
693 if err != nil {
694 z.errors <- err
695 return
696 }
697
Jeff Gaston175f34c2017-08-17 21:43:21 -0700698 z.cpuRateLimiter.Finish()
699
Dan Willemsen017d8932016-08-04 15:43:03 -0700700 resultChan <- result
701}
702
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700703func (z *ZipWriter) compressBlock(r io.Reader, dict []byte, last bool) (*bytes.Buffer, error) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700704 buf := new(bytes.Buffer)
705 var fw *flate.Writer
706 var err error
707 if len(dict) > 0 {
708 // There's no way to Reset a Writer with a new dictionary, so
709 // don't use the Pool
710 fw, err = flate.NewWriterDict(buf, z.compLevel, dict)
711 } else {
712 var ok bool
713 if fw, ok = z.compressorPool.Get().(*flate.Writer); ok {
714 fw.Reset(buf)
715 } else {
716 fw, err = flate.NewWriter(buf, z.compLevel)
717 }
718 defer z.compressorPool.Put(fw)
719 }
720 if err != nil {
721 return nil, err
722 }
723
724 _, err = io.Copy(fw, r)
725 if err != nil {
726 return nil, err
727 }
728 if last {
729 fw.Close()
730 } else {
731 fw.Flush()
732 }
733
734 return buf, nil
735}
736
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700737func (z *ZipWriter) compressWholeFile(ze *zipEntry, r io.ReadSeeker, compressChan chan *zipEntry) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700738
Dan Willemsen017d8932016-08-04 15:43:03 -0700739 crc := crc32.NewIEEE()
Dan Willemsena8b55022017-03-15 21:49:26 -0700740 _, err := io.Copy(crc, r)
Colin Cross2fe66872015-03-30 17:20:39 -0700741 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700742 z.errors <- err
743 return
Colin Cross2fe66872015-03-30 17:20:39 -0700744 }
745
Dan Willemsena8b55022017-03-15 21:49:26 -0700746 ze.fh.CRC32 = crc.Sum32()
Colin Cross2fe66872015-03-30 17:20:39 -0700747
Dan Willemsen017d8932016-08-04 15:43:03 -0700748 _, err = r.Seek(0, 0)
Colin Cross2fe66872015-03-30 17:20:39 -0700749 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700750 z.errors <- err
751 return
Colin Cross2fe66872015-03-30 17:20:39 -0700752 }
753
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700754 readFile := func(reader io.ReadSeeker) ([]byte, error) {
755 _, err := reader.Seek(0, 0)
Nan Zhangf281bd82017-04-25 16:47:45 -0700756 if err != nil {
757 return nil, err
758 }
759
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700760 buf, err := ioutil.ReadAll(reader)
Nan Zhangf281bd82017-04-25 16:47:45 -0700761 if err != nil {
762 return nil, err
763 }
764
765 return buf, nil
766 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700767
Dan Willemsena8b55022017-03-15 21:49:26 -0700768 ze.futureReaders = make(chan chan io.Reader, 1)
Dan Willemsen017d8932016-08-04 15:43:03 -0700769 futureReader := make(chan io.Reader, 1)
770 ze.futureReaders <- futureReader
771 close(ze.futureReaders)
772
Nan Zhangf281bd82017-04-25 16:47:45 -0700773 if ze.fh.Method == zip.Deflate {
774 compressed, err := z.compressBlock(r, nil, true)
775 if err != nil {
776 z.errors <- err
777 return
778 }
779 if uint64(compressed.Len()) < ze.fh.UncompressedSize64 {
780 futureReader <- compressed
Nan Zhangf281bd82017-04-25 16:47:45 -0700781 } else {
782 buf, err := readFile(r)
783 if err != nil {
784 z.errors <- err
785 return
786 }
787 ze.fh.Method = zip.Store
788 futureReader <- bytes.NewReader(buf)
Nan Zhangf281bd82017-04-25 16:47:45 -0700789 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700790 } else {
Nan Zhangf281bd82017-04-25 16:47:45 -0700791 buf, err := readFile(r)
Dan Willemsen017d8932016-08-04 15:43:03 -0700792 if err != nil {
793 z.errors <- err
794 return
795 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700796 ze.fh.Method = zip.Store
797 futureReader <- bytes.NewReader(buf)
Dan Willemsen017d8932016-08-04 15:43:03 -0700798 }
Nan Zhangf281bd82017-04-25 16:47:45 -0700799
Jeff Gaston175f34c2017-08-17 21:43:21 -0700800 z.cpuRateLimiter.Finish()
801
Dan Willemsen017d8932016-08-04 15:43:03 -0700802 close(futureReader)
803
804 compressChan <- ze
805 close(compressChan)
Colin Cross2fe66872015-03-30 17:20:39 -0700806}
Colin Crosse19c7932015-04-24 15:08:38 -0700807
Colin Crosse5580972017-08-30 17:40:21 -0700808// writeDirectory annotates that dir is a directory created for the src file or directory, and adds
809// the directory entry to the zip file if directories are enabled.
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700810func (z *ZipWriter) writeDirectory(dir string, src string, emulateJar bool) error {
Jeff Gaston2d174132017-08-15 18:05:56 -0700811 // clean the input
Colin Crosse5580972017-08-30 17:40:21 -0700812 dir = filepath.Clean(dir)
Jeff Gaston2d174132017-08-15 18:05:56 -0700813
814 // discover any uncreated directories in the path
815 zipDirs := []string{}
Colin Crosse5580972017-08-30 17:40:21 -0700816 for dir != "" && dir != "." {
817 if _, exists := z.createdDirs[dir]; exists {
818 break
819 }
Jeff Gaston2d174132017-08-15 18:05:56 -0700820
Colin Crosse5580972017-08-30 17:40:21 -0700821 if prev, exists := z.createdFiles[dir]; exists {
822 return fmt.Errorf("destination %q is both a directory %q and a file %q", dir, src, prev)
823 }
824
825 z.createdDirs[dir] = src
Jeff Gaston2d174132017-08-15 18:05:56 -0700826 // parent directories precede their children
Colin Crosse5580972017-08-30 17:40:21 -0700827 zipDirs = append([]string{dir}, zipDirs...)
Jeff Gaston2d174132017-08-15 18:05:56 -0700828
Colin Crosse5580972017-08-30 17:40:21 -0700829 dir = filepath.Dir(dir)
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700830 }
831
Colin Crosse5580972017-08-30 17:40:21 -0700832 if z.directories {
833 // make a directory entry for each uncreated directory
834 for _, cleanDir := range zipDirs {
Colin Cross635acc92017-09-12 22:50:46 -0700835 var dirHeader *zip.FileHeader
Colin Crosse19c7932015-04-24 15:08:38 -0700836
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700837 if emulateJar && cleanDir+"/" == jar.MetaDir {
Colin Cross635acc92017-09-12 22:50:46 -0700838 dirHeader = jar.MetaDirFileHeader()
839 } else {
840 dirHeader = &zip.FileHeader{
841 Name: cleanDir + "/",
842 }
843 dirHeader.SetMode(0700 | os.ModeDir)
Colin Crosse5580972017-08-30 17:40:21 -0700844 }
Jeff Gaston8edbb3a2017-08-22 20:05:28 -0700845
Colin Cross635acc92017-09-12 22:50:46 -0700846 dirHeader.SetModTime(z.time)
847
Colin Crosse5580972017-08-30 17:40:21 -0700848 ze := make(chan *zipEntry, 1)
849 ze <- &zipEntry{
850 fh: dirHeader,
851 }
852 close(ze)
853 z.writeOps <- ze
Colin Crosse19c7932015-04-24 15:08:38 -0700854 }
Colin Crosse19c7932015-04-24 15:08:38 -0700855 }
856
857 return nil
858}
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700859
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700860func (z *ZipWriter) writeSymlink(rel, file string) error {
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700861 fileHeader := &zip.FileHeader{
862 Name: rel,
863 }
864 fileHeader.SetModTime(z.time)
Colin Cross297d9bc2018-06-22 16:37:47 -0700865 fileHeader.SetMode(0777 | os.ModeSymlink)
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700866
Colin Cross05518bc2018-09-27 15:06:19 -0700867 dest, err := z.fs.Readlink(file)
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700868 if err != nil {
869 return err
870 }
871
Colin Cross297d9bc2018-06-22 16:37:47 -0700872 fileHeader.UncompressedSize64 = uint64(len(dest))
873 fileHeader.CRC32 = crc32.ChecksumIEEE([]byte(dest))
874
Dan Willemsen017d8932016-08-04 15:43:03 -0700875 ze := make(chan *zipEntry, 1)
876 futureReaders := make(chan chan io.Reader, 1)
877 futureReader := make(chan io.Reader, 1)
878 futureReaders <- futureReader
879 close(futureReaders)
880 futureReader <- bytes.NewBufferString(dest)
881 close(futureReader)
882
Dan Willemsen017d8932016-08-04 15:43:03 -0700883 ze <- &zipEntry{
884 fh: fileHeader,
885 futureReaders: futureReaders,
886 }
887 close(ze)
888 z.writeOps <- ze
889
890 return nil
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700891}
Colin Cross7b10cf12017-08-30 14:12:21 -0700892
893func recursiveGlobFiles(path string) []string {
894 var files []string
895 filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
896 if !info.IsDir() {
897 files = append(files, path)
898 }
899 return nil
900 })
901
902 return files
903}