OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 The LUCI Authors. All rights reserved. | |
2 // Use of this source code is governed under the Apache License, Version 2.0 | |
3 // that can be found in the LICENSE file. | |
4 | |
5 // Read an ar file with BSD formatted file names. | |
6 | |
7 package ar | |
8 | |
9 import ( | |
10 "bytes" | |
11 "fmt" | |
12 "io" | |
13 "io/ioutil" | |
14 "os" | |
15 "time" | |
16 ) | |
17 | |
18 // Special UsageError that indicates trying to read after closing. | |
19 var ( | |
20 ErrReadAfterClose = UsageError{msg: "read after file closed"} | |
21 ) | |
22 | |
23 // ReadDataIOError indicates an error while reading data from the archive. | |
24 type ReadDataIOError struct { | |
25 IOError | |
26 wanted []byte | |
27 got []byte | |
28 } | |
29 | |
30 func (e *ReadDataIOError) Error() string { | |
31 return fmt.Sprintf("%s (wanted '%s', got '%s') -- *archive corrupted*", e.IOError.Error(), e.wanted, e.got) | |
mcgreevy
2016/11/07 00:37:22
FYI, got idiomatically precedes want in Go (you wi
| |
32 } | |
33 | |
34 // Fatal is always true for ReadDataIOError. | |
35 func (e *ReadDataIOError) Fatal() bool { | |
36 return true | |
37 } | |
38 | |
39 // FileInfo contains information about a file inside the ar archive. | |
40 type FileInfo interface { | |
41 os.FileInfo | |
42 UserID() int | |
43 GroupID() int | |
44 } | |
45 | |
46 type arFileInfoData struct { | |
47 // os.FileInfo parts | |
48 name string | |
49 size int64 | |
50 mode uint32 | |
51 modtime uint64 | |
52 // Extra parts | |
53 uid int | |
54 gid int | |
55 } | |
56 | |
57 // os.FileInfo interface | |
58 func (fi *arFileInfoData) Name() string { return fi.name } | |
59 func (fi *arFileInfoData) Size() int64 { return fi.size } | |
60 func (fi *arFileInfoData) Mode() os.FileMode { return os.FileMode(fi.mode) } | |
61 func (fi *arFileInfoData) ModTime() time.Time { return time.Unix(int64(fi.modtim e), 0) } | |
62 func (fi *arFileInfoData) IsDir() bool { return fi.Mode().IsDir() } | |
63 func (fi *arFileInfoData) Sys() interface{} { return fi } | |
64 | |
65 // Extra | |
66 func (fi *arFileInfoData) UserID() int { return fi.uid } | |
67 func (fi *arFileInfoData) GroupID() int { return fi.gid } | |
68 | |
69 type readerStage uint | |
70 | |
71 // Reader allows reading an existing ar archives. | |
72 type Reader struct { | |
73 r io.Reader | |
74 bodyReader io.LimitedReader | |
75 bodyHasPadding bool | |
76 } | |
77 | |
78 // NewReader allows reading an existing ar archives. | |
79 func NewReader(r io.Reader) (*Reader, Error) { | |
80 reader := Reader{r: r} | |
81 if err := reader.checkBytes("header", []byte("!<arch>\n")); err != nil { | |
82 return nil, err | |
83 } | |
84 return &reader, nil | |
85 } | |
86 | |
87 func (ar *Reader) checkBytes(section string, str []byte) Error { | |
88 buffer := make([]byte, len(str)) | |
89 | |
90 if _, err := io.ReadFull(ar.r, buffer); err != nil { | |
91 return &IOError{section: section, err: err} | |
92 } | |
93 | |
94 if !bytes.Equal(str, buffer) { | |
95 return &ReadDataIOError{IOError: IOError{section: section}, want ed: str, got: buffer} | |
96 } | |
97 | |
98 return nil | |
99 } | |
100 | |
101 // Close the archive. | |
102 func (ar *Reader) Close() Error { | |
103 if ar.r == nil { | |
104 return &ErrReadAfterClose | |
105 } | |
106 ar.r = nil | |
107 ar.bodyReader.R = nil | |
108 return nil | |
109 } | |
110 | |
111 func (ar *Reader) readHeaderInt64(section string, bytes int, formatstr string) ( int64, Error) { | |
112 data := make([]byte, bytes) | |
113 if _, err := io.ReadFull(ar.r, data); err != nil { | |
114 return -1, &IOError{section: section, err: err} | |
115 } | |
116 | |
117 var output int64 | |
118 if _, err := fmt.Sscanf(string(data), formatstr, &output); err != nil { | |
119 return -1, &ReadDataIOError{IOError: IOError{section: section, e rr: err}, wanted: []byte(formatstr), got: data} | |
120 } | |
121 | |
122 if output <= 0 { | |
123 return -1, &ReadDataIOError{IOError: IOError{section: section}, wanted: []byte(formatstr), got: data} | |
124 } | |
125 return output, nil | |
126 } | |
127 | |
128 // Next file in the archive. | |
129 func (ar *Reader) Next() (FileInfo, Error) { | |
130 if ar.r == nil { | |
131 return nil, &ErrReadAfterClose | |
132 } | |
133 | |
134 if ar.bodyReader.N > 0 { | |
135 // Skip over any remaining part of previous file. | |
136 if s, ok := ar.r.(io.Seeker); ok { | |
137 if _, err := s.Seek(ar.bodyReader.N, io.SeekCurrent); er r != nil { | |
138 return nil, &ReadDataIOError{IOError: IOError{se ction: "body", err: err}, wanted: nil, got: nil} | |
139 } | |
140 } else { | |
141 if _, err := io.Copy(ioutil.Discard, &ar.bodyReader); er r != nil { | |
142 return nil, &ReadDataIOError{IOError: IOError{se ction: "body", err: err}, wanted: nil, got: nil} | |
143 } | |
144 } | |
145 | |
146 // Padding to 16bit boundary | |
147 if ar.bodyHasPadding { | |
148 if err := ar.checkBytes("body padding", []byte{'\n'}); e rr != nil { | |
149 return nil, err | |
150 } | |
151 ar.bodyHasPadding = false | |
152 } | |
153 } | |
154 | |
155 var fi arFileInfoData | |
156 | |
157 // File name length prefixed with '#1/' (BSD variant), 16 bytes | |
158 namelen, err := ar.readHeaderInt64("file header file path length", 16, " #1/%13d") | |
159 if err != nil { | |
160 return nil, err | |
161 } | |
162 | |
163 // Modtime, 12 bytes | |
164 modtime, err := ar.readHeaderInt64("file modtime", 12, "%12d") | |
165 if err != nil { | |
166 return nil, err | |
167 } | |
168 fi.modtime = uint64(modtime) | |
169 | |
170 // Owner ID, 6 bytes | |
171 ownerid, err := ar.readHeaderInt64("file header owner id", 6, "%6d") | |
172 if err != nil { | |
173 return nil, err | |
174 } | |
175 fi.uid = int(ownerid) | |
176 | |
177 // Group ID, 6 bytes | |
178 groupid, err := ar.readHeaderInt64("file header group id", 6, "%6d") | |
179 if err != nil { | |
180 return nil, err | |
181 } | |
182 fi.gid = int(groupid) | |
183 | |
184 // File mode, 8 bytes | |
185 filemod, err := ar.readHeaderInt64("file header file mode", 8, "%8o") | |
186 if err != nil { | |
187 return nil, err | |
188 } | |
189 fi.mode = uint32(filemod) | |
190 | |
191 // File size, 10 bytes | |
192 size, err := ar.readHeaderInt64("file header data size", 10, "%10d") | |
193 if err != nil { | |
194 return nil, err | |
195 } | |
196 fi.size = size - namelen | |
197 | |
198 // File magic, 2 bytes | |
199 if err = ar.checkBytes("file header file magic", []byte{'\x60', '\n'}); err != nil { | |
200 return nil, err | |
201 } | |
202 | |
203 ar.bodyReader = io.LimitedReader{R: ar.r, N: size} | |
204 ar.bodyHasPadding = (size%2 != 0) | |
205 | |
206 // Filename - BSD variant | |
207 filename := make([]byte, namelen) | |
208 if _, err := io.ReadFull(&ar.bodyReader, filename); err != nil { | |
209 return nil, &IOError{section: "body filename", err: err} | |
210 } | |
211 fi.name = string(filename) | |
212 | |
213 return &fi, nil | |
214 } | |
215 | |
216 // Body for the current file in the archive. | |
217 func (ar *Reader) Body() io.Reader { | |
mcgreevy
2016/11/07 00:37:22
Is there a reason why the body can be returned fro
| |
218 return &ar.bodyReader | |
219 } | |
OLD | NEW |