Chromium Code Reviews| 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"} | |
| 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 } | |
| OLD | NEW |