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

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

Powered by Google App Engine
This is Rietveld 408576698