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 zipPrefix = flag.String("zip-prefix", "", "optional prefix within the zip file to extract, stripping the prefix") 33 filter multiFlag 34) 35 36func init() { 37 flag.Var(&filter, "f", "optional filter pattern") 38} 39 40func must(err error) { 41 if err != nil { 42 log.Fatal(err) 43 } 44} 45 46func writeFile(filename string, in io.Reader, perm os.FileMode) error { 47 out, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) 48 if err != nil { 49 return err 50 } 51 _, err = io.Copy(out, in) 52 if err != nil { 53 out.Close() 54 return err 55 } 56 57 return out.Close() 58} 59 60func writeSymlink(filename string, in io.Reader) error { 61 b, err := ioutil.ReadAll(in) 62 if err != nil { 63 return err 64 } 65 dest := string(b) 66 err = os.Symlink(dest, filename) 67 return err 68} 69 70func main() { 71 flag.Usage = func() { 72 fmt.Fprintln(os.Stderr, "usage: zipsync -d <output dir> [-l <output file>] [-f <pattern>] [zip]...") 73 flag.PrintDefaults() 74 } 75 76 flag.Parse() 77 78 if *outputDir == "" { 79 flag.Usage() 80 os.Exit(1) 81 } 82 83 inputs := flag.Args() 84 85 // For now, just wipe the output directory and replace its contents with the zip files 86 // Eventually this could only modify the directory contents as necessary to bring it up 87 // to date with the zip files. 88 must(os.RemoveAll(*outputDir)) 89 90 must(os.MkdirAll(*outputDir, 0777)) 91 92 var files []string 93 seen := make(map[string]string) 94 95 if *zipPrefix != "" { 96 *zipPrefix = filepath.Clean(*zipPrefix) + "/" 97 } 98 99 for _, input := range inputs { 100 reader, err := zip.OpenReader(input) 101 if err != nil { 102 log.Fatal(err) 103 } 104 defer reader.Close() 105 106 for _, f := range reader.File { 107 name := f.Name 108 if *zipPrefix != "" { 109 if !strings.HasPrefix(name, *zipPrefix) { 110 continue 111 } 112 name = strings.TrimPrefix(name, *zipPrefix) 113 } 114 115 if filter != nil { 116 if match, err := filter.Match(filepath.Base(name)); err != nil { 117 log.Fatal(err) 118 } else if !match { 119 continue 120 } 121 } 122 123 if filepath.IsAbs(name) { 124 log.Fatalf("%q in %q is an absolute path", name, input) 125 } 126 127 if prev, exists := seen[name]; exists { 128 log.Fatalf("%q found in both %q and %q", name, prev, input) 129 } 130 seen[name] = input 131 132 filename := filepath.Join(*outputDir, name) 133 if f.FileInfo().IsDir() { 134 must(os.MkdirAll(filename, 0777)) 135 } else { 136 must(os.MkdirAll(filepath.Dir(filename), 0777)) 137 in, err := f.Open() 138 if err != nil { 139 log.Fatal(err) 140 } 141 if f.FileInfo().Mode()&os.ModeSymlink != 0 { 142 must(writeSymlink(filename, in)) 143 } else { 144 must(writeFile(filename, in, f.FileInfo().Mode())) 145 } 146 in.Close() 147 files = append(files, filename) 148 } 149 } 150 } 151 152 if *outputFile != "" { 153 data := strings.Join(files, "\n") 154 if len(files) > 0 { 155 data += "\n" 156 } 157 must(ioutil.WriteFile(*outputFile, []byte(data), 0666)) 158 } 159} 160 161type multiFlag []string 162 163func (m *multiFlag) String() string { 164 return strings.Join(*m, " ") 165} 166 167func (m *multiFlag) Set(s string) error { 168 *m = append(*m, s) 169 return nil 170} 171 172func (m *multiFlag) Match(s string) (bool, error) { 173 if m == nil { 174 return false, nil 175 } 176 for _, f := range *m { 177 if match, err := filepath.Match(f, s); err != nil { 178 return false, err 179 } else if match { 180 return true, nil 181 } 182 } 183 return false, nil 184} 185