OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package cipd | |
6 | |
7 import ( | |
8 "archive/zip" | |
9 "bytes" | |
10 "fmt" | |
11 "io" | |
12 "io/ioutil" | |
13 "os" | |
14 "strings" | |
15 "time" | |
16 ) | |
17 | |
18 // BuildInstanceOptions defines options for BuildInstance function. | |
19 type BuildInstanceOptions struct { | |
20 // List of files to add to the package. | |
21 Input []File | |
22 // Where to write the package file to. | |
23 Output io.Writer | |
24 // Package name, e.g. 'infra/tools/cipd'. | |
25 PackageName string | |
26 } | |
27 | |
28 // BuildInstance builds a new package instance for package named opts.PackageNam
e | |
29 // by archiving input files (passed via opts.Input). The final binary is written | |
30 // to opts.Output. Some output may be written even if BuildInstance eventually | |
31 // returns an error. | |
32 func BuildInstance(opts BuildInstanceOptions) error { | |
33 err := ValidatePackageName(opts.PackageName) | |
34 if err != nil { | |
35 return err | |
36 } | |
37 | |
38 // Make sure no files are written to package service directory. | |
39 for _, f := range opts.Input { | |
40 if strings.HasPrefix(f.Name(), packageServiceDir+"/") { | |
41 return fmt.Errorf("Can't write to %s: %s", packageServic
eDir, f.Name()) | |
42 } | |
43 } | |
44 | |
45 // Generate the manifest file, add to the list of input files. | |
46 manifestFile, err := makeManifestFile(opts) | |
47 if err != nil { | |
48 return err | |
49 } | |
50 files := append(opts.Input, manifestFile) | |
51 | |
52 // Make sure filenames are unique. | |
53 seenNames := make(map[string]struct{}, len(files)) | |
54 for _, f := range files { | |
55 _, seen := seenNames[f.Name()] | |
56 if seen { | |
57 return fmt.Errorf("File %s is provided twice", f.Name()) | |
58 } | |
59 seenNames[f.Name()] = struct{}{} | |
60 } | |
61 | |
62 // Write the final zip file. | |
63 return zipInputFiles(files, opts.Output) | |
64 } | |
65 | |
66 // zipInputFiles deterministically builds a zip archive out of input files and | |
67 // writes it to the writer. Files are written in the order given. | |
68 func zipInputFiles(files []File, w io.Writer) error { | |
69 writer := zip.NewWriter(w) | |
70 defer writer.Close() | |
71 | |
72 // Reports zipping progress to the log each second. | |
73 lastReport := time.Time{} | |
74 progress := func(count int) { | |
75 if time.Since(lastReport) > time.Second { | |
76 lastReport = time.Now() | |
77 log.Infof("Zipping files: %d files left", len(files)-cou
nt) | |
78 } | |
79 } | |
80 | |
81 for i, in := range files { | |
82 progress(i) | |
83 | |
84 // Intentionally do not add timestamp or file mode to make zip a
rchive | |
85 // deterministic. See also zip.FileInfoHeader() implementation. | |
86 fh := zip.FileHeader{ | |
87 Name: in.Name(), | |
88 Method: zip.Deflate, | |
89 } | |
90 | |
91 mode := os.FileMode(0600) | |
92 if in.Executable() { | |
93 mode |= 0100 | |
94 } | |
95 if in.Symlink() { | |
96 mode |= os.ModeSymlink | |
97 } | |
98 fh.SetMode(mode) | |
99 | |
100 dst, err := writer.CreateHeader(&fh) | |
101 if err != nil { | |
102 return err | |
103 } | |
104 if in.Symlink() { | |
105 err = zipSymlinkFile(dst, in) | |
106 } else { | |
107 err = zipRegularFile(dst, in) | |
108 } | |
109 if err != nil { | |
110 return err | |
111 } | |
112 } | |
113 | |
114 return nil | |
115 } | |
116 | |
117 func zipRegularFile(dst io.Writer, f File) error { | |
118 src, err := f.Open() | |
119 if err != nil { | |
120 return err | |
121 } | |
122 defer src.Close() | |
123 written, err := io.Copy(dst, src) | |
124 if err != nil { | |
125 return err | |
126 } | |
127 if uint64(written) != f.Size() { | |
128 return fmt.Errorf("File %s changed midway", f.Name()) | |
129 } | |
130 return nil | |
131 } | |
132 | |
133 func zipSymlinkFile(dst io.Writer, f File) error { | |
134 target, err := f.SymlinkTarget() | |
135 if err != nil { | |
136 return err | |
137 } | |
138 // Symlinks are zipped as text files with target path. os.ModeSymlink bi
t in | |
139 // the header distinguishes them from regular files. | |
140 _, err = dst.Write([]byte(target)) | |
141 return err | |
142 } | |
143 | |
144 //////////////////////////////////////////////////////////////////////////////// | |
145 | |
146 type manifestFile []byte | |
147 | |
148 func (m *manifestFile) Name() string { return manifestName } | |
149 func (m *manifestFile) Size() uint64 { return uint64(len(*m)) } | |
150 func (m *manifestFile) Executable() bool { return false } | |
151 func (m *manifestFile) Symlink() bool { return false } | |
152 | |
153 func (m *manifestFile) SymlinkTarget() (string, error) { | |
154 return "", fmt.Errorf("Not a symlink: %s", m.Name()) | |
155 } | |
156 | |
157 func (m *manifestFile) Open() (io.ReadCloser, error) { | |
158 return ioutil.NopCloser(bytes.NewReader(*m)), nil | |
159 } | |
160 | |
161 // makeManifestFile generates a package manifest file and returns it as | |
162 // File interface. | |
163 func makeManifestFile(opts BuildInstanceOptions) (File, error) { | |
164 buf := &bytes.Buffer{} | |
165 err := writeManifest(&Manifest{ | |
166 FormatVersion: manifestFormatVersion, | |
167 PackageName: opts.PackageName, | |
168 }, buf) | |
169 if err != nil { | |
170 return nil, err | |
171 } | |
172 out := manifestFile(buf.Bytes()) | |
173 return &out, nil | |
174 } | |
OLD | NEW |