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" 20 "log" 21 "os" 22 "path/filepath" 23 "sort" 24 "strings" 25 "time" 26 27 "github.com/google/blueprint/pathtools" 28 29 "android/soong/jar" 30 "android/soong/third_party/zip" 31 ) 32 33 var ( 34 input = flag.String("i", "", "zip file to read from") 35 output = flag.String("o", "", "output file") 36 sortGlobs = flag.Bool("s", false, "sort matches from each glob (defaults to the order from the input zip file)") 37 sortJava = flag.Bool("j", false, "sort using jar ordering within each glob (META-INF/MANIFEST.MF first)") 38 setTime = flag.Bool("t", false, "set timestamps to 2009-01-01 00:00:00") 39 40 staticTime = time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC) 41 42 excludes excludeArgs 43 ) 44 45 func init() { 46 flag.Var(&excludes, "x", "exclude a filespec from the output") 47 } 48 49 func main() { 50 flag.Usage = func() { 51 fmt.Fprintln(os.Stderr, "usage: zip2zip -i zipfile -o zipfile [-s|-j] [-t] [filespec]...") 52 flag.PrintDefaults() 53 fmt.Fprintln(os.Stderr, " filespec:") 54 fmt.Fprintln(os.Stderr, " <name>") 55 fmt.Fprintln(os.Stderr, " <in_name>:<out_name>") 56 fmt.Fprintln(os.Stderr, " <glob>[:<out_dir>]") 57 fmt.Fprintln(os.Stderr, "") 58 fmt.Fprintln(os.Stderr, "<glob> uses the rules at https://godoc.org/github.com/google/blueprint/pathtools/#Match") 59 fmt.Fprintln(os.Stderr, "") 60 fmt.Fprintln(os.Stderr, "Files will be copied with their existing compression from the input zipfile to") 61 fmt.Fprintln(os.Stderr, "the output zipfile, in the order of filespec arguments.") 62 fmt.Fprintln(os.Stderr, "") 63 fmt.Fprintln(os.Stderr, "If no filepsec is provided all files and directories are copied.") 64 } 65 66 flag.Parse() 67 68 if *input == "" || *output == "" { 69 flag.Usage() 70 os.Exit(1) 71 } 72 73 log.SetFlags(log.Lshortfile) 74 75 reader, err := zip.OpenReader(*input) 76 if err != nil { 77 log.Fatal(err) 78 } 79 defer reader.Close() 80 81 output, err := os.Create(*output) 82 if err != nil { 83 log.Fatal(err) 84 } 85 defer output.Close() 86 87 writer := zip.NewWriter(output) 88 defer func() { 89 err := writer.Close() 90 if err != nil { 91 log.Fatal(err) 92 } 93 }() 94 95 if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime, 96 flag.Args(), excludes); err != nil { 97 98 log.Fatal(err) 99 } 100 } 101 102 type pair struct { 103 *zip.File 104 newName string 105 } 106 107 func zip2zip(reader *zip.Reader, writer *zip.Writer, sortOutput, sortJava, setTime bool, 108 includes []string, excludes []string) error { 109 110 matches := []pair{} 111 112 sortMatches := func(matches []pair) { 113 if sortJava { 114 sort.SliceStable(matches, func(i, j int) bool { 115 return jar.EntryNamesLess(matches[i].newName, matches[j].newName) 116 }) 117 } else if sortOutput { 118 sort.SliceStable(matches, func(i, j int) bool { 119 return matches[i].newName < matches[j].newName 120 }) 121 } 122 } 123 124 for _, include := range includes { 125 // Reserve escaping for future implementation, so make sure no 126 // one is using \ and expecting a certain behavior. 127 if strings.Contains(include, "\\") { 128 return fmt.Errorf("\\ characters are not currently supported") 129 } 130 131 input, output := includeSplit(include) 132 133 var includeMatches []pair 134 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. 145 _, name := filepath.Split(file.Name) 146 newName = filepath.Join(output, name) 147 } else { 148 // Otherwise it is a file. 149 newName = output 150 } 151 } 152 includeMatches = append(includeMatches, pair{file, newName}) 153 } 154 } 155 156 sortMatches(includeMatches) 157 matches = append(matches, includeMatches...) 158 } 159 160 if len(includes) == 0 { 161 // implicitly match everything 162 for _, file := range reader.File { 163 matches = append(matches, pair{file, file.Name}) 164 } 165 sortMatches(matches) 166 } 167 168 var matchesAfterExcludes []pair 169 seen := make(map[string]*zip.File) 170 171 for _, match := range matches { 172 // Filter out matches whose original file name matches an exclude filter 173 excluded := false 174 for _, exclude := range excludes { 175 if excludeMatch, err := pathtools.Match(exclude, match.File.Name); err != nil { 176 return err 177 } else if excludeMatch { 178 excluded = true 179 break 180 } 181 } 182 183 if excluded { 184 continue 185 } 186 187 // Check for duplicate output names, ignoring ones that come from the same input zip entry. 188 if prev, exists := seen[match.newName]; exists { 189 if prev != match.File { 190 return fmt.Errorf("multiple entries for %q with different contents", match.newName) 191 } 192 continue 193 } 194 seen[match.newName] = match.File 195 196 matchesAfterExcludes = append(matchesAfterExcludes, match) 197 } 198 199 for _, match := range matchesAfterExcludes { 200 if setTime { 201 match.File.SetModTime(staticTime) 202 } 203 if err := writer.CopyFrom(match.File, match.newName); err != nil { 204 return err 205 } 206 } 207 208 return nil 209 } 210 211 func includeSplit(s string) (string, string) { 212 split := strings.SplitN(s, ":", 2) 213 if len(split) == 2 { 214 return split[0], split[1] 215 } else { 216 return split[0], "" 217 } 218 } 219 220 type excludeArgs []string 221 222 func (e *excludeArgs) String() string { 223 return strings.Join(*e, " ") 224 } 225 226 func (e *excludeArgs) Set(s string) error { 227 *e = append(*e, s) 228 return nil 229 } 230