blob: e27432cbba64d0be17def9be0af592a3698234de [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 Cross1d98ee22018-09-18 17:05:15 -070030 "syscall"
Colin Cross2fe66872015-03-30 17:20:39 -070031 "time"
Nan Zhang674dd932018-01-26 18:30:36 -080032 "unicode"
Dan Willemsen017d8932016-08-04 15:43:03 -070033
Colin Crossf83c1502017-11-10 13:11:02 -080034 "github.com/google/blueprint/pathtools"
35
Jeff Gastona2976952017-08-22 17:51:25 -070036 "android/soong/jar"
Dan Willemsen017d8932016-08-04 15:43:03 -070037 "android/soong/third_party/zip"
Colin Cross2fe66872015-03-30 17:20:39 -070038)
39
Dan Willemsen017d8932016-08-04 15:43:03 -070040// Block size used during parallel compression of a single file.
41const parallelBlockSize = 1 * 1024 * 1024 // 1MB
42
43// Minimum file size to use parallel compression. It requires more
44// flate.Writer allocations, since we can't change the dictionary
45// during Reset
46const minParallelFileSize = parallelBlockSize * 6
47
48// Size of the ZIP compression window (32KB)
49const windowSize = 32 * 1024
50
51type nopCloser struct {
52 io.Writer
53}
54
55func (nopCloser) Close() error {
56 return nil
57}
58
Jeff Gastoncef50b92017-08-23 15:41:35 -070059type byteReaderCloser struct {
Colin Cross635acc92017-09-12 22:50:46 -070060 *bytes.Reader
Jeff Gastoncef50b92017-08-23 15:41:35 -070061 io.Closer
62}
63
Nan Zhang9067b042017-03-17 14:04:43 -070064type pathMapping struct {
65 dest, src string
Nan Zhangf281bd82017-04-25 16:47:45 -070066 zipMethod uint16
67}
68
Jeff Gastonc3bdc972017-10-12 12:18:19 -070069type FileArg struct {
70 PathPrefixInZip, SourcePrefixToStrip string
71 SourceFiles []string
Colin Crossb7c69112018-09-18 16:51:43 -070072 JunkPaths bool
Jeff Gastonc3bdc972017-10-12 12:18:19 -070073 GlobDir string
74}
75
Colin Crossfe945b42018-09-27 15:00:07 -070076type FileArgsBuilder struct {
77 state FileArg
78 err error
79 fs pathtools.FileSystem
80
81 fileArgs []FileArg
82}
83
84func NewFileArgsBuilder() *FileArgsBuilder {
85 return &FileArgsBuilder{
86 fs: pathtools.OsFs,
87 }
88}
89
90func (b *FileArgsBuilder) JunkPaths(v bool) *FileArgsBuilder {
91 b.state.JunkPaths = v
92 b.state.SourcePrefixToStrip = ""
93 return b
94}
95
96func (b *FileArgsBuilder) SourcePrefixToStrip(prefixToStrip string) *FileArgsBuilder {
97 b.state.JunkPaths = false
98 b.state.SourcePrefixToStrip = prefixToStrip
99 return b
100}
101
102func (b *FileArgsBuilder) PathPrefixInZip(rootPrefix string) *FileArgsBuilder {
103 b.state.PathPrefixInZip = rootPrefix
104 return b
105}
106
107func (b *FileArgsBuilder) File(name string) *FileArgsBuilder {
108 if b.err != nil {
109 return b
110 }
111
112 arg := b.state
113 arg.SourceFiles = []string{name}
114 b.fileArgs = append(b.fileArgs, arg)
115 return b
116}
117
118func (b *FileArgsBuilder) Dir(name string) *FileArgsBuilder {
119 if b.err != nil {
120 return b
121 }
122
123 arg := b.state
124 arg.GlobDir = name
125 b.fileArgs = append(b.fileArgs, arg)
126 return b
127}
128
129func (b *FileArgsBuilder) List(name string) *FileArgsBuilder {
130 if b.err != nil {
131 return b
132 }
133
134 f, err := b.fs.Open(name)
135 if err != nil {
136 b.err = err
137 return b
138 }
139 defer f.Close()
140
141 list, err := ioutil.ReadAll(f)
142 if err != nil {
143 b.err = err
144 return b
145 }
146
147 arg := b.state
Jiyong Park04bbf982019-11-04 13:18:41 +0900148 arg.SourceFiles = strings.Fields(string(list))
Colin Crossfe945b42018-09-27 15:00:07 -0700149 b.fileArgs = append(b.fileArgs, arg)
150 return b
151}
152
Colin Cross053fca12020-08-19 13:51:47 -0700153func (b *FileArgsBuilder) RspFile(name string) *FileArgsBuilder {
154 if b.err != nil {
155 return b
156 }
157
158 f, err := b.fs.Open(name)
159 if err != nil {
160 b.err = err
161 return b
162 }
163 defer f.Close()
164
165 list, err := ioutil.ReadAll(f)
166 if err != nil {
167 b.err = err
168 return b
169 }
170
171 arg := b.state
172 arg.SourceFiles = ReadRespFile(list)
173 b.fileArgs = append(b.fileArgs, arg)
174 return b
175}
176
Colin Crossfe945b42018-09-27 15:00:07 -0700177func (b *FileArgsBuilder) Error() error {
178 if b == nil {
179 return nil
180 }
181 return b.err
182}
183
184func (b *FileArgsBuilder) FileArgs() []FileArg {
185 if b == nil {
186 return nil
187 }
188 return b.fileArgs
189}
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700190
Colin Cross1d98ee22018-09-18 17:05:15 -0700191type IncorrectRelativeRootError struct {
192 RelativeRoot string
193 Path string
194}
195
196func (x IncorrectRelativeRootError) Error() string {
197 return fmt.Sprintf("path %q is outside relative root %q", x.Path, x.RelativeRoot)
198}
199
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700200type ZipWriter struct {
Colin Crosse5580972017-08-30 17:40:21 -0700201 time time.Time
202 createdFiles map[string]string
203 createdDirs map[string]string
204 directories bool
Colin Crosse19c7932015-04-24 15:08:38 -0700205
Dan Willemsen017d8932016-08-04 15:43:03 -0700206 errors chan error
207 writeOps chan chan *zipEntry
208
Jeff Gaston175f34c2017-08-17 21:43:21 -0700209 cpuRateLimiter *CPURateLimiter
210 memoryRateLimiter *MemoryRateLimiter
Dan Willemsen017d8932016-08-04 15:43:03 -0700211
212 compressorPool sync.Pool
213 compLevel int
Colin Cross05518bc2018-09-27 15:06:19 -0700214
Colin Cross4be8f9e2018-09-28 15:16:48 -0700215 followSymlinks pathtools.ShouldFollowSymlinks
216 ignoreMissingFiles bool
Colin Cross09f11052018-09-21 15:12:39 -0700217
Colin Cross4be8f9e2018-09-28 15:16:48 -0700218 stderr io.Writer
219 fs pathtools.FileSystem
Dan Willemsen017d8932016-08-04 15:43:03 -0700220}
221
222type zipEntry struct {
223 fh *zip.FileHeader
224
225 // List of delayed io.Reader
226 futureReaders chan chan io.Reader
Jeff Gaston175f34c2017-08-17 21:43:21 -0700227
228 // Only used for passing into the MemoryRateLimiter to ensure we
229 // release as much memory as much as we request
230 allocatedSize int64
Colin Cross2fe66872015-03-30 17:20:39 -0700231}
232
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700233type ZipArgs struct {
Colin Crossfe945b42018-09-27 15:00:07 -0700234 FileArgs []FileArg
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700235 OutputFilePath string
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700236 EmulateJar bool
Colin Cross9cb51db2019-06-17 14:12:41 -0700237 SrcJar bool
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700238 AddDirectoryEntriesToZip bool
239 CompressionLevel int
240 ManifestSourcePath string
241 NumParallelJobs int
242 NonDeflatedFiles map[string]bool
Colin Crossf83c1502017-11-10 13:11:02 -0800243 WriteIfChanged bool
Colin Cross09f11052018-09-21 15:12:39 -0700244 StoreSymlinks bool
Colin Cross4be8f9e2018-09-28 15:16:48 -0700245 IgnoreMissingFiles bool
Colin Cross09f11052018-09-21 15:12:39 -0700246
Colin Cross4be8f9e2018-09-28 15:16:48 -0700247 Stderr io.Writer
Colin Cross09f11052018-09-21 15:12:39 -0700248 Filesystem pathtools.FileSystem
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700249}
Colin Cross2fe66872015-03-30 17:20:39 -0700250
Nan Zhang674dd932018-01-26 18:30:36 -0800251const NOQUOTE = '\x00'
252
253func ReadRespFile(bytes []byte) []string {
254 var args []string
255 var arg []rune
256
257 isEscaping := false
258 quotingStart := NOQUOTE
259 for _, c := range string(bytes) {
260 switch {
261 case isEscaping:
262 if quotingStart == '"' {
263 if !(c == '"' || c == '\\') {
264 // '\"' or '\\' will be escaped under double quoting.
265 arg = append(arg, '\\')
266 }
267 }
268 arg = append(arg, c)
269 isEscaping = false
270 case c == '\\' && quotingStart != '\'':
271 isEscaping = true
272 case quotingStart == NOQUOTE && (c == '\'' || c == '"'):
273 quotingStart = c
274 case quotingStart != NOQUOTE && c == quotingStart:
275 quotingStart = NOQUOTE
276 case quotingStart == NOQUOTE && unicode.IsSpace(c):
277 // Current character is a space outside quotes
278 if len(arg) != 0 {
279 args = append(args, string(arg))
280 }
281 arg = arg[:0]
282 default:
283 arg = append(arg, c)
284 }
285 }
286
287 if len(arg) != 0 {
288 args = append(args, string(arg))
289 }
290
291 return args
292}
293
Colin Cross05518bc2018-09-27 15:06:19 -0700294func ZipTo(args ZipArgs, w io.Writer) error {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700295 if args.EmulateJar {
296 args.AddDirectoryEntriesToZip = true
Jeff Gaston8edbb3a2017-08-22 20:05:28 -0700297 }
298
Colin Cross09f11052018-09-21 15:12:39 -0700299 // Have Glob follow symlinks if they are not being stored as symlinks in the zip file.
300 followSymlinks := pathtools.ShouldFollowSymlinks(!args.StoreSymlinks)
301
Colin Cross05518bc2018-09-27 15:06:19 -0700302 z := &ZipWriter{
Colin Cross4be8f9e2018-09-28 15:16:48 -0700303 time: jar.DefaultTime,
304 createdDirs: make(map[string]string),
305 createdFiles: make(map[string]string),
306 directories: args.AddDirectoryEntriesToZip,
307 compLevel: args.CompressionLevel,
308 followSymlinks: followSymlinks,
309 ignoreMissingFiles: args.IgnoreMissingFiles,
310 stderr: args.Stderr,
311 fs: args.Filesystem,
Colin Cross2fe66872015-03-30 17:20:39 -0700312 }
Colin Cross05518bc2018-09-27 15:06:19 -0700313
314 if z.fs == nil {
315 z.fs = pathtools.OsFs
316 }
317
Colin Cross4be8f9e2018-09-28 15:16:48 -0700318 if z.stderr == nil {
319 z.stderr = os.Stderr
320 }
321
Nan Zhang9067b042017-03-17 14:04:43 -0700322 pathMappings := []pathMapping{}
Nan Zhang9067b042017-03-17 14:04:43 -0700323
Colin Crossd3216292018-09-14 15:06:31 -0700324 noCompression := args.CompressionLevel == 0
325
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700326 for _, fa := range args.FileArgs {
Colin Cross1d98ee22018-09-18 17:05:15 -0700327 var srcs []string
328 for _, s := range fa.SourceFiles {
329 s = strings.TrimSpace(s)
330 if s == "" {
331 continue
332 }
333
Colin Cross09f11052018-09-21 15:12:39 -0700334 globbed, _, err := z.fs.Glob(s, nil, followSymlinks)
Colin Cross1d98ee22018-09-18 17:05:15 -0700335 if err != nil {
336 return err
337 }
338 if len(globbed) == 0 {
Colin Cross4be8f9e2018-09-28 15:16:48 -0700339 err := &os.PathError{
340 Op: "lstat",
Colin Cross1d98ee22018-09-18 17:05:15 -0700341 Path: s,
342 Err: os.ErrNotExist,
343 }
Colin Cross4be8f9e2018-09-28 15:16:48 -0700344 if args.IgnoreMissingFiles {
Dan Willemsenedc934c2018-12-27 12:41:25 -0800345 fmt.Fprintln(z.stderr, "warning:", err)
Colin Cross4be8f9e2018-09-28 15:16:48 -0700346 } else {
347 return err
348 }
Colin Cross1d98ee22018-09-18 17:05:15 -0700349 }
350 srcs = append(srcs, globbed...)
351 }
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700352 if fa.GlobDir != "" {
Colin Cross1d98ee22018-09-18 17:05:15 -0700353 if exists, isDir, err := z.fs.Exists(fa.GlobDir); err != nil {
354 return err
Colin Cross4be8f9e2018-09-28 15:16:48 -0700355 } else if !exists && !args.IgnoreMissingFiles {
356 err := &os.PathError{
357 Op: "lstat",
Colin Cross1d98ee22018-09-18 17:05:15 -0700358 Path: fa.GlobDir,
359 Err: os.ErrNotExist,
360 }
Colin Cross4be8f9e2018-09-28 15:16:48 -0700361 if args.IgnoreMissingFiles {
Dan Willemsenedc934c2018-12-27 12:41:25 -0800362 fmt.Fprintln(z.stderr, "warning:", err)
Colin Cross4be8f9e2018-09-28 15:16:48 -0700363 } else {
364 return err
365 }
366 } else if !isDir && !args.IgnoreMissingFiles {
367 err := &os.PathError{
368 Op: "lstat",
Colin Cross1d98ee22018-09-18 17:05:15 -0700369 Path: fa.GlobDir,
370 Err: syscall.ENOTDIR,
371 }
Colin Cross4be8f9e2018-09-28 15:16:48 -0700372 if args.IgnoreMissingFiles {
Dan Willemsenedc934c2018-12-27 12:41:25 -0800373 fmt.Fprintln(z.stderr, "warning:", err)
Colin Cross4be8f9e2018-09-28 15:16:48 -0700374 } else {
375 return err
376 }
Colin Cross1d98ee22018-09-18 17:05:15 -0700377 }
Colin Cross09f11052018-09-21 15:12:39 -0700378 globbed, _, err := z.fs.Glob(filepath.Join(fa.GlobDir, "**/*"), nil, followSymlinks)
Colin Cross1d98ee22018-09-18 17:05:15 -0700379 if err != nil {
380 return err
381 }
382 srcs = append(srcs, globbed...)
Colin Cross7b10cf12017-08-30 14:12:21 -0700383 }
384 for _, src := range srcs {
Colin Crossb7c69112018-09-18 16:51:43 -0700385 err := fillPathPairs(fa, src, &pathMappings, args.NonDeflatedFiles, noCompression)
Colin Crossd3216292018-09-14 15:06:31 -0700386 if err != nil {
Colin Cross05518bc2018-09-27 15:06:19 -0700387 return err
Nan Zhang9067b042017-03-17 14:04:43 -0700388 }
389 }
390 }
391
Colin Cross9cb51db2019-06-17 14:12:41 -0700392 return z.write(w, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.SrcJar, args.NumParallelJobs)
Colin Cross05518bc2018-09-27 15:06:19 -0700393}
394
395func Zip(args ZipArgs) error {
396 if args.OutputFilePath == "" {
397 return fmt.Errorf("output file path must be nonempty")
398 }
399
Colin Crossf83c1502017-11-10 13:11:02 -0800400 buf := &bytes.Buffer{}
401 var out io.Writer = buf
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700402
Colin Crossf83c1502017-11-10 13:11:02 -0800403 if !args.WriteIfChanged {
404 f, err := os.Create(args.OutputFilePath)
405 if err != nil {
406 return err
407 }
408
409 defer f.Close()
410 defer func() {
411 if err != nil {
412 os.Remove(args.OutputFilePath)
413 }
414 }()
415
416 out = f
417 }
418
Colin Cross05518bc2018-09-27 15:06:19 -0700419 err := ZipTo(args, out)
Colin Crossf83c1502017-11-10 13:11:02 -0800420 if err != nil {
421 return err
422 }
423
424 if args.WriteIfChanged {
425 err := pathtools.WriteFileIfChanged(args.OutputFilePath, buf.Bytes(), 0666)
426 if err != nil {
427 return err
428 }
429 }
430
431 return nil
Colin Cross2fe66872015-03-30 17:20:39 -0700432}
433
Colin Crossb7c69112018-09-18 16:51:43 -0700434func fillPathPairs(fa FileArg, src string, pathMappings *[]pathMapping,
Colin Crossd3216292018-09-14 15:06:31 -0700435 nonDeflatedFiles map[string]bool, noCompression bool) error {
436
Colin Crossb7c69112018-09-18 16:51:43 -0700437 var dest string
438
439 if fa.JunkPaths {
440 dest = filepath.Base(src)
441 } else {
442 var err error
443 dest, err = filepath.Rel(fa.SourcePrefixToStrip, src)
444 if err != nil {
445 return err
446 }
Colin Cross1d98ee22018-09-18 17:05:15 -0700447 if strings.HasPrefix(dest, "../") {
448 return IncorrectRelativeRootError{
449 Path: src,
450 RelativeRoot: fa.SourcePrefixToStrip,
451 }
452 }
453
Nan Zhang9067b042017-03-17 14:04:43 -0700454 }
Colin Crossb7c69112018-09-18 16:51:43 -0700455 dest = filepath.Join(fa.PathPrefixInZip, dest)
Nan Zhang9067b042017-03-17 14:04:43 -0700456
Nan Zhangf281bd82017-04-25 16:47:45 -0700457 zipMethod := zip.Deflate
Colin Crossd3216292018-09-14 15:06:31 -0700458 if _, found := nonDeflatedFiles[dest]; found || noCompression {
Nan Zhangf281bd82017-04-25 16:47:45 -0700459 zipMethod = zip.Store
460 }
461 *pathMappings = append(*pathMappings,
462 pathMapping{dest: dest, src: src, zipMethod: zipMethod})
Nan Zhang9067b042017-03-17 14:04:43 -0700463
464 return nil
465}
466
Jeff Gastona2976952017-08-22 17:51:25 -0700467func jarSort(mappings []pathMapping) {
468 less := func(i int, j int) (smaller bool) {
469 return jar.EntryNamesLess(mappings[i].dest, mappings[j].dest)
470 }
471 sort.SliceStable(mappings, less)
472}
473
Colin Cross9cb51db2019-06-17 14:12:41 -0700474func (z *ZipWriter) write(f io.Writer, pathMappings []pathMapping, manifest string, emulateJar, srcJar bool,
475 parallelJobs int) error {
476
Dan Willemsen017d8932016-08-04 15:43:03 -0700477 z.errors = make(chan error)
478 defer close(z.errors)
Colin Cross2fe66872015-03-30 17:20:39 -0700479
Dan Willemsen017d8932016-08-04 15:43:03 -0700480 // This channel size can be essentially unlimited -- it's used as a fifo
481 // queue decouple the CPU and IO loads. Directories don't require any
482 // compression time, but still cost some IO. Similar with small files that
483 // can be very fast to compress. Some files that are more difficult to
484 // compress won't take a corresponding longer time writing out.
485 //
486 // The optimum size here depends on your CPU and IO characteristics, and
487 // the the layout of your zip file. 1000 was chosen mostly at random as
488 // something that worked reasonably well for a test file.
489 //
490 // The RateLimit object will put the upper bounds on the number of
491 // parallel compressions and outstanding buffers.
492 z.writeOps = make(chan chan *zipEntry, 1000)
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700493 z.cpuRateLimiter = NewCPURateLimiter(int64(parallelJobs))
Jeff Gaston175f34c2017-08-17 21:43:21 -0700494 z.memoryRateLimiter = NewMemoryRateLimiter(0)
495 defer func() {
496 z.cpuRateLimiter.Stop()
497 z.memoryRateLimiter.Stop()
498 }()
Jeff Gastona2976952017-08-22 17:51:25 -0700499
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700500 if manifest != "" && !emulateJar {
Colin Cross635acc92017-09-12 22:50:46 -0700501 return errors.New("must specify --jar when specifying a manifest via -m")
Jeff Gastona2976952017-08-22 17:51:25 -0700502 }
503
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700504 if emulateJar {
Colin Cross635acc92017-09-12 22:50:46 -0700505 // manifest may be empty, in which case addManifest will fill in a default
506 pathMappings = append(pathMappings, pathMapping{jar.ManifestFile, manifest, zip.Deflate})
507
Jeff Gastona2976952017-08-22 17:51:25 -0700508 jarSort(pathMappings)
509 }
510
Dan Willemsen017d8932016-08-04 15:43:03 -0700511 go func() {
512 var err error
513 defer close(z.writeOps)
514
Nan Zhang9067b042017-03-17 14:04:43 -0700515 for _, ele := range pathMappings {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700516 if emulateJar && ele.dest == jar.ManifestFile {
Jeff Gastoncef50b92017-08-23 15:41:35 -0700517 err = z.addManifest(ele.dest, ele.src, ele.zipMethod)
518 } else {
Colin Cross9cb51db2019-06-17 14:12:41 -0700519 err = z.addFile(ele.dest, ele.src, ele.zipMethod, emulateJar, srcJar)
Jeff Gastoncef50b92017-08-23 15:41:35 -0700520 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700521 if err != nil {
522 z.errors <- err
523 return
524 }
525 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700526 }()
527
528 zipw := zip.NewWriter(f)
529
530 var currentWriteOpChan chan *zipEntry
531 var currentWriter io.WriteCloser
532 var currentReaders chan chan io.Reader
533 var currentReader chan io.Reader
534 var done bool
535
536 for !done {
537 var writeOpsChan chan chan *zipEntry
538 var writeOpChan chan *zipEntry
539 var readersChan chan chan io.Reader
540
541 if currentReader != nil {
542 // Only read and process errors
543 } else if currentReaders != nil {
544 readersChan = currentReaders
545 } else if currentWriteOpChan != nil {
546 writeOpChan = currentWriteOpChan
547 } else {
548 writeOpsChan = z.writeOps
549 }
550
551 select {
552 case writeOp, ok := <-writeOpsChan:
553 if !ok {
554 done = true
555 }
556
557 currentWriteOpChan = writeOp
558
559 case op := <-writeOpChan:
560 currentWriteOpChan = nil
561
Colin Crossf83c1502017-11-10 13:11:02 -0800562 var err error
Dan Willemsen017d8932016-08-04 15:43:03 -0700563 if op.fh.Method == zip.Deflate {
564 currentWriter, err = zipw.CreateCompressedHeader(op.fh)
565 } else {
566 var zw io.Writer
Jeff Gastonc5eb66d2017-08-24 14:11:27 -0700567
568 op.fh.CompressedSize64 = op.fh.UncompressedSize64
569
570 zw, err = zipw.CreateHeaderAndroid(op.fh)
Dan Willemsen017d8932016-08-04 15:43:03 -0700571 currentWriter = nopCloser{zw}
572 }
573 if err != nil {
574 return err
575 }
576
577 currentReaders = op.futureReaders
578 if op.futureReaders == nil {
579 currentWriter.Close()
580 currentWriter = nil
581 }
Jeff Gaston175f34c2017-08-17 21:43:21 -0700582 z.memoryRateLimiter.Finish(op.allocatedSize)
Dan Willemsen017d8932016-08-04 15:43:03 -0700583
584 case futureReader, ok := <-readersChan:
585 if !ok {
586 // Done with reading
587 currentWriter.Close()
588 currentWriter = nil
589 currentReaders = nil
590 }
591
592 currentReader = futureReader
593
594 case reader := <-currentReader:
Colin Crossf83c1502017-11-10 13:11:02 -0800595 _, err := io.Copy(currentWriter, reader)
Dan Willemsen017d8932016-08-04 15:43:03 -0700596 if err != nil {
597 return err
598 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700599
600 currentReader = nil
601
Colin Crossf83c1502017-11-10 13:11:02 -0800602 case err := <-z.errors:
Colin Cross2fe66872015-03-30 17:20:39 -0700603 return err
604 }
605 }
606
Dan Willemsen017d8932016-08-04 15:43:03 -0700607 // One last chance to catch an error
608 select {
Colin Crossf83c1502017-11-10 13:11:02 -0800609 case err := <-z.errors:
Dan Willemsen017d8932016-08-04 15:43:03 -0700610 return err
611 default:
612 zipw.Close()
613 return nil
Colin Cross2fe66872015-03-30 17:20:39 -0700614 }
Colin Cross2fe66872015-03-30 17:20:39 -0700615}
616
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700617// imports (possibly with compression) <src> into the zip at sub-path <dest>
Colin Cross9cb51db2019-06-17 14:12:41 -0700618func (z *ZipWriter) addFile(dest, src string, method uint16, emulateJar, srcJar bool) error {
Dan Willemsen017d8932016-08-04 15:43:03 -0700619 var fileSize int64
Dan Willemsen10462b32017-03-15 19:02:51 -0700620 var executable bool
Dan Willemsen017d8932016-08-04 15:43:03 -0700621
Colin Cross09f11052018-09-21 15:12:39 -0700622 var s os.FileInfo
623 var err error
624 if z.followSymlinks {
625 s, err = z.fs.Stat(src)
626 } else {
627 s, err = z.fs.Lstat(src)
628 }
629
630 if err != nil {
Colin Cross4be8f9e2018-09-28 15:16:48 -0700631 if os.IsNotExist(err) && z.ignoreMissingFiles {
632 fmt.Fprintln(z.stderr, "warning:", err)
633 return nil
634 }
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700635 return err
Colin Cross9cb51db2019-06-17 14:12:41 -0700636 }
637
638 createParentDirs := func(dest, src string) error {
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700639 if err := z.writeDirectory(filepath.Dir(dest), src, emulateJar); err != nil {
Colin Crosse5580972017-08-30 17:40:21 -0700640 return err
641 }
642
643 if prev, exists := z.createdDirs[dest]; exists {
644 return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
645 }
646 if prev, exists := z.createdFiles[dest]; exists {
647 return fmt.Errorf("destination %q has two files %q and %q", dest, prev, src)
648 }
649
650 z.createdFiles[dest] = src
651
Colin Cross9cb51db2019-06-17 14:12:41 -0700652 return nil
653 }
654
655 if s.IsDir() {
656 if z.directories {
657 return z.writeDirectory(dest, src, emulateJar)
658 }
659 return nil
660 } else if s.Mode()&os.ModeSymlink != 0 {
661 err = createParentDirs(dest, src)
662 if err != nil {
663 return err
664 }
665
666 return z.writeSymlink(dest, src)
667 } else if s.Mode().IsRegular() {
668 r, err := z.fs.Open(src)
669 if err != nil {
670 return err
671 }
672
673 if srcJar && filepath.Ext(src) == ".java" {
674 // rewrite the destination using the package path if it can be determined
675 pkg, err := jar.JavaPackage(r, src)
676 if err != nil {
677 // ignore errors for now, leaving the file at in its original location in the zip
678 } else {
679 dest = filepath.Join(filepath.Join(strings.Split(pkg, ".")...), filepath.Base(src))
680 }
681
682 _, err = r.Seek(0, io.SeekStart)
683 if err != nil {
684 return err
685 }
Colin Crosse5580972017-08-30 17:40:21 -0700686 }
687
Dan Willemsen017d8932016-08-04 15:43:03 -0700688 fileSize = s.Size()
Dan Willemsen10462b32017-03-15 19:02:51 -0700689 executable = s.Mode()&0100 != 0
Colin Cross957cc4e2015-04-24 15:10:32 -0700690
Colin Cross9cb51db2019-06-17 14:12:41 -0700691 header := &zip.FileHeader{
692 Name: dest,
693 Method: method,
694 UncompressedSize64: uint64(fileSize),
695 }
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700696
Colin Cross9cb51db2019-06-17 14:12:41 -0700697 if executable {
698 header.SetMode(0700)
699 }
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700700
Colin Cross9cb51db2019-06-17 14:12:41 -0700701 err = createParentDirs(dest, src)
702 if err != nil {
703 return err
704 }
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700705
Colin Cross9cb51db2019-06-17 14:12:41 -0700706 return z.writeFileContents(header, r)
707 } else {
708 return fmt.Errorf("%s is not a file, directory, or symlink", src)
709 }
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700710}
711
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700712func (z *ZipWriter) addManifest(dest string, src string, method uint16) error {
Colin Crosse5580972017-08-30 17:40:21 -0700713 if prev, exists := z.createdDirs[dest]; exists {
714 return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
715 }
716 if prev, exists := z.createdFiles[dest]; exists {
717 return fmt.Errorf("destination %q has two files %q and %q", dest, prev, src)
718 }
719
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700720 if err := z.writeDirectory(filepath.Dir(dest), src, true); err != nil {
Colin Cross635acc92017-09-12 22:50:46 -0700721 return err
Jeff Gastoncef50b92017-08-23 15:41:35 -0700722 }
723
Colin Cross05518bc2018-09-27 15:06:19 -0700724 var contents []byte
725 if src != "" {
726 f, err := z.fs.Open(src)
727 if err != nil {
728 return err
729 }
730
731 contents, err = ioutil.ReadAll(f)
732 f.Close()
733 if err != nil {
734 return err
735 }
736 }
737
738 fh, buf, err := jar.ManifestFileContents(contents)
Colin Cross635acc92017-09-12 22:50:46 -0700739 if err != nil {
740 return err
Jeff Gastoncef50b92017-08-23 15:41:35 -0700741 }
742
Colin Cross635acc92017-09-12 22:50:46 -0700743 reader := &byteReaderCloser{bytes.NewReader(buf), ioutil.NopCloser(nil)}
744
745 return z.writeFileContents(fh, reader)
Jeff Gastoncef50b92017-08-23 15:41:35 -0700746}
747
Colin Cross05518bc2018-09-27 15:06:19 -0700748func (z *ZipWriter) writeFileContents(header *zip.FileHeader, r pathtools.ReaderAtSeekerCloser) (err error) {
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700749
750 header.SetModTime(z.time)
751
Dan Willemsen017d8932016-08-04 15:43:03 -0700752 compressChan := make(chan *zipEntry, 1)
753 z.writeOps <- compressChan
754
755 // Pre-fill a zipEntry, it will be sent in the compressChan once
756 // we're sure about the Method and CRC.
757 ze := &zipEntry{
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700758 fh: header,
Dan Willemsen10462b32017-03-15 19:02:51 -0700759 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700760
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700761 ze.allocatedSize = int64(header.UncompressedSize64)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700762 z.cpuRateLimiter.Request()
763 z.memoryRateLimiter.Request(ze.allocatedSize)
Dan Willemsen017d8932016-08-04 15:43:03 -0700764
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700765 fileSize := int64(header.UncompressedSize64)
766 if fileSize == 0 {
767 fileSize = int64(header.UncompressedSize)
768 }
769
770 if header.Method == zip.Deflate && fileSize >= minParallelFileSize {
Dan Willemsen017d8932016-08-04 15:43:03 -0700771 wg := new(sync.WaitGroup)
772
773 // Allocate enough buffer to hold all readers. We'll limit
774 // this based on actual buffer sizes in RateLimit.
775 ze.futureReaders = make(chan chan io.Reader, (fileSize/parallelBlockSize)+1)
776
777 // Calculate the CRC in the background, since reading the entire
778 // file could take a while.
779 //
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700780 // We could split this up into chunks as well, but it's faster
Dan Willemsen017d8932016-08-04 15:43:03 -0700781 // than the compression. Due to the Go Zip API, we also need to
782 // know the result before we can begin writing the compressed
783 // data out to the zipfile.
784 wg.Add(1)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700785 go z.crcFile(r, ze, compressChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700786
787 for start := int64(0); start < fileSize; start += parallelBlockSize {
788 sr := io.NewSectionReader(r, start, parallelBlockSize)
789 resultChan := make(chan io.Reader, 1)
790 ze.futureReaders <- resultChan
791
Jeff Gaston175f34c2017-08-17 21:43:21 -0700792 z.cpuRateLimiter.Request()
Dan Willemsen017d8932016-08-04 15:43:03 -0700793
794 last := !(start+parallelBlockSize < fileSize)
795 var dict []byte
796 if start >= windowSize {
797 dict, err = ioutil.ReadAll(io.NewSectionReader(r, start-windowSize, windowSize))
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700798 if err != nil {
799 return err
800 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700801 }
802
803 wg.Add(1)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700804 go z.compressPartialFile(sr, dict, last, resultChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700805 }
806
807 close(ze.futureReaders)
808
809 // Close the file handle after all readers are done
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700810 go func(wg *sync.WaitGroup, closer io.Closer) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700811 wg.Wait()
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700812 closer.Close()
Dan Willemsen017d8932016-08-04 15:43:03 -0700813 }(wg, r)
814 } else {
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700815 go func() {
816 z.compressWholeFile(ze, r, compressChan)
817 r.Close()
818 }()
Dan Willemsen017d8932016-08-04 15:43:03 -0700819 }
820
821 return nil
822}
823
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700824func (z *ZipWriter) crcFile(r io.Reader, ze *zipEntry, resultChan chan *zipEntry, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700825 defer wg.Done()
Jeff Gaston175f34c2017-08-17 21:43:21 -0700826 defer z.cpuRateLimiter.Finish()
Dan Willemsen017d8932016-08-04 15:43:03 -0700827
828 crc := crc32.NewIEEE()
829 _, err := io.Copy(crc, r)
830 if err != nil {
831 z.errors <- err
832 return
833 }
834
835 ze.fh.CRC32 = crc.Sum32()
836 resultChan <- ze
837 close(resultChan)
838}
839
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700840func (z *ZipWriter) compressPartialFile(r io.Reader, dict []byte, last bool, resultChan chan io.Reader, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700841 defer wg.Done()
842
843 result, err := z.compressBlock(r, dict, last)
844 if err != nil {
845 z.errors <- err
846 return
847 }
848
Jeff Gaston175f34c2017-08-17 21:43:21 -0700849 z.cpuRateLimiter.Finish()
850
Dan Willemsen017d8932016-08-04 15:43:03 -0700851 resultChan <- result
852}
853
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700854func (z *ZipWriter) compressBlock(r io.Reader, dict []byte, last bool) (*bytes.Buffer, error) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700855 buf := new(bytes.Buffer)
856 var fw *flate.Writer
857 var err error
858 if len(dict) > 0 {
859 // There's no way to Reset a Writer with a new dictionary, so
860 // don't use the Pool
861 fw, err = flate.NewWriterDict(buf, z.compLevel, dict)
862 } else {
863 var ok bool
864 if fw, ok = z.compressorPool.Get().(*flate.Writer); ok {
865 fw.Reset(buf)
866 } else {
867 fw, err = flate.NewWriter(buf, z.compLevel)
868 }
869 defer z.compressorPool.Put(fw)
870 }
871 if err != nil {
872 return nil, err
873 }
874
875 _, err = io.Copy(fw, r)
876 if err != nil {
877 return nil, err
878 }
879 if last {
880 fw.Close()
881 } else {
882 fw.Flush()
883 }
884
885 return buf, nil
886}
887
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700888func (z *ZipWriter) compressWholeFile(ze *zipEntry, r io.ReadSeeker, compressChan chan *zipEntry) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700889
Dan Willemsen017d8932016-08-04 15:43:03 -0700890 crc := crc32.NewIEEE()
Dan Willemsena8b55022017-03-15 21:49:26 -0700891 _, err := io.Copy(crc, r)
Colin Cross2fe66872015-03-30 17:20:39 -0700892 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700893 z.errors <- err
894 return
Colin Cross2fe66872015-03-30 17:20:39 -0700895 }
896
Dan Willemsena8b55022017-03-15 21:49:26 -0700897 ze.fh.CRC32 = crc.Sum32()
Colin Cross2fe66872015-03-30 17:20:39 -0700898
Dan Willemsen017d8932016-08-04 15:43:03 -0700899 _, err = r.Seek(0, 0)
Colin Cross2fe66872015-03-30 17:20:39 -0700900 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700901 z.errors <- err
902 return
Colin Cross2fe66872015-03-30 17:20:39 -0700903 }
904
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700905 readFile := func(reader io.ReadSeeker) ([]byte, error) {
906 _, err := reader.Seek(0, 0)
Nan Zhangf281bd82017-04-25 16:47:45 -0700907 if err != nil {
908 return nil, err
909 }
910
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700911 buf, err := ioutil.ReadAll(reader)
Nan Zhangf281bd82017-04-25 16:47:45 -0700912 if err != nil {
913 return nil, err
914 }
915
916 return buf, nil
917 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700918
Dan Willemsena8b55022017-03-15 21:49:26 -0700919 ze.futureReaders = make(chan chan io.Reader, 1)
Dan Willemsen017d8932016-08-04 15:43:03 -0700920 futureReader := make(chan io.Reader, 1)
921 ze.futureReaders <- futureReader
922 close(ze.futureReaders)
923
Nan Zhangf281bd82017-04-25 16:47:45 -0700924 if ze.fh.Method == zip.Deflate {
925 compressed, err := z.compressBlock(r, nil, true)
926 if err != nil {
927 z.errors <- err
928 return
929 }
930 if uint64(compressed.Len()) < ze.fh.UncompressedSize64 {
931 futureReader <- compressed
Nan Zhangf281bd82017-04-25 16:47:45 -0700932 } else {
933 buf, err := readFile(r)
934 if err != nil {
935 z.errors <- err
936 return
937 }
938 ze.fh.Method = zip.Store
939 futureReader <- bytes.NewReader(buf)
Nan Zhangf281bd82017-04-25 16:47:45 -0700940 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700941 } else {
Nan Zhangf281bd82017-04-25 16:47:45 -0700942 buf, err := readFile(r)
Dan Willemsen017d8932016-08-04 15:43:03 -0700943 if err != nil {
944 z.errors <- err
945 return
946 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700947 ze.fh.Method = zip.Store
948 futureReader <- bytes.NewReader(buf)
Dan Willemsen017d8932016-08-04 15:43:03 -0700949 }
Nan Zhangf281bd82017-04-25 16:47:45 -0700950
Jeff Gaston175f34c2017-08-17 21:43:21 -0700951 z.cpuRateLimiter.Finish()
952
Dan Willemsen017d8932016-08-04 15:43:03 -0700953 close(futureReader)
954
955 compressChan <- ze
956 close(compressChan)
Colin Cross2fe66872015-03-30 17:20:39 -0700957}
Colin Crosse19c7932015-04-24 15:08:38 -0700958
Colin Crosse5580972017-08-30 17:40:21 -0700959// writeDirectory annotates that dir is a directory created for the src file or directory, and adds
960// the directory entry to the zip file if directories are enabled.
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700961func (z *ZipWriter) writeDirectory(dir string, src string, emulateJar bool) error {
Jeff Gaston2d174132017-08-15 18:05:56 -0700962 // clean the input
Colin Crosse5580972017-08-30 17:40:21 -0700963 dir = filepath.Clean(dir)
Jeff Gaston2d174132017-08-15 18:05:56 -0700964
965 // discover any uncreated directories in the path
966 zipDirs := []string{}
Colin Crosse5580972017-08-30 17:40:21 -0700967 for dir != "" && dir != "." {
968 if _, exists := z.createdDirs[dir]; exists {
969 break
970 }
Jeff Gaston2d174132017-08-15 18:05:56 -0700971
Colin Crosse5580972017-08-30 17:40:21 -0700972 if prev, exists := z.createdFiles[dir]; exists {
973 return fmt.Errorf("destination %q is both a directory %q and a file %q", dir, src, prev)
974 }
975
976 z.createdDirs[dir] = src
Jeff Gaston2d174132017-08-15 18:05:56 -0700977 // parent directories precede their children
Colin Crosse5580972017-08-30 17:40:21 -0700978 zipDirs = append([]string{dir}, zipDirs...)
Jeff Gaston2d174132017-08-15 18:05:56 -0700979
Colin Crosse5580972017-08-30 17:40:21 -0700980 dir = filepath.Dir(dir)
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700981 }
982
Colin Crosse5580972017-08-30 17:40:21 -0700983 if z.directories {
984 // make a directory entry for each uncreated directory
985 for _, cleanDir := range zipDirs {
Colin Cross635acc92017-09-12 22:50:46 -0700986 var dirHeader *zip.FileHeader
Colin Crosse19c7932015-04-24 15:08:38 -0700987
Jeff Gastonc3bdc972017-10-12 12:18:19 -0700988 if emulateJar && cleanDir+"/" == jar.MetaDir {
Colin Cross635acc92017-09-12 22:50:46 -0700989 dirHeader = jar.MetaDirFileHeader()
990 } else {
991 dirHeader = &zip.FileHeader{
992 Name: cleanDir + "/",
993 }
994 dirHeader.SetMode(0700 | os.ModeDir)
Colin Crosse5580972017-08-30 17:40:21 -0700995 }
Jeff Gaston8edbb3a2017-08-22 20:05:28 -0700996
Colin Cross635acc92017-09-12 22:50:46 -0700997 dirHeader.SetModTime(z.time)
998
Colin Crosse5580972017-08-30 17:40:21 -0700999 ze := make(chan *zipEntry, 1)
1000 ze <- &zipEntry{
1001 fh: dirHeader,
1002 }
1003 close(ze)
1004 z.writeOps <- ze
Colin Crosse19c7932015-04-24 15:08:38 -07001005 }
Colin Crosse19c7932015-04-24 15:08:38 -07001006 }
1007
1008 return nil
1009}
Dan Willemsena59a3bc2016-08-03 17:47:23 -07001010
Jeff Gastonc3bdc972017-10-12 12:18:19 -07001011func (z *ZipWriter) writeSymlink(rel, file string) error {
Dan Willemsena59a3bc2016-08-03 17:47:23 -07001012 fileHeader := &zip.FileHeader{
1013 Name: rel,
1014 }
1015 fileHeader.SetModTime(z.time)
Colin Cross297d9bc2018-06-22 16:37:47 -07001016 fileHeader.SetMode(0777 | os.ModeSymlink)
Dan Willemsena59a3bc2016-08-03 17:47:23 -07001017
Colin Cross05518bc2018-09-27 15:06:19 -07001018 dest, err := z.fs.Readlink(file)
Dan Willemsena59a3bc2016-08-03 17:47:23 -07001019 if err != nil {
1020 return err
1021 }
1022
Colin Cross297d9bc2018-06-22 16:37:47 -07001023 fileHeader.UncompressedSize64 = uint64(len(dest))
1024 fileHeader.CRC32 = crc32.ChecksumIEEE([]byte(dest))
1025
Dan Willemsen017d8932016-08-04 15:43:03 -07001026 ze := make(chan *zipEntry, 1)
1027 futureReaders := make(chan chan io.Reader, 1)
1028 futureReader := make(chan io.Reader, 1)
1029 futureReaders <- futureReader
1030 close(futureReaders)
1031 futureReader <- bytes.NewBufferString(dest)
1032 close(futureReader)
1033
Dan Willemsen017d8932016-08-04 15:43:03 -07001034 ze <- &zipEntry{
1035 fh: fileHeader,
1036 futureReaders: futureReaders,
1037 }
1038 close(ze)
1039 z.writeOps <- ze
1040
1041 return nil
Dan Willemsena59a3bc2016-08-03 17:47:23 -07001042}