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 writeSymlink(filename string, in io.Reader) error { 57 b, err := ioutil.ReadAll(in) 58 if err != nil { 59 return err 60 } 61 dest := string(b) 62 err = os.Symlink(dest, filename) 63 return err 64} 65 66func main() { 67 flag.Usage = func() { 68 fmt.Fprintln(os.Stderr, "usage: zipsync -d <output dir> [-l <output file>] [-f <pattern>] [zip]...") 69 flag.PrintDefaults() 70 } 71 72 flag.Parse() 73 74 if *outputDir == "" { 75 flag.Usage() 76 os.Exit(1) 77 } 78 79 inputs := flag.Args() 80 81 // For now, just wipe the output directory and replace its contents with the zip files 82 // Eventually this could only modify the directory contents as necessary to bring it up 83 // to date with the zip files. 84 must(os.RemoveAll(*outputDir)) 85 86 must(os.MkdirAll(*outputDir, 0777)) 87 88 var files []string 89 seen := make(map[string]string) 90 91 if *zipPrefix != "" { 92 *zipPrefix = filepath.Clean(*zipPrefix) + "/" 93 } 94 95 for _, input := range inputs { 96 reader, err := zip.OpenReader(input) 97 if err != nil { 98 log.Fatal(err) 99 } 100 defer reader.Close() 101 102 for _, f := range reader.File { 103 name := f.Name 104 if *zipPrefix != "" { 105 if !strings.HasPrefix(name, *zipPrefix) { 106 continue 107 } 108 name = strings.TrimPrefix(name, *zipPrefix) 109 } 110 if *filter != "" { 111 if match, err := filepath.Match(*filter, filepath.Base(name)); err != nil { 112 log.Fatal(err) 113 } else if !match { 114 continue 115 } 116 } 117 if filepath.IsAbs(name) { 118 log.Fatalf("%q in %q is an absolute path", name, input) 119 } 120 121 if prev, exists := seen[name]; exists { 122 log.Fatalf("%q found in both %q and %q", name, prev, input) 123 } 124 seen[name] = input 125 126 filename := filepath.Join(*outputDir, name) 127 if f.FileInfo().IsDir() { 128 must(os.MkdirAll(filename, 0777)) 129 } else { 130 must(os.MkdirAll(filepath.Dir(filename), 0777)) 131 in, err := f.Open() 132 if err != nil { 133 log.Fatal(err) 134 } 135 if f.FileInfo().Mode()&os.ModeSymlink != 0 { 136 must(writeSymlink(filename, in)) 137 } else { 138 must(writeFile(filename, in, f.FileInfo().Mode())) 139 } 140 in.Close() 141 files = append(files, filename) 142 } 143 } 144 } 145 146 if *outputFile != "" { 147 data := strings.Join(files, "\n") 148 if len(files) > 0 { 149 data += "\n" 150 } 151 must(ioutil.WriteFile(*outputFile, []byte(data), 0666)) 152 } 153} 154