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

Side by Side Diff: common/archive/ar/writer.go

Issue 2043623002: luci-go: Tools for working with BSD style ar archives. (Closed) Base URL: https://github.com/luci/luci-go@master
Patch Set: Latest fixes. Created 4 years, 6 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
OLDNEW
(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 // Write an ar archive file with BSD style filenames.
6
7 package ar
8
9 import (
10 "fmt"
11 "io"
12 "log"
13 "os"
14 )
15
16 // Special UsageError that indicates trying to write after closing.
17 var (
M-A Ruel 2016/06/14 14:30:43 since there's only one, do not use () I don't thin
18 ErrWriteAfterClose = UsageError{msg: "write after file closed"}
19 )
20
21 // WriteTooLongError indicates trying to write the wrong amount of data into the archive.
M-A Ruel 2016/06/14 14:30:43 wrap at 80 cols
mithro 2016/11/06 22:56:53 Done.
22 // WriteTooLongError is never fatal.
23 type WriteTooLongError struct {
M-A Ruel 2016/06/14 14:30:43 I don't think these error types should be exported
24 needed int64
25 got int64
26 }
27
28 func (e *WriteTooLongError) Error() string {
29 return fmt.Sprintf("archive/ar: invalid data length (needed %d, got %d)" , e.needed, e.got)
30 }
31 func (e *WriteTooLongError) Fatal() bool {
32 return false
33 }
34
35 // WriteTooLongFatalError indicates that the wrong amount of data *was* written into the archive.
36 // WriteTooLongFatalError is always fatal.
37 type WriteTooLongFatalError struct {
38 needed int64
39 got int64
40 }
41
42 func (e *WriteTooLongFatalError) Error() string {
43 return fmt.Sprintf("archive/ar: *archive corrupted* -- invalid data writ ten (needed %d, got %d)", e.needed, e.got)
44 }
45 func (e *WriteTooLongFatalError) Fatal() bool {
46 return true
47 }
48
49 const DefaultModifyTime = 1447140471
50 const DefaultUser = 1000
51 const DefaultGroup = 1000
52 const DefaultFileMode = 0100640 // 100640 -- Octal
53
54 type writerStage uint
55
56 const (
57 writeStageHeader writerStage = iota
58 writeStageBody
59 writeStageClosed
60 )
61
62 type Writer struct {
63 w io.Writer
64 stage writerStage
65
66 streamSizeNeeded int64
67 bodyNeedsPadding bool
68 }
69
70 func NewWriter(w io.Writer) (*Writer, Error) {
71 if _, err := io.WriteString(w, "!<arch>\n"); err != nil {
72 return nil, &IOError{section: "archive header", err: err}
73 }
74 return &Writer{w: w, stage: writeStageHeader}, nil
75 }
76
77 func (aw *Writer) Close() Error {
78 switch aw.stage {
79 case writeStageHeader:
80 // Good
81 case writeStageBody:
82 return &UsageError{msg: "currently writing a file"}
83 case writeStageClosed:
84 return &ErrWriteAfterClose
85 default:
86 log.Fatalf("unknown writer mode: %d", aw.stage)
87 }
88 aw.stage = writeStageClosed
89 aw.w = nil
90 return nil
91 }
92
93 func (aw *Writer) wroteBytes(numbytes int64) Error {
94 if numbytes > aw.streamSizeNeeded {
95 return &WriteTooLongFatalError{aw.streamSizeNeeded, numbytes}
96 }
97
98 aw.streamSizeNeeded -= numbytes
99 if aw.streamSizeNeeded != 0 {
100 return nil
101 }
102
103 // Padding to 16bit boundary
104 if aw.bodyNeedsPadding {
105 if _, err := io.WriteString(aw.w, "\n"); err != nil {
106 return &IOError{section: "body padding", err: err}
107 }
108 aw.bodyNeedsPadding = false
109 }
110 aw.stage = writeStageHeader
111 return nil
112 }
113
114 // checkCanWriteContent returns nil if the stream is in a position to write a
115 // stream content.
116 func (aw *Writer) checkCanWriteContent() Error {
117 switch aw.stage {
118 case writeStageHeader:
119 return &UsageError{msg: "need to write header first"}
120 case writeStageBody:
121 // Good
122 return nil
123 case writeStageClosed:
124 return &ErrWriteAfterClose
125 default:
126 log.Fatalf("unknown writer mode: %d", aw.stage)
127 }
128 return nil
129 }
130
131 // Check we have finished writing bytes
132 func (aw *Writer) checkFinished() Error {
133 if aw.streamSizeNeeded != 0 {
134 return &WriteTooLongFatalError{aw.streamSizeNeeded, -1}
135 }
136 return nil
137 }
138
139 func (aw *Writer) writePartial(section string, data []byte) Error {
140 if err := aw.checkCanWriteContent(); err != nil {
141 return err
142 }
143
144 datalen := int64(len(data))
145 if datalen > aw.streamSizeNeeded {
146 return &WriteTooLongError{aw.streamSizeNeeded, datalen}
147 }
148
149 if _, err := aw.w.Write(data); err != nil {
150 return &IOError{section: section, err: err}
151 }
152 if err := aw.wroteBytes(datalen); err != nil {
153 return err
154 }
155 return nil
156 }
157
158 // ReaderFrom writes all the data from r (till EOF) into the archive.
159 // The size of data should match the value given previously to WriteHeader*
160 // functions.
161 // ReaderFrom returns the number of bytes written on success.
162 // Calling with wrong size data will return ErrFatalWriteToLong, the archive
163 // should be considered broken.
164 // Calling after Close will return ErrWriteAfterClose
165 func (aw *Writer) ReaderFrom(r io.Reader) (int64, Error) {
166 if err := aw.checkCanWriteContent(); err != nil {
167 return -1, err
168 }
169
170 count, err := io.Copy(aw.w, r)
171 if err != nil {
172 return -1, &IOError{section: "body file contents", err: err}
173 }
174 if err := aw.wroteBytes(count); err != nil {
175 return -1, err
176 }
177 if err := aw.checkFinished(); err != nil {
178 return -1, err
179 }
180
181 return count, nil
182 }
183
184 // WriteBytes writes the given byte data into the archive.
185 // The size of data array should match the value given previously to
186 // WriteHeader* functions.
187 // WriteBytes returns nil on success.
188 // Calling with wrong size data will return WriteTooLongError but the archive wi ll
M-A Ruel 2016/06/14 14:30:43 80 cols for comments is helpful
mithro 2016/11/06 22:56:53 Done.
189 // still be valid.
190 // Calling after Close will return ErrWriteAfterClose.
191 func (aw *Writer) WriteBytes(data []byte) Error {
192 if err := aw.checkCanWriteContent(); err != nil {
193 return err
194 }
195
196 if datalen := int64(len(data)); datalen != aw.streamSizeNeeded {
197 return &WriteTooLongError{aw.streamSizeNeeded, datalen}
198 }
199
200 if err := aw.writePartial("body content", data); err != nil {
201 return err
202 }
203 if err := aw.checkFinished(); err != nil {
M-A Ruel 2016/06/14 14:30:43 tail call optimisation: return aw.checkFinished()
mithro 2016/11/06 22:56:53 Done.
204 return err
205 }
206 return nil
207 }
208
209 func (aw *Writer) writeHeaderInternal(filepath string, size int64, modtime uint6 4, ownerid uint, groupid uint, filemod uint) Error {
210 switch aw.stage {
211 case writeStageHeader:
212 // Good
213 case writeStageBody:
214 return &UsageError{msg: "usage error, currently writing a file." }
215 case writeStageClosed:
216 return &ErrWriteAfterClose
217 default:
218 log.Fatalf("unknown writer mode: %d", aw.stage)
219 }
220
221 // File name length prefixed with '#1/' (BSD variant), 16 bytes
222 if _, err := fmt.Fprintf(aw.w, "#1/%-13d", len(filepath)); err != nil {
223 return &IOError{section: "file header filepath length", err: err }
224 }
225
226 // Modtime, 12 bytes
227 if _, err := fmt.Fprintf(aw.w, "%-12d", modtime); err != nil {
228 return &IOError{section: "file header modtime", err: err}
229 }
230
231 // Owner ID, 6 bytes
232 if _, err := fmt.Fprintf(aw.w, "%-6d", ownerid); err != nil {
233 return &IOError{section: "file header owner id", err: err}
234 }
235
236 // Group ID, 6 bytes
237 if _, err := fmt.Fprintf(aw.w, "%-6d", groupid); err != nil {
238 return &IOError{section: "file header group id", err: err}
239 }
240
241 // File mode, 8 bytes
242 if _, err := fmt.Fprintf(aw.w, "%-8o", filemod); err != nil {
243 return &IOError{section: "file header file mode", err: err}
244 }
245
246 // In BSD variant, file size includes the filepath length
247 aw.streamSizeNeeded = int64(len(filepath)) + size
248
249 // File size, 10 bytes
250 if _, err := fmt.Fprintf(aw.w, "%-10d", aw.streamSizeNeeded); err != nil {
251 return &IOError{section: "file header file size", err: err}
252 }
253
254 // File magic, 2 bytes
255 if _, err := io.WriteString(aw.w, "\x60\n"); err != nil {
256 return &IOError{section: "file header file magic", err: err}
257 }
258
259 aw.stage = writeStageBody
260 aw.bodyNeedsPadding = (aw.streamSizeNeeded%2 != 0)
261
262 // File path - BSD variant
263 return aw.writePartial("body filepath", []byte(filepath))
264 }
265
266 // WriteHeaderDefault writes header information about a file to the archive
267 // using default values for everything apart from name and size.
268 // WriteBytes or ReaderFrom should be called after writing the header.
269 // Calling at the wrong time will return a UsageError.
270 func (aw *Writer) WriteHeaderDefault(filepath string, size int64) Error {
271 return aw.writeHeaderInternal(filepath, size, DefaultModifyTime, Default User, DefaultGroup, DefaultFileMode)
272 }
273
274 // WriteHeader writes header information about a file to the archive using the
275 // information from os.Stat.
276 // WriteBytes or ReaderFrom should be called after writing the header.
277 // Calling at the wrong time will return a UsageError.
278 func (aw *Writer) WriteHeader(stat os.FileInfo) Error {
279 if stat.IsDir() {
280 return &UsageError{msg: "only work with files, not directories"}
281 }
282
283 mode := stat.Mode()
284 if mode&os.ModeSymlink == os.ModeSymlink {
285 return &UsageError{msg: "only work with files, not symlinks"}
286 }
287
288 /* FIXME: Should we also exclude other "special" files?
M-A Ruel 2016/06/14 14:30:43 TODO(mithro):
mithro 2016/11/06 22:56:53 Done.
289 if (stat.Mode().ModeType != 0) {
290 return &argError{stat, "Only work with plain files."}
291 }
292 */
293
294 return aw.writeHeaderInternal(stat.Name(), stat.Size(), uint64(stat.ModT ime().Unix()), DefaultUser, DefaultGroup, uint(mode&os.ModePerm))
295 }
296
297 // Add a file to the archive.
298 // Function is equivalent to calling "WriteHeaderDefault(filepath, len(data); Wr iteBytes(data)".
299 func (aw *Writer) Add(filepath string, data []byte) Error {
M-A Ruel 2016/06/14 14:30:43 AddRaw(), AddContent() ? I would have liked WriteF
mithro 2016/11/06 22:56:53 Used AddWithContent().
300 if err := aw.WriteHeaderDefault(filepath, int64(len(data))); err != nil {
301 return err
302 }
303
304 return aw.WriteBytes(data)
305 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698