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