Support in soong_zip to write byte buffers in addition to copying files

This will enable writing a modified manifest without having to create a temporary file first

Bug: 64536066
Test: soong_zip --jar -o /tmp/out.zip -C . -l files.list && \
      # make sure that the output is binary equal
      # with and without this patch

Change-Id: I559d653e0e72e641e1ee6745924cb835bb0a355b
diff --git a/cmd/soong_zip/soong_zip.go b/cmd/soong_zip/soong_zip.go
index bb2a70f..f7dc9e0 100644
--- a/cmd/soong_zip/soong_zip.go
+++ b/cmd/soong_zip/soong_zip.go
@@ -286,6 +286,13 @@
 	sort.SliceStable(mappings, less)
 }
 
+type readerSeekerCloser interface {
+	io.Reader
+	io.ReaderAt
+	io.Closer
+	io.Seeker
+}
+
 func (z *zipWriter) write(out string, pathMappings []pathMapping, manifest string) error {
 	f, err := os.Create(out)
 	if err != nil {
@@ -434,6 +441,7 @@
 	}
 }
 
+// imports (possibly with compression) <src> into the zip at sub-path <dest>
 func (z *zipWriter) writeFile(dest, src string, method uint16) error {
 	var fileSize int64
 	var executable bool
@@ -454,7 +462,31 @@
 		executable = s.Mode()&0100 != 0
 	}
 
+	r, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+
+	header := &zip.FileHeader{
+		Name:               dest,
+		Method:             method,
+		UncompressedSize64: uint64(fileSize),
+	}
+
+	if executable {
+		header.SetMode(0700)
+	}
+
+	return z.writeFileContents(header, r)
+}
+
+// writes the contents of r according to the specifications in header
+func (z *zipWriter) writeFileContents(header *zip.FileHeader, r readerSeekerCloser) (err error) {
+
+	header.SetModTime(z.time)
+
 	if z.directories {
+		dest := header.Name
 		dir, _ := filepath.Split(dest)
 		err := z.writeDirectory(dir)
 		if err != nil {
@@ -468,28 +500,19 @@
 	// Pre-fill a zipEntry, it will be sent in the compressChan once
 	// we're sure about the Method and CRC.
 	ze := &zipEntry{
-		fh: &zip.FileHeader{
-			Name:   dest,
-			Method: method,
-
-			UncompressedSize64: uint64(fileSize),
-		},
-	}
-	ze.fh.SetModTime(z.time)
-	if executable {
-		ze.fh.SetMode(0700)
+		fh: header,
 	}
 
-	r, err := os.Open(src)
-	if err != nil {
-		return err
-	}
-
-	ze.allocatedSize = fileSize
+	ze.allocatedSize = int64(header.UncompressedSize64)
 	z.cpuRateLimiter.Request()
 	z.memoryRateLimiter.Request(ze.allocatedSize)
 
-	if method == zip.Deflate && fileSize >= minParallelFileSize {
+	fileSize := int64(header.UncompressedSize64)
+	if fileSize == 0 {
+		fileSize = int64(header.UncompressedSize)
+	}
+
+	if header.Method == zip.Deflate && fileSize >= minParallelFileSize {
 		wg := new(sync.WaitGroup)
 
 		// Allocate enough buffer to hold all readers. We'll limit
@@ -499,7 +522,7 @@
 		// Calculate the CRC in the background, since reading the entire
 		// file could take a while.
 		//
-		// We could split this up into chuncks as well, but it's faster
+		// We could split this up into chunks as well, but it's faster
 		// than the compression. Due to the Go Zip API, we also need to
 		// know the result before we can begin writing the compressed
 		// data out to the zipfile.
@@ -517,6 +540,9 @@
 			var dict []byte
 			if start >= windowSize {
 				dict, err = ioutil.ReadAll(io.NewSectionReader(r, start-windowSize, windowSize))
+				if err != nil {
+					return err
+				}
 			}
 
 			wg.Add(1)
@@ -526,12 +552,15 @@
 		close(ze.futureReaders)
 
 		// Close the file handle after all readers are done
-		go func(wg *sync.WaitGroup, f *os.File) {
+		go func(wg *sync.WaitGroup, closer io.Closer) {
 			wg.Wait()
-			f.Close()
+			closer.Close()
 		}(wg, r)
 	} else {
-		go z.compressWholeFile(ze, r, compressChan)
+		go func() {
+			z.compressWholeFile(ze, r, compressChan)
+			r.Close()
+		}()
 	}
 
 	return nil
@@ -601,8 +630,7 @@
 	return buf, nil
 }
 
-func (z *zipWriter) compressWholeFile(ze *zipEntry, r *os.File, compressChan chan *zipEntry) {
-	defer r.Close()
+func (z *zipWriter) compressWholeFile(ze *zipEntry, r io.ReadSeeker, compressChan chan *zipEntry) {
 
 	crc := crc32.NewIEEE()
 	_, err := io.Copy(crc, r)
@@ -619,13 +647,13 @@
 		return
 	}
 
-	readFile := func(r *os.File) ([]byte, error) {
-		_, err = r.Seek(0, 0)
+	readFile := func(reader io.ReadSeeker) ([]byte, error) {
+		_, err := reader.Seek(0, 0)
 		if err != nil {
 			return nil, err
 		}
 
-		buf, err := ioutil.ReadAll(r)
+		buf, err := ioutil.ReadAll(reader)
 		if err != nil {
 			return nil, err
 		}