Improve soong_zip filename collisions

Allow filename collisions for directories, which may happen if
multiple globs of resources include the same directory names.
Continue to report errors if collisions occur between files, and
also add checks for collisions between files and directories.

Test: manual
Change-Id: Iac19fbd325c53fbb41552ea230d813c8dddf9439
diff --git a/cmd/soong_zip/soong_zip.go b/cmd/soong_zip/soong_zip.go
index 78a77b8..ae176df 100644
--- a/cmd/soong_zip/soong_zip.go
+++ b/cmd/soong_zip/soong_zip.go
@@ -190,9 +190,10 @@
 }
 
 type zipWriter struct {
-	time        time.Time
-	createdDirs map[string]bool
-	directories bool
+	time         time.Time
+	createdFiles map[string]string
+	createdDirs  map[string]string
+	directories  bool
 
 	errors   chan error
 	writeOps chan chan *zipEntry
@@ -254,14 +255,14 @@
 	}
 
 	w := &zipWriter{
-		time:        time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC),
-		createdDirs: make(map[string]bool),
-		directories: *directories,
-		compLevel:   *compLevel,
+		time:         time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC),
+		createdDirs:  make(map[string]string),
+		createdFiles: make(map[string]string),
+		directories:  *directories,
+		compLevel:    *compLevel,
 	}
 
 	pathMappings := []pathMapping{}
-	set := make(map[string]string)
 
 	for _, fa := range fArgs {
 		srcs := fa.sourceFiles
@@ -270,7 +271,7 @@
 		}
 		for _, src := range srcs {
 			if err := fillPathPairs(fa.pathPrefixInZip,
-				fa.sourcePrefixToStrip, src, set, &pathMappings); err != nil {
+				fa.sourcePrefixToStrip, src, &pathMappings); err != nil {
 				log.Fatal(err)
 			}
 		}
@@ -283,7 +284,7 @@
 	}
 }
 
-func fillPathPairs(prefix, rel, src string, set map[string]string, pathMappings *[]pathMapping) error {
+func fillPathPairs(prefix, rel, src string, pathMappings *[]pathMapping) error {
 	src = strings.TrimSpace(src)
 	if src == "" {
 		return nil
@@ -295,14 +296,6 @@
 	}
 	dest = filepath.Join(prefix, dest)
 
-	if _, found := set[dest]; found {
-		return fmt.Errorf("found two file paths to be copied into dest path: %q,"+
-			" both [%q]%q and [%q]%q!",
-			dest, dest, src, dest, set[dest])
-	} else {
-		set[dest] = src
-	}
-
 	zipMethod := zip.Deflate
 	if _, found := nonDeflatedFiles[dest]; found {
 		zipMethod = zip.Store
@@ -488,14 +481,29 @@
 		return err
 	} else if s.IsDir() {
 		if z.directories {
-			return z.writeDirectory(dest)
+			return z.writeDirectory(dest, src)
 		}
 		return nil
-	} else if s.Mode()&os.ModeSymlink != 0 {
-		return z.writeSymlink(dest, src)
-	} else if !s.Mode().IsRegular() {
-		return fmt.Errorf("%s is not a file, directory, or symlink", src)
 	} else {
+		if err := z.writeDirectory(filepath.Dir(dest), src); err != nil {
+			return err
+		}
+
+		if prev, exists := z.createdDirs[dest]; exists {
+			return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
+		}
+		if prev, exists := z.createdFiles[dest]; exists {
+			return fmt.Errorf("destination %q has two files %q and %q", dest, prev, src)
+		}
+
+		z.createdFiles[dest] = src
+
+		if s.Mode()&os.ModeSymlink != 0 {
+			return z.writeSymlink(dest, src)
+		} else if !s.Mode().IsRegular() {
+			return fmt.Errorf("%s is not a file, directory, or symlink", src)
+		}
+
 		fileSize = s.Size()
 		executable = s.Mode()&0100 != 0
 	}
@@ -518,13 +526,19 @@
 	return z.writeFileContents(header, r)
 }
 
-// writes the contents of r according to the specifications in header
 func (z *zipWriter) addManifest(dest string, src string, method uint16) error {
 	givenBytes, err := ioutil.ReadFile(src)
 	if err != nil {
 		return err
 	}
 
+	if prev, exists := z.createdDirs[dest]; exists {
+		return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
+	}
+	if prev, exists := z.createdFiles[dest]; exists {
+		return fmt.Errorf("destination %q has two files %q and %q", dest, prev, src)
+	}
+
 	manifestMarker := []byte("Manifest-Version:")
 	header := append(manifestMarker, []byte(" 1.0\nCreated-By: soong_zip\n")...)
 
@@ -552,15 +566,6 @@
 
 	header.SetModTime(z.time)
 
-	if z.directories {
-		dest := header.Name
-		dir, _ := filepath.Split(dest)
-		err := z.writeDirectory(dir)
-		if err != nil {
-			return err
-		}
-	}
-
 	compressChan := make(chan *zipEntry, 1)
 	z.writeOps <- compressChan
 
@@ -781,53 +786,57 @@
 	zipHeader.Extra = append(zipHeader.Extra, data...)
 }
 
-func (z *zipWriter) writeDirectory(dir string) error {
+// writeDirectory annotates that dir is a directory created for the src file or directory, and adds
+// the directory entry to the zip file if directories are enabled.
+func (z *zipWriter) writeDirectory(dir, src string) error {
 	// clean the input
-	cleanDir := filepath.Clean(dir)
+	dir = filepath.Clean(dir)
 
 	// discover any uncreated directories in the path
 	zipDirs := []string{}
-	for cleanDir != "" && cleanDir != "." && !z.createdDirs[cleanDir] {
+	for dir != "" && dir != "." {
+		if _, exists := z.createdDirs[dir]; exists {
+			break
+		}
 
-		z.createdDirs[cleanDir] = true
+		if prev, exists := z.createdFiles[dir]; exists {
+			return fmt.Errorf("destination %q is both a directory %q and a file %q", dir, src, prev)
+		}
+
+		z.createdDirs[dir] = src
 		// parent directories precede their children
-		zipDirs = append([]string{cleanDir}, zipDirs...)
+		zipDirs = append([]string{dir}, zipDirs...)
 
-		cleanDir = filepath.Dir(cleanDir)
+		dir = filepath.Dir(dir)
 	}
 
-	// make a directory entry for each uncreated directory
-	for _, cleanDir := range zipDirs {
-		dirHeader := &zip.FileHeader{
-			Name: cleanDir + "/",
-		}
-		dirHeader.SetMode(0700 | os.ModeDir)
-		dirHeader.SetModTime(z.time)
+	if z.directories {
+		// make a directory entry for each uncreated directory
+		for _, cleanDir := range zipDirs {
+			dirHeader := &zip.FileHeader{
+				Name: cleanDir + "/",
+			}
+			dirHeader.SetMode(0700 | os.ModeDir)
+			dirHeader.SetModTime(z.time)
 
-		if *emulateJar && dir == "META-INF/" {
-			// Jar files have a 0-length extra field with header "CAFE"
-			z.addExtraField(dirHeader, [2]byte{0xca, 0xfe}, []byte{})
-		}
+			if *emulateJar && dir == "META-INF/" {
+				// Jar files have a 0-length extra field with header "CAFE"
+				z.addExtraField(dirHeader, [2]byte{0xca, 0xfe}, []byte{})
+			}
 
-		ze := make(chan *zipEntry, 1)
-		ze <- &zipEntry{
-			fh: dirHeader,
+			ze := make(chan *zipEntry, 1)
+			ze <- &zipEntry{
+				fh: dirHeader,
+			}
+			close(ze)
+			z.writeOps <- ze
 		}
-		close(ze)
-		z.writeOps <- ze
 	}
 
 	return nil
 }
 
 func (z *zipWriter) writeSymlink(rel, file string) error {
-	if z.directories {
-		dir, _ := filepath.Split(rel)
-		if err := z.writeDirectory(dir); err != nil {
-			return err
-		}
-	}
-
 	fileHeader := &zip.FileHeader{
 		Name: rel,
 	}