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

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: 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 // 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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698