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

Side by Side Diff: common/archive/ar/reader.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 // 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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698