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 "crypto/sha1" | |
10 "encoding/hex" | |
11 "fmt" | |
12 "io" | |
13 "io/ioutil" | |
14 "os" | |
15 ) | |
16 | |
17 // PackageInstance represents a binary package file. | |
18 type PackageInstance interface { | |
19 // Close shuts down the package and its data provider. | |
20 Close() error | |
21 // PackageName returns package name, as defined in the manifest. | |
22 PackageName() string | |
23 // InstanceID returns id that identifies particular built of the package
. It's a hash of the package data. | |
24 InstanceID() string | |
25 // Files returns a list of files inside the package. | |
26 Files() []File | |
27 // DataReader returns reader that reads raw package data. | |
28 DataReader() io.ReadSeeker | |
29 } | |
30 | |
31 // OpenInstance verifies package SHA1 hash (instanceID if not empty string) and | |
32 // prepares a package instance for extraction. If the call succeeds, | |
33 // PackageInstance takes ownership of io.ReadSeeker. If it also implements | |
34 // io.Closer, it will be closed when package.Close() is called. If an error is | |
35 // returned, io.ReadSeeker remains unowned and caller is responsible for closing | |
36 // it (if required). | |
37 func OpenInstance(r io.ReadSeeker, instanceID string) (PackageInstance, error) { | |
38 out := &packageInstance{data: r} | |
39 err := out.open(instanceID) | |
40 if err != nil { | |
41 return nil, err | |
42 } | |
43 return out, nil | |
44 } | |
45 | |
46 // OpenInstanceFile opens a package instance file on disk. | |
47 func OpenInstanceFile(path string, instanceID string) (inst PackageInstance, err
error) { | |
48 file, err := os.Open(path) | |
49 if err != nil { | |
50 return | |
51 } | |
52 inst, err = OpenInstance(file, instanceID) | |
53 if err != nil { | |
54 file.Close() | |
55 } | |
56 return | |
57 } | |
58 | |
59 // ExtractInstance extracts all files from a package instance into a destination
. | |
60 func ExtractInstance(inst PackageInstance, dest Destination) error { | |
61 err := dest.Begin() | |
62 if err != nil { | |
63 return err | |
64 } | |
65 | |
66 // Do not leave garbage around in case of a panic. | |
67 needToEnd := true | |
68 defer func() { | |
69 if needToEnd { | |
70 dest.End(false) | |
71 } | |
72 }() | |
73 | |
74 // Use a nested function in a loop for defers. | |
75 extractRegularFile := func(f File) error { | |
76 out, err := dest.CreateFile(f.Name(), f.Executable()) | |
77 if err != nil { | |
78 return err | |
79 } | |
80 defer out.Close() | |
81 in, err := f.Open() | |
82 if err != nil { | |
83 return err | |
84 } | |
85 defer in.Close() | |
86 _, err = io.Copy(out, in) | |
87 return err | |
88 } | |
89 | |
90 extractSymlinkFile := func(f File) error { | |
91 target, err := f.SymlinkTarget() | |
92 if err != nil { | |
93 return err | |
94 } | |
95 return dest.CreateSymlink(f.Name(), target) | |
96 } | |
97 | |
98 files := inst.Files() | |
99 for i, f := range files { | |
100 log.Infof("[%d/%d] inflating %s", i+1, len(files), f.Name()) | |
101 if f.Symlink() { | |
102 err = extractSymlinkFile(f) | |
103 } else { | |
104 err = extractRegularFile(f) | |
105 } | |
106 if err != nil { | |
107 break | |
108 } | |
109 } | |
110 | |
111 needToEnd = false | |
112 if err == nil { | |
113 err = dest.End(true) | |
114 } else { | |
115 // Ignore error in 'End' and return the original error. | |
116 dest.End(false) | |
117 } | |
118 | |
119 return err | |
120 } | |
121 | |
122 //////////////////////////////////////////////////////////////////////////////// | |
123 // PackageInstance implementation. | |
124 | |
125 type packageInstance struct { | |
126 data io.ReadSeeker | |
127 dataSize int64 | |
128 instanceID string | |
129 zip *zip.Reader | |
130 files []File | |
131 manifest Manifest | |
132 } | |
133 | |
134 // open reads the package data , verifies SHA1 hash and reads manifest. | |
135 func (inst *packageInstance) open(instanceID string) error { | |
136 // Calculate SHA1 of the data to verify it matches expected instanceID. | |
137 _, err := inst.data.Seek(0, os.SEEK_SET) | |
138 if err != nil { | |
139 return err | |
140 } | |
141 hash := sha1.New() | |
142 _, err = io.Copy(hash, inst.data) | |
143 if err != nil { | |
144 return err | |
145 } | |
146 inst.dataSize, err = inst.data.Seek(0, os.SEEK_CUR) | |
147 if err != nil { | |
148 return err | |
149 } | |
150 calculatedSHA1 := hex.EncodeToString(hash.Sum(nil)) | |
151 if instanceID != "" && instanceID != calculatedSHA1 { | |
152 return fmt.Errorf("Package SHA1 hash mismatch") | |
153 } | |
154 inst.instanceID = calculatedSHA1 | |
155 | |
156 // List files inside and package manifest. | |
157 inst.zip, err = zip.NewReader(&readerAt{r: inst.data}, inst.dataSize) | |
158 if err != nil { | |
159 return err | |
160 } | |
161 inst.files = make([]File, len(inst.zip.File)) | |
162 for i, zf := range inst.zip.File { | |
163 inst.files[i] = &fileInZip{z: zf} | |
164 if inst.files[i].Name() == manifestName { | |
165 inst.manifest, err = readManifestFile(inst.files[i]) | |
166 if err != nil { | |
167 return err | |
168 } | |
169 } | |
170 } | |
171 return nil | |
172 } | |
173 | |
174 func (inst *packageInstance) Close() error { | |
175 if inst.data != nil { | |
176 if closer, ok := inst.data.(io.Closer); ok { | |
177 closer.Close() | |
178 } | |
179 inst.data = nil | |
180 } | |
181 inst.dataSize = 0 | |
182 inst.instanceID = "" | |
183 inst.zip = nil | |
184 inst.files = []File{} | |
185 inst.manifest = Manifest{} | |
186 return nil | |
187 } | |
188 | |
189 func (inst *packageInstance) InstanceID() string { return inst.instanceID
} | |
190 func (inst *packageInstance) PackageName() string { return inst.manifest.P
ackageName } | |
191 func (inst *packageInstance) Files() []File { return inst.files } | |
192 func (inst *packageInstance) DataReader() io.ReadSeeker { return inst.data } | |
193 | |
194 //////////////////////////////////////////////////////////////////////////////// | |
195 // Utilities. | |
196 | |
197 // readManifestFile decodes manifest file zipped inside the package. | |
198 func readManifestFile(f File) (Manifest, error) { | |
199 r, err := f.Open() | |
200 if err != nil { | |
201 return Manifest{}, err | |
202 } | |
203 defer r.Close() | |
204 return readManifest(r) | |
205 } | |
206 | |
207 //////////////////////////////////////////////////////////////////////////////// | |
208 // File interface implementation via zip.File. | |
209 | |
210 type fileInZip struct { | |
211 z *zip.File | |
212 } | |
213 | |
214 func (f *fileInZip) Name() string { return f.z.Name } | |
215 func (f *fileInZip) Symlink() bool { return (f.z.Mode() & os.ModeSymlink) != 0 } | |
216 | |
217 func (f *fileInZip) Executable() bool { | |
218 if f.Symlink() { | |
219 return false | |
220 } | |
221 return (f.z.Mode() & 0100) != 0 | |
222 } | |
223 | |
224 func (f *fileInZip) Size() uint64 { | |
225 if f.Symlink() { | |
226 return 0 | |
227 } | |
228 return f.z.UncompressedSize64 | |
229 } | |
230 | |
231 func (f *fileInZip) SymlinkTarget() (string, error) { | |
232 if !f.Symlink() { | |
233 return "", fmt.Errorf("Not a symlink: %s", f.Name()) | |
234 } | |
235 r, err := f.z.Open() | |
236 if err != nil { | |
237 return "", err | |
238 } | |
239 defer r.Close() | |
240 data, err := ioutil.ReadAll(r) | |
241 if err != nil { | |
242 return "", err | |
243 } | |
244 return string(data), nil | |
245 } | |
246 | |
247 func (f *fileInZip) Open() (io.ReadCloser, error) { | |
248 if f.Symlink() { | |
249 return nil, fmt.Errorf("Opening a symlink is not allowed: %s", f
.Name()) | |
250 } | |
251 return f.z.Open() | |
252 } | |
253 | |
254 //////////////////////////////////////////////////////////////////////////////// | |
255 // ReaderAt implementation via ReadSeeker. Not concurrency safe, moves file | |
256 // pointer around without any locking. Works OK in the context of OpenInstance | |
257 // function though (where OpenInstance takes sole ownership of io.ReadSeeker). | |
258 | |
259 type readerAt struct { | |
260 r io.ReadSeeker | |
261 } | |
262 | |
263 func (r *readerAt) ReadAt(data []byte, off int64) (int, error) { | |
264 _, err := r.r.Seek(off, os.SEEK_SET) | |
265 if err != nil { | |
266 return 0, err | |
267 } | |
268 return r.r.Read(data) | |
269 } | |
OLD | NEW |