1// Copyright 2018 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 15package main 16 17import ( 18 "archive/zip" 19 "flag" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "log" 24 "os" 25 "path/filepath" 26 "strings" 27) 28 29var ( 30 outputDir = flag.String("d", "", "output dir") 31 outputFile = flag.String("l", "", "output list file") 32 filter = flag.String("f", "", "optional filter pattern") 33 zipPrefix = flag.String("zip-prefix", "", "optional prefix within the zip file to extract, stripping the prefix") 34) 35 36func must(err error) { 37 if err != nil { 38 log.Fatal(err) 39 } 40} 41 42func writeFile(filename string, in io.Reader, perm os.FileMode) error { 43 out, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) 44 if err != nil { 45 return err 46 } 47 _, err = io.Copy(out, in) 48 if err != nil { 49 out.Close() 50 return err 51 } 52 53 return out.Close() 54} 55 56func main() { 57 flag.Usage = func() { 58 fmt.Fprintln(os.Stderr, "usage: zipsync -d <output dir> [-l <output file>] [-f <pattern>] [zip]...") 59 flag.PrintDefaults() 60 } 61 62 flag.Parse() 63 64 if *outputDir == "" { 65 flag.Usage() 66 os.Exit(1) 67 } 68 69 inputs := flag.Args() 70 71 // For now, just wipe the output directory and replace its contents with the zip files 72 // Eventually this could only modify the directory contents as necessary to bring it up 73 // to date with the zip files. 74 must(os.RemoveAll(*outputDir)) 75 76 must(os.MkdirAll(*outputDir, 0777)) 77 78 var files []string 79 seen := make(map[string]string) 80 81 if *zipPrefix != "" { 82 *zipPrefix = filepath.Clean(*zipPrefix) + "/" 83 } 84 85 for _, input := range inputs { 86 reader, err := zip.OpenReader(input) 87 if err != nil { 88 log.Fatal(err) 89 } 90 defer reader.Close() 91 92 for _, f := range reader.File { 93 name := f.Name 94 if *zipPrefix != "" { 95 if !strings.HasPrefix(name, *zipPrefix) { 96 continue 97 } 98 name = strings.TrimPrefix(name, *zipPrefix) 99 } 100 if *filter != "" { 101 if match, err := filepath.Match(*filter, filepath.Base(name)); err != nil { 102 log.Fatal(err) 103 } else if !match { 104 continue 105 } 106 } 107 if filepath.IsAbs(name) { 108 log.Fatalf("%q in %q is an absolute path", name, input) 109 } 110 111 if prev, exists := seen[name]; exists { 112 log.Fatalf("%q found in both %q and %q", name, prev, input) 113 } 114 seen[name] = input 115 116 filename := filepath.Join(*outputDir, name) 117 if f.FileInfo().IsDir() { 118 must(os.MkdirAll(filename, 0777)) 119 } else { 120 must(os.MkdirAll(filepath.Dir(filename), 0777)) 121 in, err := f.Open() 122 if err != nil { 123 log.Fatal(err) 124 } 125 must(writeFile(filename, in, f.FileInfo().Mode())) 126 in.Close() 127 files = append(files, filename) 128 } 129 } 130 } 131 132 if *outputFile != "" { 133 data := strings.Join(files, "\n") 134 if len(files) > 0 { 135 data += "\n" 136 } 137 must(ioutil.WriteFile(*outputFile, []byte(data), 0666)) 138 } 139} 140