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"} | |
M-A Ruel
2016/06/14 14:30:43
same comments as in writer.go
| |
21 ) | |
22 | |
23 type ReadDataIOError struct { | |
M-A Ruel
2016/06/14 14:30:43
same comments as in writer.go
| |
24 IOError | |
25 wanted []byte | |
26 got []byte | |
27 } | |
28 | |
29 func (e *ReadDataIOError) Error() string { | |
30 return fmt.Sprintf("%s (wanted '%s', got '%s') -- *archive corrupted*", e.IOError.Error(), e.wanted, e.got) | |
31 } | |
32 | |
33 type ArFileInfo interface { | |
34 os.FileInfo | |
35 UserId() int | |
36 GroupId() int | |
37 } | |
38 | |
39 type arFileInfoData struct { | |
40 // os.FileInfo parts | |
41 name string | |
42 size int64 | |
43 mode uint32 | |
44 modtime uint64 | |
45 // Extra parts | |
46 uid int | |
47 gid int | |
48 } | |
49 | |
50 // os.FileInfo interface | |
51 func (fi *arFileInfoData) Name() string { return fi.name } | |
52 func (fi *arFileInfoData) Size() int64 { return fi.size } | |
53 func (fi *arFileInfoData) Mode() os.FileMode { return os.FileMode(fi.mode) } | |
54 func (fi *arFileInfoData) ModTime() time.Time { return time.Unix(int64(fi.modtim e), 0) } | |
55 func (fi *arFileInfoData) IsDir() bool { return fi.Mode().IsDir() } | |
56 func (fi *arFileInfoData) Sys() interface{} { return fi } | |
57 | |
58 // Extra | |
59 func (fi *arFileInfoData) UserId() int { return fi.uid } | |
60 func (fi *arFileInfoData) GroupId() int { return fi.gid } | |
61 | |
62 type readerStage uint | |
63 | |
64 type Reader struct { | |
65 r io.Reader | |
66 bodyReader io.LimitedReader | |
67 bodyHasPadding bool | |
68 } | |
69 | |
70 func NewReader(r io.Reader) (*Reader, Error) { | |
71 reader := Reader{r: r} | |
72 if err := reader.checkBytes("header", []byte("!<arch>\n")); err != nil { | |
73 return nil, err | |
74 } | |
75 return &reader, nil | |
76 } | |
77 | |
78 func (ar *Reader) checkBytes(section string, str []byte) Error { | |
79 buffer := make([]byte, len(str)) | |
80 | |
81 if _, err := io.ReadFull(ar.r, buffer); err != nil { | |
82 return &IOError{section: section, err: err} | |
83 } | |
84 | |
85 if !bytes.Equal(str, buffer) { | |
86 return &ReadDataIOError{IOError: IOError{section: section}, want ed: str, got: buffer} | |
87 } | |
88 | |
89 return nil | |
90 } | |
91 | |
92 func (ar *Reader) Close() Error { | |
93 if ar.r == nil { | |
94 return &ErrReadAfterClose | |
95 } | |
96 ar.r = nil | |
97 ar.bodyReader.R = nil | |
98 return nil | |
99 } | |
100 | |
101 func (ar *Reader) readHeaderInt64(section string, bytes int, formatstr string) ( int64, Error) { | |
102 data := make([]byte, bytes) | |
103 if _, err := io.ReadFull(ar.r, data); err != nil { | |
104 return -1, &IOError{section: section, err: err} | |
105 } | |
106 | |
107 var output int64 | |
108 if _, err := fmt.Sscanf(string(data), formatstr, &output); err != nil { | |
109 return -1, &ReadDataIOError{IOError: IOError{section: section, e rr: err}, wanted: []byte(formatstr), got: data} | |
110 } | |
111 | |
112 if output <= 0 { | |
113 return -1, &ReadDataIOError{IOError: IOError{section: section}, wanted: []byte(formatstr), got: data} | |
114 } | |
115 return output, nil | |
116 } | |
117 | |
118 func (ar *Reader) Next() (ArFileInfo, Error) { | |
119 if ar.r == nil { | |
120 return nil, &ErrReadAfterClose | |
121 } | |
122 | |
123 if ar.bodyReader.N > 0 { | |
124 // Read any remains of the previous file | |
M-A Ruel
2016/06/14 14:30:43
You can do dynamic cast; https://golang.org/pkg/io
mithro
2016/11/06 22:56:53
Done.
| |
125 io.Copy(ioutil.Discard, &ar.bodyReader) | |
126 | |
127 // Padding to 16bit boundary | |
128 if ar.bodyHasPadding { | |
129 if err := ar.checkBytes("body padding", []byte{'\n'}); e rr != nil { | |
130 return nil, err | |
131 } | |
132 ar.bodyHasPadding = false | |
133 } | |
134 } | |
135 | |
136 var fi arFileInfoData | |
137 | |
138 // File name length prefixed with '#1/' (BSD variant), 16 bytes | |
139 namelen, err := ar.readHeaderInt64("file header file path length", 16, " #1/%13d") | |
140 if err != nil { | |
141 return nil, err | |
142 } | |
143 | |
144 // Modtime, 12 bytes | |
145 modtime, err := ar.readHeaderInt64("file modtime", 12, "%12d") | |
146 if err != nil { | |
147 return nil, err | |
148 } | |
149 fi.modtime = uint64(modtime) | |
150 | |
151 // Owner ID, 6 bytes | |
152 ownerid, err := ar.readHeaderInt64("file header owner id", 6, "%6d") | |
153 if err != nil { | |
154 return nil, err | |
155 } | |
156 fi.uid = int(ownerid) | |
157 | |
158 // Group ID, 6 bytes | |
159 groupid, err := ar.readHeaderInt64("file header group id", 6, "%6d") | |
160 if err != nil { | |
161 return nil, err | |
162 } | |
163 fi.gid = int(groupid) | |
164 | |
165 // File mode, 8 bytes | |
166 filemod, err := ar.readHeaderInt64("file header file mode", 8, "%8o") | |
167 if err != nil { | |
168 return nil, err | |
169 } | |
170 fi.mode = uint32(filemod) | |
171 | |
172 // File size, 10 bytes | |
173 size, err := ar.readHeaderInt64("file header data size", 10, "%10d") | |
174 if err != nil { | |
175 return nil, err | |
176 } | |
177 fi.size = size - namelen | |
178 | |
179 // File magic, 2 bytes | |
180 if err = ar.checkBytes("file header file magic", []byte{'\x60', '\n'}); err != nil { | |
181 return nil, err | |
182 } | |
183 | |
184 ar.bodyReader = io.LimitedReader{R: ar.r, N: size} | |
185 ar.bodyHasPadding = (size%2 != 0) | |
186 | |
187 // Filename - BSD variant | |
188 filename := make([]byte, namelen) | |
189 if _, err := io.ReadFull(&ar.bodyReader, filename); err != nil { | |
190 return nil, &IOError{section: "body filename", err: err} | |
191 } | |
192 fi.name = string(filename) | |
193 | |
194 return &fi, nil | |
195 } | |
196 | |
197 func (ar *Reader) Body() io.Reader { | |
198 return &ar.bodyReader | |
199 } | |
OLD | NEW |