zip2zip.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // Copyright 2016 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. package main
  15. import (
  16. "flag"
  17. "fmt"
  18. "io"
  19. "log"
  20. "os"
  21. "path/filepath"
  22. "sort"
  23. "strings"
  24. "time"
  25. "github.com/google/blueprint/pathtools"
  26. "android/soong/jar"
  27. "android/soong/third_party/zip"
  28. )
  29. var (
  30. input = flag.String("i", "", "zip file to read from")
  31. output = flag.String("o", "", "output file")
  32. sortGlobs = flag.Bool("s", false, "sort matches from each glob (defaults to the order from the input zip file)")
  33. sortJava = flag.Bool("j", false, "sort using jar ordering within each glob (META-INF/MANIFEST.MF first)")
  34. setTime = flag.Bool("t", false, "set timestamps to 2009-01-01 00:00:00")
  35. staticTime = time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC)
  36. excludes multiFlag
  37. includes multiFlag
  38. uncompress multiFlag
  39. )
  40. func init() {
  41. flag.Var(&excludes, "x", "exclude a filespec from the output")
  42. flag.Var(&includes, "X", "include a filespec in the output that was previously excluded")
  43. flag.Var(&uncompress, "0", "convert a filespec to uncompressed in the output")
  44. }
  45. func main() {
  46. flag.Usage = func() {
  47. fmt.Fprintln(os.Stderr, "usage: zip2zip -i zipfile -o zipfile [-s|-j] [-t] [filespec]...")
  48. flag.PrintDefaults()
  49. fmt.Fprintln(os.Stderr, " filespec:")
  50. fmt.Fprintln(os.Stderr, " <name>")
  51. fmt.Fprintln(os.Stderr, " <in_name>:<out_name>")
  52. fmt.Fprintln(os.Stderr, " <glob>[:<out_dir>]")
  53. fmt.Fprintln(os.Stderr, "")
  54. fmt.Fprintln(os.Stderr, "<glob> uses the rules at https://godoc.org/github.com/google/blueprint/pathtools/#Match")
  55. fmt.Fprintln(os.Stderr, "")
  56. fmt.Fprintln(os.Stderr, "Files will be copied with their existing compression from the input zipfile to")
  57. fmt.Fprintln(os.Stderr, "the output zipfile, in the order of filespec arguments.")
  58. fmt.Fprintln(os.Stderr, "")
  59. fmt.Fprintln(os.Stderr, "If no filepsec is provided all files and directories are copied.")
  60. }
  61. flag.Parse()
  62. if *input == "" || *output == "" {
  63. flag.Usage()
  64. os.Exit(1)
  65. }
  66. log.SetFlags(log.Lshortfile)
  67. reader, err := zip.OpenReader(*input)
  68. if err != nil {
  69. log.Fatal(err)
  70. }
  71. defer reader.Close()
  72. output, err := os.Create(*output)
  73. if err != nil {
  74. log.Fatal(err)
  75. }
  76. defer output.Close()
  77. writer := zip.NewWriter(output)
  78. defer func() {
  79. err := writer.Close()
  80. if err != nil {
  81. log.Fatal(err)
  82. }
  83. }()
  84. if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime,
  85. flag.Args(), excludes, includes, uncompress); err != nil {
  86. log.Fatal(err)
  87. }
  88. }
  89. type pair struct {
  90. *zip.File
  91. newName string
  92. uncompress bool
  93. }
  94. func zip2zip(reader *zip.Reader, writer *zip.Writer, sortOutput, sortJava, setTime bool,
  95. args []string, excludes, includes multiFlag, uncompresses []string) error {
  96. matches := []pair{}
  97. sortMatches := func(matches []pair) {
  98. if sortJava {
  99. sort.SliceStable(matches, func(i, j int) bool {
  100. return jar.EntryNamesLess(matches[i].newName, matches[j].newName)
  101. })
  102. } else if sortOutput {
  103. sort.SliceStable(matches, func(i, j int) bool {
  104. return matches[i].newName < matches[j].newName
  105. })
  106. }
  107. }
  108. for _, arg := range args {
  109. input, output := includeSplit(arg)
  110. var includeMatches []pair
  111. for _, file := range reader.File {
  112. var newName string
  113. if match, err := pathtools.Match(input, file.Name); err != nil {
  114. return err
  115. } else if match {
  116. if output == "" {
  117. newName = file.Name
  118. } else {
  119. if pathtools.IsGlob(input) {
  120. // If the input is a glob then the output is a directory.
  121. rel, err := filepath.Rel(constantPartOfPattern(input), file.Name)
  122. if err != nil {
  123. return err
  124. } else if strings.HasPrefix("../", rel) {
  125. return fmt.Errorf("globbed path %q was not in %q", file.Name, constantPartOfPattern(input))
  126. }
  127. newName = filepath.Join(output, rel)
  128. } else {
  129. // Otherwise it is a file.
  130. newName = output
  131. }
  132. }
  133. includeMatches = append(includeMatches, pair{file, newName, false})
  134. }
  135. }
  136. sortMatches(includeMatches)
  137. matches = append(matches, includeMatches...)
  138. }
  139. if len(args) == 0 {
  140. // implicitly match everything
  141. for _, file := range reader.File {
  142. matches = append(matches, pair{file, file.Name, false})
  143. }
  144. sortMatches(matches)
  145. }
  146. var matchesAfterExcludes []pair
  147. seen := make(map[string]*zip.File)
  148. for _, match := range matches {
  149. // Filter out matches whose original file name matches an exclude filter, unless it also matches an
  150. // include filter
  151. if exclude, err := excludes.Match(match.File.Name); err != nil {
  152. return err
  153. } else if exclude {
  154. if include, err := includes.Match(match.File.Name); err != nil {
  155. return err
  156. } else if !include {
  157. continue
  158. }
  159. }
  160. // Check for duplicate output names, ignoring ones that come from the same input zip entry.
  161. if prev, exists := seen[match.newName]; exists {
  162. if prev != match.File {
  163. return fmt.Errorf("multiple entries for %q with different contents", match.newName)
  164. }
  165. continue
  166. }
  167. seen[match.newName] = match.File
  168. for _, u := range uncompresses {
  169. if uncompressMatch, err := pathtools.Match(u, match.newName); err != nil {
  170. return err
  171. } else if uncompressMatch {
  172. match.uncompress = true
  173. break
  174. }
  175. }
  176. matchesAfterExcludes = append(matchesAfterExcludes, match)
  177. }
  178. for _, match := range matchesAfterExcludes {
  179. if setTime {
  180. match.File.SetModTime(staticTime)
  181. }
  182. if match.uncompress && match.File.FileHeader.Method != zip.Store {
  183. fh := match.File.FileHeader
  184. fh.Name = match.newName
  185. fh.Method = zip.Store
  186. fh.CompressedSize64 = fh.UncompressedSize64
  187. zw, err := writer.CreateHeaderAndroid(&fh)
  188. if err != nil {
  189. return err
  190. }
  191. zr, err := match.File.Open()
  192. if err != nil {
  193. return err
  194. }
  195. _, err = io.Copy(zw, zr)
  196. zr.Close()
  197. if err != nil {
  198. return err
  199. }
  200. } else {
  201. err := writer.CopyFrom(match.File, match.newName)
  202. if err != nil {
  203. return err
  204. }
  205. }
  206. }
  207. return nil
  208. }
  209. func includeSplit(s string) (string, string) {
  210. split := strings.SplitN(s, ":", 2)
  211. if len(split) == 2 {
  212. return split[0], split[1]
  213. } else {
  214. return split[0], ""
  215. }
  216. }
  217. type multiFlag []string
  218. func (m *multiFlag) String() string {
  219. return strings.Join(*m, " ")
  220. }
  221. func (m *multiFlag) Set(s string) error {
  222. *m = append(*m, s)
  223. return nil
  224. }
  225. func (m *multiFlag) Match(s string) (bool, error) {
  226. if m == nil {
  227. return false, nil
  228. }
  229. for _, f := range *m {
  230. if match, err := pathtools.Match(f, s); err != nil {
  231. return false, err
  232. } else if match {
  233. return true, nil
  234. }
  235. }
  236. return false, nil
  237. }
  238. func constantPartOfPattern(pattern string) string {
  239. ret := ""
  240. for pattern != "" {
  241. var first string
  242. first, pattern = splitFirst(pattern)
  243. if pathtools.IsGlob(first) {
  244. return ret
  245. }
  246. ret = filepath.Join(ret, first)
  247. }
  248. return ret
  249. }
  250. func splitFirst(path string) (string, string) {
  251. i := strings.IndexRune(path, filepath.Separator)
  252. if i < 0 {
  253. return path, ""
  254. }
  255. return path[:i], path[i+1:]
  256. }