blob: 4eb4ebe72260627dc062e7d408e6d0d3eaa506e5 [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
15package main
16
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 "flag"
22 "fmt"
Dan Willemsen017d8932016-08-04 15:43:03 -070023 "hash/crc32"
Colin Cross2fe66872015-03-30 17:20:39 -070024 "io"
25 "io/ioutil"
Nan Zhang9067b042017-03-17 14:04:43 -070026 "log"
Colin Cross2fe66872015-03-30 17:20:39 -070027 "os"
28 "path/filepath"
Dan Willemsen017d8932016-08-04 15:43:03 -070029 "runtime"
30 "runtime/pprof"
31 "runtime/trace"
Jeff Gastona2976952017-08-22 17:51:25 -070032 "sort"
Colin Cross2fe66872015-03-30 17:20:39 -070033 "strings"
Dan Willemsen017d8932016-08-04 15:43:03 -070034 "sync"
Colin Cross2fe66872015-03-30 17:20:39 -070035 "time"
Dan Willemsen017d8932016-08-04 15:43:03 -070036
Jeff Gastona2976952017-08-22 17:51:25 -070037 "android/soong/jar"
Dan Willemsen017d8932016-08-04 15:43:03 -070038 "android/soong/third_party/zip"
Colin Cross2fe66872015-03-30 17:20:39 -070039)
40
Dan Willemsen017d8932016-08-04 15:43:03 -070041// Block size used during parallel compression of a single file.
42const parallelBlockSize = 1 * 1024 * 1024 // 1MB
43
44// Minimum file size to use parallel compression. It requires more
45// flate.Writer allocations, since we can't change the dictionary
46// during Reset
47const minParallelFileSize = parallelBlockSize * 6
48
49// Size of the ZIP compression window (32KB)
50const windowSize = 32 * 1024
51
52type nopCloser struct {
53 io.Writer
54}
55
56func (nopCloser) Close() error {
57 return nil
58}
59
Jeff Gastoncef50b92017-08-23 15:41:35 -070060type byteReaderCloser struct {
61 bytes.Reader
62 io.Closer
63}
64
65// the file path in the zip at which a Java manifest file gets written
66const manifestDest = "META-INF/MANIFEST.MF"
67
Colin Cross2fe66872015-03-30 17:20:39 -070068type fileArg struct {
Nan Zhangf281bd82017-04-25 16:47:45 -070069 pathPrefixInZip, sourcePrefixToStrip string
70 sourceFiles []string
Nan Zhang9067b042017-03-17 14:04:43 -070071}
72
73type pathMapping struct {
74 dest, src string
Nan Zhangf281bd82017-04-25 16:47:45 -070075 zipMethod uint16
76}
77
78type uniqueSet map[string]bool
79
80func (u *uniqueSet) String() string {
81 return `""`
82}
83
84func (u *uniqueSet) Set(s string) error {
85 if _, found := (*u)[s]; found {
86 return fmt.Errorf("File %q was specified twice as a file to not deflate", s)
87 } else {
88 (*u)[s] = true
89 }
90
91 return nil
Colin Cross2fe66872015-03-30 17:20:39 -070092}
93
94type fileArgs []fileArg
95
Nan Zhangf281bd82017-04-25 16:47:45 -070096type file struct{}
97
98type listFiles struct{}
99
100func (f *file) String() string {
Colin Cross2fe66872015-03-30 17:20:39 -0700101 return `""`
102}
103
Nan Zhangf281bd82017-04-25 16:47:45 -0700104func (f *file) Set(s string) error {
Colin Cross2fe66872015-03-30 17:20:39 -0700105 if *relativeRoot == "" {
Nan Zhang9067b042017-03-17 14:04:43 -0700106 return fmt.Errorf("must pass -C before -f or -l")
Colin Cross2fe66872015-03-30 17:20:39 -0700107 }
108
Nan Zhangf281bd82017-04-25 16:47:45 -0700109 fArgs = append(fArgs, fileArg{
110 pathPrefixInZip: filepath.Clean(*rootPrefix),
111 sourcePrefixToStrip: filepath.Clean(*relativeRoot),
112 sourceFiles: []string{s},
113 })
114
Colin Cross2fe66872015-03-30 17:20:39 -0700115 return nil
116}
117
Nan Zhangf281bd82017-04-25 16:47:45 -0700118func (l *listFiles) String() string {
119 return `""`
120}
121
122func (l *listFiles) Set(s string) error {
123 if *relativeRoot == "" {
124 return fmt.Errorf("must pass -C before -f or -l")
125 }
126
127 list, err := ioutil.ReadFile(s)
128 if err != nil {
129 return err
130 }
131
132 fArgs = append(fArgs, fileArg{
133 pathPrefixInZip: filepath.Clean(*rootPrefix),
134 sourcePrefixToStrip: filepath.Clean(*relativeRoot),
135 sourceFiles: strings.Split(string(list), "\n"),
136 })
137
138 return nil
Colin Cross2fe66872015-03-30 17:20:39 -0700139}
140
141var (
Dan Willemsen47ec28f2016-08-10 16:12:30 -0700142 out = flag.String("o", "", "file to write zip file to")
143 manifest = flag.String("m", "", "input jar manifest file name")
144 directories = flag.Bool("d", false, "include directories in zip")
Nan Zhang9067b042017-03-17 14:04:43 -0700145 rootPrefix = flag.String("P", "", "path prefix within the zip at which to place files")
Colin Cross2fe66872015-03-30 17:20:39 -0700146 relativeRoot = flag.String("C", "", "path to use as relative root of files in next -f or -l argument")
Dan Willemsen017d8932016-08-04 15:43:03 -0700147 parallelJobs = flag.Int("j", runtime.NumCPU(), "number of parallel threads to use")
148 compLevel = flag.Int("L", 5, "deflate compression level (0-9)")
Jeff Gastona2976952017-08-22 17:51:25 -0700149 emulateJar = flag.Bool("jar", false, "modify the resultant .zip to emulate the output of 'jar'")
Nan Zhang9067b042017-03-17 14:04:43 -0700150
Nan Zhangf281bd82017-04-25 16:47:45 -0700151 fArgs fileArgs
152 nonDeflatedFiles = make(uniqueSet)
Dan Willemsen017d8932016-08-04 15:43:03 -0700153
154 cpuProfile = flag.String("cpuprofile", "", "write cpu profile to file")
155 traceFile = flag.String("trace", "", "write trace to file")
Colin Cross2fe66872015-03-30 17:20:39 -0700156)
157
158func init() {
Nan Zhangf281bd82017-04-25 16:47:45 -0700159 flag.Var(&listFiles{}, "l", "file containing list of .class files")
160 flag.Var(&file{}, "f", "file to include in zip")
161 flag.Var(&nonDeflatedFiles, "s", "file path to be stored within the zip without compression")
Colin Cross2fe66872015-03-30 17:20:39 -0700162}
163
164func usage() {
Dan Willemsen47ec28f2016-08-10 16:12:30 -0700165 fmt.Fprintf(os.Stderr, "usage: soong_zip -o zipfile [-m manifest] -C dir [-f|-l file]...\n")
Colin Cross2fe66872015-03-30 17:20:39 -0700166 flag.PrintDefaults()
167 os.Exit(2)
168}
169
Colin Crosse19c7932015-04-24 15:08:38 -0700170type zipWriter struct {
Colin Cross2fe66872015-03-30 17:20:39 -0700171 time time.Time
172 createdDirs map[string]bool
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
196func main() {
197 flag.Parse()
198
Dan Willemsen017d8932016-08-04 15:43:03 -0700199 if *cpuProfile != "" {
200 f, err := os.Create(*cpuProfile)
201 if err != nil {
202 fmt.Fprintln(os.Stderr, err.Error())
203 os.Exit(1)
204 }
205 defer f.Close()
206 pprof.StartCPUProfile(f)
207 defer pprof.StopCPUProfile()
208 }
209
210 if *traceFile != "" {
211 f, err := os.Create(*traceFile)
212 if err != nil {
213 fmt.Fprintln(os.Stderr, err.Error())
214 os.Exit(1)
215 }
216 defer f.Close()
217 err = trace.Start(f)
218 if err != nil {
219 fmt.Fprintln(os.Stderr, err.Error())
220 os.Exit(1)
221 }
222 defer trace.Stop()
223 }
224
Colin Cross2fe66872015-03-30 17:20:39 -0700225 if *out == "" {
226 fmt.Fprintf(os.Stderr, "error: -o is required\n")
227 usage()
228 }
229
Jeff Gaston8edbb3a2017-08-22 20:05:28 -0700230 if *emulateJar {
231 *directories = true
232 }
233
Colin Crosse19c7932015-04-24 15:08:38 -0700234 w := &zipWriter{
Dan Willemsen77a6b862016-08-04 20:38:47 -0700235 time: time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC),
Colin Cross2fe66872015-03-30 17:20:39 -0700236 createdDirs: make(map[string]bool),
237 directories: *directories,
Dan Willemsen017d8932016-08-04 15:43:03 -0700238 compLevel: *compLevel,
Colin Cross2fe66872015-03-30 17:20:39 -0700239 }
240
Nan Zhang9067b042017-03-17 14:04:43 -0700241 pathMappings := []pathMapping{}
242 set := make(map[string]string)
243
Nan Zhangf281bd82017-04-25 16:47:45 -0700244 for _, fa := range fArgs {
245 for _, src := range fa.sourceFiles {
246 if err := fillPathPairs(fa.pathPrefixInZip,
247 fa.sourcePrefixToStrip, src, set, &pathMappings); err != nil {
Nan Zhang9067b042017-03-17 14:04:43 -0700248 log.Fatal(err)
249 }
250 }
251 }
252
Nan Zhang9067b042017-03-17 14:04:43 -0700253 err := w.write(*out, pathMappings, *manifest)
Colin Cross2fe66872015-03-30 17:20:39 -0700254 if err != nil {
255 fmt.Fprintln(os.Stderr, err.Error())
256 os.Exit(1)
257 }
258}
259
Nan Zhang9067b042017-03-17 14:04:43 -0700260func fillPathPairs(prefix, rel, src string, set map[string]string, pathMappings *[]pathMapping) error {
261 src = strings.TrimSpace(src)
262 if src == "" {
263 return nil
264 }
265 src = filepath.Clean(src)
266 dest, err := filepath.Rel(rel, src)
267 if err != nil {
268 return err
269 }
270 dest = filepath.Join(prefix, dest)
271
272 if _, found := set[dest]; found {
273 return fmt.Errorf("found two file paths to be copied into dest path: %q,"+
274 " both [%q]%q and [%q]%q!",
275 dest, dest, src, dest, set[dest])
276 } else {
277 set[dest] = src
278 }
279
Nan Zhangf281bd82017-04-25 16:47:45 -0700280 zipMethod := zip.Deflate
281 if _, found := nonDeflatedFiles[dest]; found {
282 zipMethod = zip.Store
283 }
284 *pathMappings = append(*pathMappings,
285 pathMapping{dest: dest, src: src, zipMethod: zipMethod})
Nan Zhang9067b042017-03-17 14:04:43 -0700286
287 return nil
288}
289
Jeff Gastona2976952017-08-22 17:51:25 -0700290func jarSort(mappings []pathMapping) {
291 less := func(i int, j int) (smaller bool) {
292 return jar.EntryNamesLess(mappings[i].dest, mappings[j].dest)
293 }
294 sort.SliceStable(mappings, less)
295}
296
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700297type readerSeekerCloser interface {
298 io.Reader
299 io.ReaderAt
300 io.Closer
301 io.Seeker
302}
303
Nan Zhang9067b042017-03-17 14:04:43 -0700304func (z *zipWriter) write(out string, pathMappings []pathMapping, manifest string) error {
Colin Cross2fe66872015-03-30 17:20:39 -0700305 f, err := os.Create(out)
306 if err != nil {
307 return err
308 }
309
310 defer f.Close()
311 defer func() {
312 if err != nil {
313 os.Remove(out)
314 }
315 }()
316
Dan Willemsen017d8932016-08-04 15:43:03 -0700317 z.errors = make(chan error)
318 defer close(z.errors)
Colin Cross2fe66872015-03-30 17:20:39 -0700319
Dan Willemsen017d8932016-08-04 15:43:03 -0700320 // This channel size can be essentially unlimited -- it's used as a fifo
321 // queue decouple the CPU and IO loads. Directories don't require any
322 // compression time, but still cost some IO. Similar with small files that
323 // can be very fast to compress. Some files that are more difficult to
324 // compress won't take a corresponding longer time writing out.
325 //
326 // The optimum size here depends on your CPU and IO characteristics, and
327 // the the layout of your zip file. 1000 was chosen mostly at random as
328 // something that worked reasonably well for a test file.
329 //
330 // The RateLimit object will put the upper bounds on the number of
331 // parallel compressions and outstanding buffers.
332 z.writeOps = make(chan chan *zipEntry, 1000)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700333 z.cpuRateLimiter = NewCPURateLimiter(int64(*parallelJobs))
334 z.memoryRateLimiter = NewMemoryRateLimiter(0)
335 defer func() {
336 z.cpuRateLimiter.Stop()
337 z.memoryRateLimiter.Stop()
338 }()
Jeff Gastona2976952017-08-22 17:51:25 -0700339
340 if manifest != "" {
341 if !*emulateJar {
342 return errors.New("must specify --jar when specifying a manifest via -m")
343 }
Jeff Gastoncef50b92017-08-23 15:41:35 -0700344 pathMappings = append(pathMappings, pathMapping{manifestDest, manifest, zip.Deflate})
Jeff Gastona2976952017-08-22 17:51:25 -0700345 }
346
347 if *emulateJar {
348 jarSort(pathMappings)
349 }
350
Dan Willemsen017d8932016-08-04 15:43:03 -0700351 go func() {
352 var err error
353 defer close(z.writeOps)
354
Nan Zhang9067b042017-03-17 14:04:43 -0700355 for _, ele := range pathMappings {
Jeff Gastoncef50b92017-08-23 15:41:35 -0700356 if *emulateJar && ele.dest == manifestDest {
357 err = z.addManifest(ele.dest, ele.src, ele.zipMethod)
358 } else {
359 err = z.addFile(ele.dest, ele.src, ele.zipMethod)
360 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700361 if err != nil {
362 z.errors <- err
363 return
364 }
365 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700366 }()
367
368 zipw := zip.NewWriter(f)
369
370 var currentWriteOpChan chan *zipEntry
371 var currentWriter io.WriteCloser
372 var currentReaders chan chan io.Reader
373 var currentReader chan io.Reader
374 var done bool
375
376 for !done {
377 var writeOpsChan chan chan *zipEntry
378 var writeOpChan chan *zipEntry
379 var readersChan chan chan io.Reader
380
381 if currentReader != nil {
382 // Only read and process errors
383 } else if currentReaders != nil {
384 readersChan = currentReaders
385 } else if currentWriteOpChan != nil {
386 writeOpChan = currentWriteOpChan
387 } else {
388 writeOpsChan = z.writeOps
389 }
390
391 select {
392 case writeOp, ok := <-writeOpsChan:
393 if !ok {
394 done = true
395 }
396
397 currentWriteOpChan = writeOp
398
399 case op := <-writeOpChan:
400 currentWriteOpChan = nil
401
402 if op.fh.Method == zip.Deflate {
403 currentWriter, err = zipw.CreateCompressedHeader(op.fh)
404 } else {
405 var zw io.Writer
Jeff Gastonc5eb66d2017-08-24 14:11:27 -0700406
407 op.fh.CompressedSize64 = op.fh.UncompressedSize64
408
409 zw, err = zipw.CreateHeaderAndroid(op.fh)
Dan Willemsen017d8932016-08-04 15:43:03 -0700410 currentWriter = nopCloser{zw}
411 }
412 if err != nil {
413 return err
414 }
415
416 currentReaders = op.futureReaders
417 if op.futureReaders == nil {
418 currentWriter.Close()
419 currentWriter = nil
420 }
Jeff Gaston175f34c2017-08-17 21:43:21 -0700421 z.memoryRateLimiter.Finish(op.allocatedSize)
Dan Willemsen017d8932016-08-04 15:43:03 -0700422
423 case futureReader, ok := <-readersChan:
424 if !ok {
425 // Done with reading
426 currentWriter.Close()
427 currentWriter = nil
428 currentReaders = nil
429 }
430
431 currentReader = futureReader
432
433 case reader := <-currentReader:
Jeff Gaston175f34c2017-08-17 21:43:21 -0700434 _, err = io.Copy(currentWriter, reader)
Dan Willemsen017d8932016-08-04 15:43:03 -0700435 if err != nil {
436 return err
437 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700438
439 currentReader = nil
440
441 case err = <-z.errors:
Colin Cross2fe66872015-03-30 17:20:39 -0700442 return err
443 }
444 }
445
Dan Willemsen017d8932016-08-04 15:43:03 -0700446 // One last chance to catch an error
447 select {
448 case err = <-z.errors:
449 return err
450 default:
451 zipw.Close()
452 return nil
Colin Cross2fe66872015-03-30 17:20:39 -0700453 }
Colin Cross2fe66872015-03-30 17:20:39 -0700454}
455
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700456// imports (possibly with compression) <src> into the zip at sub-path <dest>
Jeff Gastoncef50b92017-08-23 15:41:35 -0700457func (z *zipWriter) addFile(dest, src string, method uint16) error {
Dan Willemsen017d8932016-08-04 15:43:03 -0700458 var fileSize int64
Dan Willemsen10462b32017-03-15 19:02:51 -0700459 var executable bool
Dan Willemsen017d8932016-08-04 15:43:03 -0700460
Nan Zhang9067b042017-03-17 14:04:43 -0700461 if s, err := os.Lstat(src); err != nil {
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700462 return err
463 } else if s.IsDir() {
Colin Cross957cc4e2015-04-24 15:10:32 -0700464 if z.directories {
Nan Zhang9067b042017-03-17 14:04:43 -0700465 return z.writeDirectory(dest)
Colin Cross957cc4e2015-04-24 15:10:32 -0700466 }
467 return nil
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700468 } else if s.Mode()&os.ModeSymlink != 0 {
Nan Zhang9067b042017-03-17 14:04:43 -0700469 return z.writeSymlink(dest, src)
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700470 } else if !s.Mode().IsRegular() {
Nan Zhang9067b042017-03-17 14:04:43 -0700471 return fmt.Errorf("%s is not a file, directory, or symlink", src)
Dan Willemsen017d8932016-08-04 15:43:03 -0700472 } else {
473 fileSize = s.Size()
Dan Willemsen10462b32017-03-15 19:02:51 -0700474 executable = s.Mode()&0100 != 0
Colin Cross957cc4e2015-04-24 15:10:32 -0700475 }
476
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700477 r, err := os.Open(src)
478 if err != nil {
479 return err
480 }
481
482 header := &zip.FileHeader{
483 Name: dest,
484 Method: method,
485 UncompressedSize64: uint64(fileSize),
486 }
487
488 if executable {
489 header.SetMode(0700)
490 }
491
492 return z.writeFileContents(header, r)
493}
494
495// writes the contents of r according to the specifications in header
Jeff Gastoncef50b92017-08-23 15:41:35 -0700496func (z *zipWriter) addManifest(dest string, src string, method uint16) error {
497 givenBytes, err := ioutil.ReadFile(src)
498 if err != nil {
499 return err
500 }
501
502 manifestMarker := []byte("Manifest-Version:")
503 header := append(manifestMarker, []byte(" 1.0\nCreated-By: soong_zip\n")...)
504
505 var finalBytes []byte
506 if !bytes.Contains(givenBytes, manifestMarker) {
507 finalBytes = append(append(header, givenBytes...), byte('\n'))
508 } else {
509 finalBytes = givenBytes
510 }
511
512 byteReader := bytes.NewReader(finalBytes)
513
514 reader := &byteReaderCloser{*byteReader, ioutil.NopCloser(nil)}
515
516 fileHeader := &zip.FileHeader{
517 Name: dest,
518 Method: zip.Store,
519 UncompressedSize64: uint64(byteReader.Len()),
520 }
521
522 return z.writeFileContents(fileHeader, reader)
523}
524
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700525func (z *zipWriter) writeFileContents(header *zip.FileHeader, r readerSeekerCloser) (err error) {
526
527 header.SetModTime(z.time)
528
Colin Crosse19c7932015-04-24 15:08:38 -0700529 if z.directories {
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700530 dest := header.Name
Nan Zhang9067b042017-03-17 14:04:43 -0700531 dir, _ := filepath.Split(dest)
Colin Crosse19c7932015-04-24 15:08:38 -0700532 err := z.writeDirectory(dir)
533 if err != nil {
534 return err
Colin Cross2fe66872015-03-30 17:20:39 -0700535 }
536 }
537
Dan Willemsen017d8932016-08-04 15:43:03 -0700538 compressChan := make(chan *zipEntry, 1)
539 z.writeOps <- compressChan
540
541 // Pre-fill a zipEntry, it will be sent in the compressChan once
542 // we're sure about the Method and CRC.
543 ze := &zipEntry{
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700544 fh: header,
Dan Willemsen10462b32017-03-15 19:02:51 -0700545 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700546
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700547 ze.allocatedSize = int64(header.UncompressedSize64)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700548 z.cpuRateLimiter.Request()
549 z.memoryRateLimiter.Request(ze.allocatedSize)
Dan Willemsen017d8932016-08-04 15:43:03 -0700550
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700551 fileSize := int64(header.UncompressedSize64)
552 if fileSize == 0 {
553 fileSize = int64(header.UncompressedSize)
554 }
555
556 if header.Method == zip.Deflate && fileSize >= minParallelFileSize {
Dan Willemsen017d8932016-08-04 15:43:03 -0700557 wg := new(sync.WaitGroup)
558
559 // Allocate enough buffer to hold all readers. We'll limit
560 // this based on actual buffer sizes in RateLimit.
561 ze.futureReaders = make(chan chan io.Reader, (fileSize/parallelBlockSize)+1)
562
563 // Calculate the CRC in the background, since reading the entire
564 // file could take a while.
565 //
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700566 // We could split this up into chunks as well, but it's faster
Dan Willemsen017d8932016-08-04 15:43:03 -0700567 // than the compression. Due to the Go Zip API, we also need to
568 // know the result before we can begin writing the compressed
569 // data out to the zipfile.
570 wg.Add(1)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700571 go z.crcFile(r, ze, compressChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700572
573 for start := int64(0); start < fileSize; start += parallelBlockSize {
574 sr := io.NewSectionReader(r, start, parallelBlockSize)
575 resultChan := make(chan io.Reader, 1)
576 ze.futureReaders <- resultChan
577
Jeff Gaston175f34c2017-08-17 21:43:21 -0700578 z.cpuRateLimiter.Request()
Dan Willemsen017d8932016-08-04 15:43:03 -0700579
580 last := !(start+parallelBlockSize < fileSize)
581 var dict []byte
582 if start >= windowSize {
583 dict, err = ioutil.ReadAll(io.NewSectionReader(r, start-windowSize, windowSize))
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700584 if err != nil {
585 return err
586 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700587 }
588
589 wg.Add(1)
Jeff Gaston175f34c2017-08-17 21:43:21 -0700590 go z.compressPartialFile(sr, dict, last, resultChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700591 }
592
593 close(ze.futureReaders)
594
595 // Close the file handle after all readers are done
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700596 go func(wg *sync.WaitGroup, closer io.Closer) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700597 wg.Wait()
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700598 closer.Close()
Dan Willemsen017d8932016-08-04 15:43:03 -0700599 }(wg, r)
600 } else {
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700601 go func() {
602 z.compressWholeFile(ze, r, compressChan)
603 r.Close()
604 }()
Dan Willemsen017d8932016-08-04 15:43:03 -0700605 }
606
607 return nil
608}
609
Jeff Gaston175f34c2017-08-17 21:43:21 -0700610func (z *zipWriter) crcFile(r io.Reader, ze *zipEntry, resultChan chan *zipEntry, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700611 defer wg.Done()
Jeff Gaston175f34c2017-08-17 21:43:21 -0700612 defer z.cpuRateLimiter.Finish()
Dan Willemsen017d8932016-08-04 15:43:03 -0700613
614 crc := crc32.NewIEEE()
615 _, err := io.Copy(crc, r)
616 if err != nil {
617 z.errors <- err
618 return
619 }
620
621 ze.fh.CRC32 = crc.Sum32()
622 resultChan <- ze
623 close(resultChan)
624}
625
Jeff Gaston175f34c2017-08-17 21:43:21 -0700626func (z *zipWriter) compressPartialFile(r io.Reader, dict []byte, last bool, resultChan chan io.Reader, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700627 defer wg.Done()
628
629 result, err := z.compressBlock(r, dict, last)
630 if err != nil {
631 z.errors <- err
632 return
633 }
634
Jeff Gaston175f34c2017-08-17 21:43:21 -0700635 z.cpuRateLimiter.Finish()
636
Dan Willemsen017d8932016-08-04 15:43:03 -0700637 resultChan <- result
638}
639
640func (z *zipWriter) compressBlock(r io.Reader, dict []byte, last bool) (*bytes.Buffer, error) {
641 buf := new(bytes.Buffer)
642 var fw *flate.Writer
643 var err error
644 if len(dict) > 0 {
645 // There's no way to Reset a Writer with a new dictionary, so
646 // don't use the Pool
647 fw, err = flate.NewWriterDict(buf, z.compLevel, dict)
648 } else {
649 var ok bool
650 if fw, ok = z.compressorPool.Get().(*flate.Writer); ok {
651 fw.Reset(buf)
652 } else {
653 fw, err = flate.NewWriter(buf, z.compLevel)
654 }
655 defer z.compressorPool.Put(fw)
656 }
657 if err != nil {
658 return nil, err
659 }
660
661 _, err = io.Copy(fw, r)
662 if err != nil {
663 return nil, err
664 }
665 if last {
666 fw.Close()
667 } else {
668 fw.Flush()
669 }
670
671 return buf, nil
672}
673
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700674func (z *zipWriter) compressWholeFile(ze *zipEntry, r io.ReadSeeker, compressChan chan *zipEntry) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700675
Dan Willemsen017d8932016-08-04 15:43:03 -0700676 crc := crc32.NewIEEE()
Dan Willemsena8b55022017-03-15 21:49:26 -0700677 _, err := io.Copy(crc, r)
Colin Cross2fe66872015-03-30 17:20:39 -0700678 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700679 z.errors <- err
680 return
Colin Cross2fe66872015-03-30 17:20:39 -0700681 }
682
Dan Willemsena8b55022017-03-15 21:49:26 -0700683 ze.fh.CRC32 = crc.Sum32()
Colin Cross2fe66872015-03-30 17:20:39 -0700684
Dan Willemsen017d8932016-08-04 15:43:03 -0700685 _, err = r.Seek(0, 0)
Colin Cross2fe66872015-03-30 17:20:39 -0700686 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700687 z.errors <- err
688 return
Colin Cross2fe66872015-03-30 17:20:39 -0700689 }
690
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700691 readFile := func(reader io.ReadSeeker) ([]byte, error) {
692 _, err := reader.Seek(0, 0)
Nan Zhangf281bd82017-04-25 16:47:45 -0700693 if err != nil {
694 return nil, err
695 }
696
Jeff Gaston66dd6e52017-08-23 15:12:48 -0700697 buf, err := ioutil.ReadAll(reader)
Nan Zhangf281bd82017-04-25 16:47:45 -0700698 if err != nil {
699 return nil, err
700 }
701
702 return buf, nil
703 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700704
Dan Willemsena8b55022017-03-15 21:49:26 -0700705 ze.futureReaders = make(chan chan io.Reader, 1)
Dan Willemsen017d8932016-08-04 15:43:03 -0700706 futureReader := make(chan io.Reader, 1)
707 ze.futureReaders <- futureReader
708 close(ze.futureReaders)
709
Nan Zhangf281bd82017-04-25 16:47:45 -0700710 if ze.fh.Method == zip.Deflate {
711 compressed, err := z.compressBlock(r, nil, true)
712 if err != nil {
713 z.errors <- err
714 return
715 }
716 if uint64(compressed.Len()) < ze.fh.UncompressedSize64 {
717 futureReader <- compressed
Nan Zhangf281bd82017-04-25 16:47:45 -0700718 } else {
719 buf, err := readFile(r)
720 if err != nil {
721 z.errors <- err
722 return
723 }
724 ze.fh.Method = zip.Store
725 futureReader <- bytes.NewReader(buf)
Nan Zhangf281bd82017-04-25 16:47:45 -0700726 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700727 } else {
Nan Zhangf281bd82017-04-25 16:47:45 -0700728 buf, err := readFile(r)
Dan Willemsen017d8932016-08-04 15:43:03 -0700729 if err != nil {
730 z.errors <- err
731 return
732 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700733 ze.fh.Method = zip.Store
734 futureReader <- bytes.NewReader(buf)
Dan Willemsen017d8932016-08-04 15:43:03 -0700735 }
Nan Zhangf281bd82017-04-25 16:47:45 -0700736
Jeff Gaston175f34c2017-08-17 21:43:21 -0700737 z.cpuRateLimiter.Finish()
738
Dan Willemsen017d8932016-08-04 15:43:03 -0700739 close(futureReader)
740
741 compressChan <- ze
742 close(compressChan)
Colin Cross2fe66872015-03-30 17:20:39 -0700743}
Colin Crosse19c7932015-04-24 15:08:38 -0700744
Jeff Gaston8edbb3a2017-08-22 20:05:28 -0700745func (z *zipWriter) addExtraField(zipHeader *zip.FileHeader, fieldHeader [2]byte, data []byte) {
746 // add the field header in little-endian order
747 zipHeader.Extra = append(zipHeader.Extra, fieldHeader[1], fieldHeader[0])
748
749 // specify the length of the data (in little-endian order)
750 dataLength := len(data)
751 lengthBytes := []byte{byte(dataLength % 256), byte(dataLength / 256)}
752 zipHeader.Extra = append(zipHeader.Extra, lengthBytes...)
753
754 // add the contents of the extra field
755 zipHeader.Extra = append(zipHeader.Extra, data...)
756}
757
Colin Crosse19c7932015-04-24 15:08:38 -0700758func (z *zipWriter) writeDirectory(dir string) error {
Jeff Gaston2d174132017-08-15 18:05:56 -0700759 // clean the input
760 cleanDir := filepath.Clean(dir)
761
762 // discover any uncreated directories in the path
763 zipDirs := []string{}
764 for cleanDir != "" && cleanDir != "." && !z.createdDirs[cleanDir] {
765
766 z.createdDirs[cleanDir] = true
767 // parent directories precede their children
768 zipDirs = append([]string{cleanDir}, zipDirs...)
769
770 cleanDir = filepath.Dir(cleanDir)
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700771 }
772
Jeff Gaston2d174132017-08-15 18:05:56 -0700773 // make a directory entry for each uncreated directory
774 for _, cleanDir := range zipDirs {
Colin Crosse19c7932015-04-24 15:08:38 -0700775 dirHeader := &zip.FileHeader{
Jeff Gaston2d174132017-08-15 18:05:56 -0700776 Name: cleanDir + "/",
Colin Crosse19c7932015-04-24 15:08:38 -0700777 }
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700778 dirHeader.SetMode(0700 | os.ModeDir)
Colin Crosse19c7932015-04-24 15:08:38 -0700779 dirHeader.SetModTime(z.time)
780
Jeff Gaston8edbb3a2017-08-22 20:05:28 -0700781 if *emulateJar && dir == "META-INF/" {
782 // Jar files have a 0-length extra field with header "CAFE"
783 z.addExtraField(dirHeader, [2]byte{0xca, 0xfe}, []byte{})
784 }
785
Dan Willemsen017d8932016-08-04 15:43:03 -0700786 ze := make(chan *zipEntry, 1)
787 ze <- &zipEntry{
788 fh: dirHeader,
Colin Crosse19c7932015-04-24 15:08:38 -0700789 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700790 close(ze)
791 z.writeOps <- ze
Colin Crosse19c7932015-04-24 15:08:38 -0700792 }
793
794 return nil
795}
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700796
797func (z *zipWriter) writeSymlink(rel, file string) error {
798 if z.directories {
799 dir, _ := filepath.Split(rel)
800 if err := z.writeDirectory(dir); err != nil {
801 return err
802 }
803 }
804
805 fileHeader := &zip.FileHeader{
806 Name: rel,
807 }
808 fileHeader.SetModTime(z.time)
809 fileHeader.SetMode(0700 | os.ModeSymlink)
810
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700811 dest, err := os.Readlink(file)
812 if err != nil {
813 return err
814 }
815
Dan Willemsen017d8932016-08-04 15:43:03 -0700816 ze := make(chan *zipEntry, 1)
817 futureReaders := make(chan chan io.Reader, 1)
818 futureReader := make(chan io.Reader, 1)
819 futureReaders <- futureReader
820 close(futureReaders)
821 futureReader <- bytes.NewBufferString(dest)
822 close(futureReader)
823
Dan Willemsen017d8932016-08-04 15:43:03 -0700824 ze <- &zipEntry{
825 fh: fileHeader,
826 futureReaders: futureReaders,
827 }
828 close(ze)
829 z.writeOps <- ze
830
831 return nil
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700832}