Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(194)

Side by Side Diff: go/src/infra/tools/cipd/reader.go

Issue 1129043003: cipd: Refactor client to make it more readable. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « go/src/infra/tools/cipd/pkgdef_test.go ('k') | go/src/infra/tools/cipd/reader_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « go/src/infra/tools/cipd/pkgdef_test.go ('k') | go/src/infra/tools/cipd/reader_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698