1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Mkzip creates a zip file from a 'proto' file describing the contents. 6 // 7 // The proto file is inspired by the Plan 9 mkfs prototype file format. 8 // It describes a file tree, one directory per line, with leading tab 9 // indentation marking the tree structure. Each line contains a leading 10 // name field giving the name of the file to copy into the zip file, 11 // and then a sequence of optional key=value attributes to control 12 // the copy. The only known attribute is src=foo, meaning copy the 13 // actual data for the file (or directory) from an alternate location. 14 package main 15 16 import ( 17 "archive/zip" 18 "bufio" 19 "flag" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "log" 24 "os" 25 "path" 26 "path/filepath" 27 "strings" 28 ) 29 30 func usage() { 31 fmt.Fprintf(os.Stderr, "usage: mkzip [-r root] src.proto out.zip\n") 32 os.Exit(2) 33 } 34 35 func sysfatal(format string, args ...interface{}) { 36 fmt.Fprintf(os.Stderr, "mkzip: %s\n", fmt.Sprintf(format, args...)) 37 os.Exit(2) 38 } 39 40 var ( 41 root = flag.String("r", ".", "interpret source paths relative to this directory") 42 gopackage = flag.String("p", "", "write Go source file in this package") 43 ) 44 45 type stack struct { 46 name string 47 src string 48 depth int 49 } 50 51 func main() { 52 log.SetFlags(0) 53 flag.Usage = usage 54 flag.Parse() 55 56 args := flag.Args() 57 if len(args) != 2 { 58 usage() 59 } 60 61 rf, err := os.Open(args[0]) 62 if err != nil { 63 sysfatal("%v", err) 64 } 65 r := bufio.NewScanner(rf) 66 67 zf, err := os.Create(args[1]) 68 if err != nil { 69 sysfatal("%v", err) 70 } 71 72 var w io.Writer = zf 73 if *gopackage != "" { 74 fmt.Fprintf(zf, `package %s 75 import "sync" 76 func init() { 77 var once sync.Once 78 fsinit = func() { 79 once.Do(func() { 80 unzip("`, *gopackage) 81 gw := &goWriter{b: bufio.NewWriter(w)} 82 defer func() { 83 if err := gw.Close(); err != nil { 84 sysfatal("finishing Go output: %v", err) 85 } 86 }() 87 w = gw 88 } 89 z := zip.NewWriter(w) 90 91 lineno := 0 92 93 addfile := func(info os.FileInfo, dst string, src string) { 94 zh, err := zip.FileInfoHeader(info) 95 if err != nil { 96 sysfatal("%s:%d: %s: %v", args[0], lineno, src, err) 97 } 98 zh.Name = dst 99 zh.Method = zip.Deflate 100 if info.IsDir() && !strings.HasSuffix(dst, "/") { 101 zh.Name += "/" 102 } 103 w, err := z.CreateHeader(zh) 104 if err != nil { 105 sysfatal("%s:%d: %s: %v", args[0], lineno, src, err) 106 } 107 if info.IsDir() { 108 return 109 } 110 r, err := os.Open(src) 111 if err != nil { 112 sysfatal("%s:%d: %s: %v", args[0], lineno, src, err) 113 } 114 defer r.Close() 115 if _, err := io.Copy(w, r); err != nil { 116 sysfatal("%s:%d: %s: %v", args[0], lineno, src, err) 117 } 118 } 119 120 var stk []stack 121 122 for r.Scan() { 123 line := r.Text() 124 lineno++ 125 s := strings.TrimLeft(line, "\t") 126 prefix, line := line[:len(line)-len(s)], s 127 if i := strings.Index(line, "#"); i >= 0 { 128 line = line[:i] 129 } 130 f := strings.Fields(line) 131 if len(f) == 0 { 132 continue 133 } 134 if strings.HasPrefix(line, " ") { 135 sysfatal("%s:%d: must use tabs for indentation", args[0], lineno) 136 } 137 depth := len(prefix) 138 for len(stk) > 0 && depth <= stk[len(stk)-1].depth { 139 stk = stk[:len(stk)-1] 140 } 141 parent := "" 142 psrc := *root 143 if len(stk) > 0 { 144 parent = stk[len(stk)-1].name 145 psrc = stk[len(stk)-1].src 146 } 147 if strings.Contains(f[0], "/") { 148 sysfatal("%s:%d: destination name cannot contain slash", args[0], lineno) 149 } 150 name := path.Join(parent, f[0]) 151 src := filepath.Join(psrc, f[0]) 152 for _, attr := range f[1:] { 153 i := strings.Index(attr, "=") 154 if i < 0 { 155 sysfatal("%s:%d: malformed attribute %q", args[0], lineno, attr) 156 } 157 key, val := attr[:i], attr[i+1:] 158 switch key { 159 case "src": 160 src = val 161 default: 162 sysfatal("%s:%d: unknown attribute %q", args[0], lineno, attr) 163 } 164 } 165 166 stk = append(stk, stack{name: name, src: src, depth: depth}) 167 168 if f[0] == "*" || f[0] == "+" { 169 if f[0] == "*" { 170 dir, err := ioutil.ReadDir(psrc) 171 if err != nil { 172 sysfatal("%s:%d: %v", args[0], lineno, err) 173 } 174 for _, d := range dir { 175 addfile(d, path.Join(parent, d.Name()), filepath.Join(psrc, d.Name())) 176 } 177 } else { 178 err := filepath.Walk(psrc, func(src string, info os.FileInfo, err error) error { 179 if err != nil { 180 return err 181 } 182 if src == psrc { 183 return nil 184 } 185 if psrc == "." { 186 psrc = "" 187 } 188 name := path.Join(parent, filepath.ToSlash(src[len(psrc):])) 189 addfile(info, name, src) 190 return nil 191 }) 192 if err != nil { 193 sysfatal("%s:%d: %v", args[0], lineno, err) 194 } 195 } 196 continue 197 } 198 199 fi, err := os.Stat(src) 200 if err != nil { 201 sysfatal("%s:%d: %v", args[0], lineno, err) 202 } 203 addfile(fi, name, src) 204 } 205 206 if err := z.Close(); err != nil { 207 sysfatal("finishing zip file: %v", err) 208 } 209 } 210 211 type goWriter struct { 212 b *bufio.Writer 213 } 214 215 func (w *goWriter) Write(b []byte) (int, error) { 216 for _, c := range b { 217 fmt.Fprintf(w.b, "\\x%02x", c) 218 } 219 return len(b), nil 220 } 221 222 func (w *goWriter) Close() error { 223 fmt.Fprintf(w.b, "\")\n\t\t})\n\t}\n}") 224 w.b.Flush() 225 return nil 226 } 227