blob: 3d47f2c6a742537ed92724238bb0a83cd3630d80 [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"
Colin Cross2fe66872015-03-30 17:20:39 -070020 "flag"
21 "fmt"
Dan Willemsen017d8932016-08-04 15:43:03 -070022 "hash/crc32"
Colin Cross2fe66872015-03-30 17:20:39 -070023 "io"
24 "io/ioutil"
Nan Zhang9067b042017-03-17 14:04:43 -070025 "log"
Colin Cross2fe66872015-03-30 17:20:39 -070026 "os"
27 "path/filepath"
Dan Willemsen017d8932016-08-04 15:43:03 -070028 "runtime"
29 "runtime/pprof"
30 "runtime/trace"
Colin Cross2fe66872015-03-30 17:20:39 -070031 "strings"
Dan Willemsen017d8932016-08-04 15:43:03 -070032 "sync"
Colin Cross2fe66872015-03-30 17:20:39 -070033 "time"
Dan Willemsen017d8932016-08-04 15:43:03 -070034
35 "android/soong/third_party/zip"
Colin Cross2fe66872015-03-30 17:20:39 -070036)
37
Dan Willemsen017d8932016-08-04 15:43:03 -070038// Block size used during parallel compression of a single file.
39const parallelBlockSize = 1 * 1024 * 1024 // 1MB
40
41// Minimum file size to use parallel compression. It requires more
42// flate.Writer allocations, since we can't change the dictionary
43// during Reset
44const minParallelFileSize = parallelBlockSize * 6
45
46// Size of the ZIP compression window (32KB)
47const windowSize = 32 * 1024
48
49type nopCloser struct {
50 io.Writer
51}
52
53func (nopCloser) Close() error {
54 return nil
55}
56
Colin Cross2fe66872015-03-30 17:20:39 -070057type fileArg struct {
Nan Zhangf281bd82017-04-25 16:47:45 -070058 pathPrefixInZip, sourcePrefixToStrip string
59 sourceFiles []string
Nan Zhang9067b042017-03-17 14:04:43 -070060}
61
62type pathMapping struct {
63 dest, src string
Nan Zhangf281bd82017-04-25 16:47:45 -070064 zipMethod uint16
65}
66
67type uniqueSet map[string]bool
68
69func (u *uniqueSet) String() string {
70 return `""`
71}
72
73func (u *uniqueSet) Set(s string) error {
74 if _, found := (*u)[s]; found {
75 return fmt.Errorf("File %q was specified twice as a file to not deflate", s)
76 } else {
77 (*u)[s] = true
78 }
79
80 return nil
Colin Cross2fe66872015-03-30 17:20:39 -070081}
82
83type fileArgs []fileArg
84
Nan Zhangf281bd82017-04-25 16:47:45 -070085type file struct{}
86
87type listFiles struct{}
88
89func (f *file) String() string {
Colin Cross2fe66872015-03-30 17:20:39 -070090 return `""`
91}
92
Nan Zhangf281bd82017-04-25 16:47:45 -070093func (f *file) Set(s string) error {
Colin Cross2fe66872015-03-30 17:20:39 -070094 if *relativeRoot == "" {
Nan Zhang9067b042017-03-17 14:04:43 -070095 return fmt.Errorf("must pass -C before -f or -l")
Colin Cross2fe66872015-03-30 17:20:39 -070096 }
97
Nan Zhangf281bd82017-04-25 16:47:45 -070098 fArgs = append(fArgs, fileArg{
99 pathPrefixInZip: filepath.Clean(*rootPrefix),
100 sourcePrefixToStrip: filepath.Clean(*relativeRoot),
101 sourceFiles: []string{s},
102 })
103
Colin Cross2fe66872015-03-30 17:20:39 -0700104 return nil
105}
106
Nan Zhangf281bd82017-04-25 16:47:45 -0700107func (l *listFiles) String() string {
108 return `""`
109}
110
111func (l *listFiles) Set(s string) error {
112 if *relativeRoot == "" {
113 return fmt.Errorf("must pass -C before -f or -l")
114 }
115
116 list, err := ioutil.ReadFile(s)
117 if err != nil {
118 return err
119 }
120
121 fArgs = append(fArgs, fileArg{
122 pathPrefixInZip: filepath.Clean(*rootPrefix),
123 sourcePrefixToStrip: filepath.Clean(*relativeRoot),
124 sourceFiles: strings.Split(string(list), "\n"),
125 })
126
127 return nil
Colin Cross2fe66872015-03-30 17:20:39 -0700128}
129
130var (
Dan Willemsen47ec28f2016-08-10 16:12:30 -0700131 out = flag.String("o", "", "file to write zip file to")
132 manifest = flag.String("m", "", "input jar manifest file name")
133 directories = flag.Bool("d", false, "include directories in zip")
Nan Zhang9067b042017-03-17 14:04:43 -0700134 rootPrefix = flag.String("P", "", "path prefix within the zip at which to place files")
Colin Cross2fe66872015-03-30 17:20:39 -0700135 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 -0700136 parallelJobs = flag.Int("j", runtime.NumCPU(), "number of parallel threads to use")
137 compLevel = flag.Int("L", 5, "deflate compression level (0-9)")
Nan Zhang9067b042017-03-17 14:04:43 -0700138
Nan Zhangf281bd82017-04-25 16:47:45 -0700139 fArgs fileArgs
140 nonDeflatedFiles = make(uniqueSet)
Dan Willemsen017d8932016-08-04 15:43:03 -0700141
142 cpuProfile = flag.String("cpuprofile", "", "write cpu profile to file")
143 traceFile = flag.String("trace", "", "write trace to file")
Colin Cross2fe66872015-03-30 17:20:39 -0700144)
145
146func init() {
Nan Zhangf281bd82017-04-25 16:47:45 -0700147 flag.Var(&listFiles{}, "l", "file containing list of .class files")
148 flag.Var(&file{}, "f", "file to include in zip")
149 flag.Var(&nonDeflatedFiles, "s", "file path to be stored within the zip without compression")
Colin Cross2fe66872015-03-30 17:20:39 -0700150}
151
152func usage() {
Dan Willemsen47ec28f2016-08-10 16:12:30 -0700153 fmt.Fprintf(os.Stderr, "usage: soong_zip -o zipfile [-m manifest] -C dir [-f|-l file]...\n")
Colin Cross2fe66872015-03-30 17:20:39 -0700154 flag.PrintDefaults()
155 os.Exit(2)
156}
157
Colin Crosse19c7932015-04-24 15:08:38 -0700158type zipWriter struct {
Colin Cross2fe66872015-03-30 17:20:39 -0700159 time time.Time
160 createdDirs map[string]bool
161 directories bool
Colin Crosse19c7932015-04-24 15:08:38 -0700162
Dan Willemsen017d8932016-08-04 15:43:03 -0700163 errors chan error
164 writeOps chan chan *zipEntry
165
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700166 cpuRateLimiter *CPURateLimiter
167 memoryRateLimiter *MemoryRateLimiter
Dan Willemsen017d8932016-08-04 15:43:03 -0700168
169 compressorPool sync.Pool
170 compLevel int
171}
172
173type zipEntry struct {
174 fh *zip.FileHeader
175
176 // List of delayed io.Reader
177 futureReaders chan chan io.Reader
Colin Cross2fe66872015-03-30 17:20:39 -0700178}
179
180func main() {
181 flag.Parse()
182
Dan Willemsen017d8932016-08-04 15:43:03 -0700183 if *cpuProfile != "" {
184 f, err := os.Create(*cpuProfile)
185 if err != nil {
186 fmt.Fprintln(os.Stderr, err.Error())
187 os.Exit(1)
188 }
189 defer f.Close()
190 pprof.StartCPUProfile(f)
191 defer pprof.StopCPUProfile()
192 }
193
194 if *traceFile != "" {
195 f, err := os.Create(*traceFile)
196 if err != nil {
197 fmt.Fprintln(os.Stderr, err.Error())
198 os.Exit(1)
199 }
200 defer f.Close()
201 err = trace.Start(f)
202 if err != nil {
203 fmt.Fprintln(os.Stderr, err.Error())
204 os.Exit(1)
205 }
206 defer trace.Stop()
207 }
208
Colin Cross2fe66872015-03-30 17:20:39 -0700209 if *out == "" {
210 fmt.Fprintf(os.Stderr, "error: -o is required\n")
211 usage()
212 }
213
Colin Crosse19c7932015-04-24 15:08:38 -0700214 w := &zipWriter{
Dan Willemsen77a6b862016-08-04 20:38:47 -0700215 time: time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC),
Colin Cross2fe66872015-03-30 17:20:39 -0700216 createdDirs: make(map[string]bool),
217 directories: *directories,
Dan Willemsen017d8932016-08-04 15:43:03 -0700218 compLevel: *compLevel,
Colin Cross2fe66872015-03-30 17:20:39 -0700219 }
220
Nan Zhang9067b042017-03-17 14:04:43 -0700221 pathMappings := []pathMapping{}
222 set := make(map[string]string)
223
Nan Zhangf281bd82017-04-25 16:47:45 -0700224 for _, fa := range fArgs {
225 for _, src := range fa.sourceFiles {
226 if err := fillPathPairs(fa.pathPrefixInZip,
227 fa.sourcePrefixToStrip, src, set, &pathMappings); err != nil {
Nan Zhang9067b042017-03-17 14:04:43 -0700228 log.Fatal(err)
229 }
230 }
231 }
232
Nan Zhang9067b042017-03-17 14:04:43 -0700233 err := w.write(*out, pathMappings, *manifest)
Colin Cross2fe66872015-03-30 17:20:39 -0700234 if err != nil {
235 fmt.Fprintln(os.Stderr, err.Error())
236 os.Exit(1)
237 }
238}
239
Nan Zhang9067b042017-03-17 14:04:43 -0700240func fillPathPairs(prefix, rel, src string, set map[string]string, pathMappings *[]pathMapping) error {
241 src = strings.TrimSpace(src)
242 if src == "" {
243 return nil
244 }
245 src = filepath.Clean(src)
246 dest, err := filepath.Rel(rel, src)
247 if err != nil {
248 return err
249 }
250 dest = filepath.Join(prefix, dest)
251
252 if _, found := set[dest]; found {
253 return fmt.Errorf("found two file paths to be copied into dest path: %q,"+
254 " both [%q]%q and [%q]%q!",
255 dest, dest, src, dest, set[dest])
256 } else {
257 set[dest] = src
258 }
259
Nan Zhangf281bd82017-04-25 16:47:45 -0700260 zipMethod := zip.Deflate
261 if _, found := nonDeflatedFiles[dest]; found {
262 zipMethod = zip.Store
263 }
264 *pathMappings = append(*pathMappings,
265 pathMapping{dest: dest, src: src, zipMethod: zipMethod})
Nan Zhang9067b042017-03-17 14:04:43 -0700266
267 return nil
268}
269
270func (z *zipWriter) write(out string, pathMappings []pathMapping, manifest string) error {
Colin Cross2fe66872015-03-30 17:20:39 -0700271 f, err := os.Create(out)
272 if err != nil {
273 return err
274 }
275
276 defer f.Close()
277 defer func() {
278 if err != nil {
279 os.Remove(out)
280 }
281 }()
282
Dan Willemsen017d8932016-08-04 15:43:03 -0700283 z.errors = make(chan error)
284 defer close(z.errors)
Colin Cross2fe66872015-03-30 17:20:39 -0700285
Dan Willemsen017d8932016-08-04 15:43:03 -0700286 // This channel size can be essentially unlimited -- it's used as a fifo
287 // queue decouple the CPU and IO loads. Directories don't require any
288 // compression time, but still cost some IO. Similar with small files that
289 // can be very fast to compress. Some files that are more difficult to
290 // compress won't take a corresponding longer time writing out.
291 //
292 // The optimum size here depends on your CPU and IO characteristics, and
293 // the the layout of your zip file. 1000 was chosen mostly at random as
294 // something that worked reasonably well for a test file.
295 //
296 // The RateLimit object will put the upper bounds on the number of
297 // parallel compressions and outstanding buffers.
298 z.writeOps = make(chan chan *zipEntry, 1000)
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700299 z.cpuRateLimiter = NewCPURateLimiter(int64(*parallelJobs))
300 z.memoryRateLimiter = NewMemoryRateLimiter(0)
301 defer func() {
302 z.cpuRateLimiter.Stop()
303 z.memoryRateLimiter.Stop()
304 }()
Dan Willemsen017d8932016-08-04 15:43:03 -0700305
306 go func() {
307 var err error
308 defer close(z.writeOps)
309
Nan Zhang9067b042017-03-17 14:04:43 -0700310 for _, ele := range pathMappings {
Nan Zhangf281bd82017-04-25 16:47:45 -0700311 err = z.writeFile(ele.dest, ele.src, ele.zipMethod)
Dan Willemsen017d8932016-08-04 15:43:03 -0700312 if err != nil {
313 z.errors <- err
314 return
315 }
316 }
317
318 if manifest != "" {
Nan Zhangf281bd82017-04-25 16:47:45 -0700319 err = z.writeFile("META-INF/MANIFEST.MF", manifest, zip.Deflate)
Dan Willemsen017d8932016-08-04 15:43:03 -0700320 if err != nil {
321 z.errors <- err
322 return
323 }
324 }
325 }()
326
327 zipw := zip.NewWriter(f)
328
329 var currentWriteOpChan chan *zipEntry
330 var currentWriter io.WriteCloser
331 var currentReaders chan chan io.Reader
332 var currentReader chan io.Reader
333 var done bool
334
335 for !done {
336 var writeOpsChan chan chan *zipEntry
337 var writeOpChan chan *zipEntry
338 var readersChan chan chan io.Reader
339
340 if currentReader != nil {
341 // Only read and process errors
342 } else if currentReaders != nil {
343 readersChan = currentReaders
344 } else if currentWriteOpChan != nil {
345 writeOpChan = currentWriteOpChan
346 } else {
347 writeOpsChan = z.writeOps
348 }
349
350 select {
351 case writeOp, ok := <-writeOpsChan:
352 if !ok {
353 done = true
354 }
355
356 currentWriteOpChan = writeOp
357
358 case op := <-writeOpChan:
359 currentWriteOpChan = nil
360
361 if op.fh.Method == zip.Deflate {
362 currentWriter, err = zipw.CreateCompressedHeader(op.fh)
363 } else {
364 var zw io.Writer
365 zw, err = zipw.CreateHeader(op.fh)
366 currentWriter = nopCloser{zw}
367 }
368 if err != nil {
369 return err
370 }
371
372 currentReaders = op.futureReaders
373 if op.futureReaders == nil {
374 currentWriter.Close()
375 currentWriter = nil
376 }
377
378 case futureReader, ok := <-readersChan:
379 if !ok {
380 // Done with reading
381 currentWriter.Close()
382 currentWriter = nil
383 currentReaders = nil
384 }
385
386 currentReader = futureReader
387
388 case reader := <-currentReader:
389 var count int64
390 count, err = io.Copy(currentWriter, reader)
391 if err != nil {
392 return err
393 }
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700394 z.memoryRateLimiter.Finish(count)
Dan Willemsen017d8932016-08-04 15:43:03 -0700395
396 currentReader = nil
397
398 case err = <-z.errors:
Colin Cross2fe66872015-03-30 17:20:39 -0700399 return err
400 }
401 }
402
Dan Willemsen017d8932016-08-04 15:43:03 -0700403 // One last chance to catch an error
404 select {
405 case err = <-z.errors:
406 return err
407 default:
408 zipw.Close()
409 return nil
Colin Cross2fe66872015-03-30 17:20:39 -0700410 }
Colin Cross2fe66872015-03-30 17:20:39 -0700411}
412
Nan Zhangf281bd82017-04-25 16:47:45 -0700413func (z *zipWriter) writeFile(dest, src string, method uint16) error {
Dan Willemsen017d8932016-08-04 15:43:03 -0700414 var fileSize int64
Dan Willemsen10462b32017-03-15 19:02:51 -0700415 var executable bool
Dan Willemsen017d8932016-08-04 15:43:03 -0700416
Nan Zhang9067b042017-03-17 14:04:43 -0700417 if s, err := os.Lstat(src); err != nil {
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700418 return err
419 } else if s.IsDir() {
Colin Cross957cc4e2015-04-24 15:10:32 -0700420 if z.directories {
Nan Zhang9067b042017-03-17 14:04:43 -0700421 return z.writeDirectory(dest)
Colin Cross957cc4e2015-04-24 15:10:32 -0700422 }
423 return nil
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700424 } else if s.Mode()&os.ModeSymlink != 0 {
Nan Zhang9067b042017-03-17 14:04:43 -0700425 return z.writeSymlink(dest, src)
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700426 } else if !s.Mode().IsRegular() {
Nan Zhang9067b042017-03-17 14:04:43 -0700427 return fmt.Errorf("%s is not a file, directory, or symlink", src)
Dan Willemsen017d8932016-08-04 15:43:03 -0700428 } else {
429 fileSize = s.Size()
Dan Willemsen10462b32017-03-15 19:02:51 -0700430 executable = s.Mode()&0100 != 0
Colin Cross957cc4e2015-04-24 15:10:32 -0700431 }
432
Colin Crosse19c7932015-04-24 15:08:38 -0700433 if z.directories {
Nan Zhang9067b042017-03-17 14:04:43 -0700434 dir, _ := filepath.Split(dest)
Colin Crosse19c7932015-04-24 15:08:38 -0700435 err := z.writeDirectory(dir)
436 if err != nil {
437 return err
Colin Cross2fe66872015-03-30 17:20:39 -0700438 }
439 }
440
Dan Willemsen017d8932016-08-04 15:43:03 -0700441 compressChan := make(chan *zipEntry, 1)
442 z.writeOps <- compressChan
443
444 // Pre-fill a zipEntry, it will be sent in the compressChan once
445 // we're sure about the Method and CRC.
446 ze := &zipEntry{
447 fh: &zip.FileHeader{
Nan Zhang9067b042017-03-17 14:04:43 -0700448 Name: dest,
Nan Zhangf281bd82017-04-25 16:47:45 -0700449 Method: method,
Dan Willemsen017d8932016-08-04 15:43:03 -0700450
451 UncompressedSize64: uint64(fileSize),
452 },
453 }
454 ze.fh.SetModTime(z.time)
Dan Willemsen10462b32017-03-15 19:02:51 -0700455 if executable {
456 ze.fh.SetMode(0700)
457 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700458
Nan Zhang9067b042017-03-17 14:04:43 -0700459 r, err := os.Open(src)
Dan Willemsen017d8932016-08-04 15:43:03 -0700460 if err != nil {
461 return err
462 }
463
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700464 z.cpuRateLimiter.Request()
Dan Willemsen017d8932016-08-04 15:43:03 -0700465
Nan Zhangf281bd82017-04-25 16:47:45 -0700466 if method == zip.Deflate && fileSize >= minParallelFileSize {
Dan Willemsen017d8932016-08-04 15:43:03 -0700467 wg := new(sync.WaitGroup)
468
469 // Allocate enough buffer to hold all readers. We'll limit
470 // this based on actual buffer sizes in RateLimit.
471 ze.futureReaders = make(chan chan io.Reader, (fileSize/parallelBlockSize)+1)
472
473 // Calculate the CRC in the background, since reading the entire
474 // file could take a while.
475 //
476 // We could split this up into chuncks as well, but it's faster
477 // than the compression. Due to the Go Zip API, we also need to
478 // know the result before we can begin writing the compressed
479 // data out to the zipfile.
480 wg.Add(1)
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700481 go z.crcFile(r, ze, compressChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700482
483 for start := int64(0); start < fileSize; start += parallelBlockSize {
484 sr := io.NewSectionReader(r, start, parallelBlockSize)
485 resultChan := make(chan io.Reader, 1)
486 ze.futureReaders <- resultChan
487
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700488 z.cpuRateLimiter.Request()
Dan Willemsen017d8932016-08-04 15:43:03 -0700489
490 last := !(start+parallelBlockSize < fileSize)
491 var dict []byte
492 if start >= windowSize {
493 dict, err = ioutil.ReadAll(io.NewSectionReader(r, start-windowSize, windowSize))
494 }
495
496 wg.Add(1)
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700497 go z.compressPartialFile(sr, dict, last, resultChan, wg)
Dan Willemsen017d8932016-08-04 15:43:03 -0700498 }
499
500 close(ze.futureReaders)
501
502 // Close the file handle after all readers are done
503 go func(wg *sync.WaitGroup, f *os.File) {
504 wg.Wait()
505 f.Close()
506 }(wg, r)
507 } else {
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700508 go z.compressWholeFile(ze, r, compressChan)
Dan Willemsen017d8932016-08-04 15:43:03 -0700509 }
510
511 return nil
512}
513
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700514func (z *zipWriter) crcFile(r io.Reader, ze *zipEntry, resultChan chan *zipEntry, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700515 defer wg.Done()
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700516 defer z.cpuRateLimiter.Finish()
Dan Willemsen017d8932016-08-04 15:43:03 -0700517
518 crc := crc32.NewIEEE()
519 _, err := io.Copy(crc, r)
520 if err != nil {
521 z.errors <- err
522 return
523 }
524
525 ze.fh.CRC32 = crc.Sum32()
526 resultChan <- ze
527 close(resultChan)
528}
529
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700530func (z *zipWriter) compressPartialFile(r io.Reader, dict []byte, last bool, resultChan chan io.Reader, wg *sync.WaitGroup) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700531 defer wg.Done()
532
533 result, err := z.compressBlock(r, dict, last)
534 if err != nil {
535 z.errors <- err
536 return
537 }
538
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700539 z.memoryRateLimiter.Request(int64(result.Len()))
540 z.cpuRateLimiter.Finish()
541
Dan Willemsen017d8932016-08-04 15:43:03 -0700542 resultChan <- result
543}
544
545func (z *zipWriter) compressBlock(r io.Reader, dict []byte, last bool) (*bytes.Buffer, error) {
546 buf := new(bytes.Buffer)
547 var fw *flate.Writer
548 var err error
549 if len(dict) > 0 {
550 // There's no way to Reset a Writer with a new dictionary, so
551 // don't use the Pool
552 fw, err = flate.NewWriterDict(buf, z.compLevel, dict)
553 } else {
554 var ok bool
555 if fw, ok = z.compressorPool.Get().(*flate.Writer); ok {
556 fw.Reset(buf)
557 } else {
558 fw, err = flate.NewWriter(buf, z.compLevel)
559 }
560 defer z.compressorPool.Put(fw)
561 }
562 if err != nil {
563 return nil, err
564 }
565
566 _, err = io.Copy(fw, r)
567 if err != nil {
568 return nil, err
569 }
570 if last {
571 fw.Close()
572 } else {
573 fw.Flush()
574 }
575
576 return buf, nil
577}
578
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700579func (z *zipWriter) compressWholeFile(ze *zipEntry, r *os.File, compressChan chan *zipEntry) {
Dan Willemsen017d8932016-08-04 15:43:03 -0700580 var bufSize int
581
582 defer r.Close()
583
Dan Willemsen017d8932016-08-04 15:43:03 -0700584 crc := crc32.NewIEEE()
Dan Willemsena8b55022017-03-15 21:49:26 -0700585 _, err := io.Copy(crc, r)
Colin Cross2fe66872015-03-30 17:20:39 -0700586 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700587 z.errors <- err
588 return
Colin Cross2fe66872015-03-30 17:20:39 -0700589 }
590
Dan Willemsena8b55022017-03-15 21:49:26 -0700591 ze.fh.CRC32 = crc.Sum32()
Colin Cross2fe66872015-03-30 17:20:39 -0700592
Dan Willemsen017d8932016-08-04 15:43:03 -0700593 _, err = r.Seek(0, 0)
Colin Cross2fe66872015-03-30 17:20:39 -0700594 if err != nil {
Dan Willemsen017d8932016-08-04 15:43:03 -0700595 z.errors <- err
596 return
Colin Cross2fe66872015-03-30 17:20:39 -0700597 }
598
Nan Zhangf281bd82017-04-25 16:47:45 -0700599 readFile := func(r *os.File) ([]byte, error) {
600 _, err = r.Seek(0, 0)
601 if err != nil {
602 return nil, err
603 }
604
605 buf, err := ioutil.ReadAll(r)
606 if err != nil {
607 return nil, err
608 }
609
610 return buf, nil
611 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700612
Dan Willemsena8b55022017-03-15 21:49:26 -0700613 ze.futureReaders = make(chan chan io.Reader, 1)
Dan Willemsen017d8932016-08-04 15:43:03 -0700614 futureReader := make(chan io.Reader, 1)
615 ze.futureReaders <- futureReader
616 close(ze.futureReaders)
617
Nan Zhangf281bd82017-04-25 16:47:45 -0700618 if ze.fh.Method == zip.Deflate {
619 compressed, err := z.compressBlock(r, nil, true)
620 if err != nil {
621 z.errors <- err
622 return
623 }
624 if uint64(compressed.Len()) < ze.fh.UncompressedSize64 {
625 futureReader <- compressed
626 bufSize = compressed.Len()
627 } else {
628 buf, err := readFile(r)
629 if err != nil {
630 z.errors <- err
631 return
632 }
633 ze.fh.Method = zip.Store
634 futureReader <- bytes.NewReader(buf)
635 bufSize = int(ze.fh.UncompressedSize64)
636 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700637 } else {
Nan Zhangf281bd82017-04-25 16:47:45 -0700638 buf, err := readFile(r)
Dan Willemsen017d8932016-08-04 15:43:03 -0700639 if err != nil {
640 z.errors <- err
641 return
642 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700643 ze.fh.Method = zip.Store
644 futureReader <- bytes.NewReader(buf)
645 bufSize = int(ze.fh.UncompressedSize64)
646 }
Nan Zhangf281bd82017-04-25 16:47:45 -0700647
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700648 z.memoryRateLimiter.Request(int64(bufSize))
649 z.cpuRateLimiter.Finish()
650
Dan Willemsen017d8932016-08-04 15:43:03 -0700651 close(futureReader)
652
653 compressChan <- ze
654 close(compressChan)
Colin Cross2fe66872015-03-30 17:20:39 -0700655}
Colin Crosse19c7932015-04-24 15:08:38 -0700656
657func (z *zipWriter) writeDirectory(dir string) error {
Jeff Gaston2d174132017-08-15 18:05:56 -0700658 // clean the input
659 cleanDir := filepath.Clean(dir)
660
661 // discover any uncreated directories in the path
662 zipDirs := []string{}
663 for cleanDir != "" && cleanDir != "." && !z.createdDirs[cleanDir] {
664
665 z.createdDirs[cleanDir] = true
666 // parent directories precede their children
667 zipDirs = append([]string{cleanDir}, zipDirs...)
668
669 cleanDir = filepath.Dir(cleanDir)
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700670 }
671
Jeff Gaston2d174132017-08-15 18:05:56 -0700672 // make a directory entry for each uncreated directory
673 for _, cleanDir := range zipDirs {
Colin Crosse19c7932015-04-24 15:08:38 -0700674 dirHeader := &zip.FileHeader{
Jeff Gaston2d174132017-08-15 18:05:56 -0700675 Name: cleanDir + "/",
Colin Crosse19c7932015-04-24 15:08:38 -0700676 }
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700677 dirHeader.SetMode(0700 | os.ModeDir)
Colin Crosse19c7932015-04-24 15:08:38 -0700678 dirHeader.SetModTime(z.time)
679
Dan Willemsen017d8932016-08-04 15:43:03 -0700680 ze := make(chan *zipEntry, 1)
681 ze <- &zipEntry{
682 fh: dirHeader,
Colin Crosse19c7932015-04-24 15:08:38 -0700683 }
Dan Willemsen017d8932016-08-04 15:43:03 -0700684 close(ze)
685 z.writeOps <- ze
Colin Crosse19c7932015-04-24 15:08:38 -0700686 }
687
688 return nil
689}
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700690
691func (z *zipWriter) writeSymlink(rel, file string) error {
692 if z.directories {
693 dir, _ := filepath.Split(rel)
694 if err := z.writeDirectory(dir); err != nil {
695 return err
696 }
697 }
698
699 fileHeader := &zip.FileHeader{
700 Name: rel,
701 }
702 fileHeader.SetModTime(z.time)
703 fileHeader.SetMode(0700 | os.ModeSymlink)
704
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700705 dest, err := os.Readlink(file)
706 if err != nil {
707 return err
708 }
709
Dan Willemsen017d8932016-08-04 15:43:03 -0700710 ze := make(chan *zipEntry, 1)
711 futureReaders := make(chan chan io.Reader, 1)
712 futureReader := make(chan io.Reader, 1)
713 futureReaders <- futureReader
714 close(futureReaders)
715 futureReader <- bytes.NewBufferString(dest)
716 close(futureReader)
717
718 // We didn't ask permission to execute, since this should be very short
719 // but we still need to increment the outstanding buffer sizes, since
720 // the read will decrement the buffer size.
Jeff Gaston69f3b3e2017-08-15 16:51:01 -0700721 z.memoryRateLimiter.Finish(int64(-len(dest)))
Dan Willemsen017d8932016-08-04 15:43:03 -0700722
723 ze <- &zipEntry{
724 fh: fileHeader,
725 futureReaders: futureReaders,
726 }
727 close(ze)
728 z.writeOps <- ze
729
730 return nil
Dan Willemsena59a3bc2016-08-03 17:47:23 -0700731}