Brad Fitzpatrick | 5194744 | 2016-03-01 22:57:46 +0000 | [diff] [blame] | 1 | // Copyright 2013 The Go Authors. All rights reserved. |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 5 | // Package archive implements reading of archive files generated by the Go |
| 6 | // toolchain. |
| 7 | package archive |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 8 | |
| 9 | import ( |
| 10 | "bufio" |
| 11 | "bytes" |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 12 | "cmd/internal/bio" |
Cherry Zhang | 27e3778 | 2020-08-02 19:36:28 -0400 | [diff] [blame] | 13 | "cmd/internal/goobj" |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 14 | "errors" |
| 15 | "fmt" |
| 16 | "io" |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 17 | "log" |
Hiroshi Ioka | 1053ae5 | 2017-09-10 09:45:49 +0900 | [diff] [blame] | 18 | "os" |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 19 | "strconv" |
Tao Qingyun | 31da1d9 | 2020-08-19 01:38:43 +0000 | [diff] [blame] | 20 | "strings" |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 21 | "time" |
| 22 | "unicode/utf8" |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 23 | ) |
| 24 | |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 25 | /* |
| 26 | The archive format is: |
| 27 | |
| 28 | First, on a line by itself |
| 29 | !<arch> |
| 30 | |
| 31 | Then zero or more file records. Each file record has a fixed-size one-line header |
| 32 | followed by data bytes followed by an optional padding byte. The header is: |
| 33 | |
| 34 | %-16s%-12d%-6d%-6d%-8o%-10d` |
| 35 | name mtime uid gid mode size |
| 36 | |
| 37 | (note the trailing backquote). The %-16s here means at most 16 *bytes* of |
| 38 | the name, and if shorter, space padded on the right. |
| 39 | */ |
| 40 | |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 41 | // A Data is a reference to data stored in an object file. |
| 42 | // It records the offset and size of the data, so that a client can |
| 43 | // read the data only if necessary. |
| 44 | type Data struct { |
| 45 | Offset int64 |
| 46 | Size int64 |
| 47 | } |
| 48 | |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 49 | type Archive struct { |
| 50 | f *os.File |
| 51 | Entries []Entry |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 52 | } |
| 53 | |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 54 | func (a *Archive) File() *os.File { return a.f } |
| 55 | |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 56 | type Entry struct { |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 57 | Name string |
| 58 | Type EntryType |
| 59 | Mtime int64 |
| 60 | Uid int |
| 61 | Gid int |
| 62 | Mode os.FileMode |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 63 | Data |
| 64 | Obj *GoObj // nil if this entry is not a Go object file |
| 65 | } |
| 66 | |
| 67 | type EntryType int |
| 68 | |
| 69 | const ( |
| 70 | EntryPkgDef EntryType = iota |
| 71 | EntryGoObj |
| 72 | EntryNativeObj |
| 73 | ) |
| 74 | |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 75 | func (e *Entry) String() string { |
| 76 | return fmt.Sprintf("%s %6d/%-6d %12d %s %s", |
| 77 | (e.Mode & 0777).String(), |
| 78 | e.Uid, |
| 79 | e.Gid, |
| 80 | e.Size, |
| 81 | time.Unix(e.Mtime, 0).Format(timeFormat), |
| 82 | e.Name) |
| 83 | } |
| 84 | |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 85 | type GoObj struct { |
| 86 | TextHeader []byte |
Tao Qingyun | 31da1d9 | 2020-08-19 01:38:43 +0000 | [diff] [blame] | 87 | Arch string |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 88 | Data |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 89 | } |
| 90 | |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 91 | const ( |
| 92 | entryHeader = "%s%-12d%-6d%-6d%-8o%-10d`\n" |
| 93 | // In entryHeader the first entry, the name, is always printed as 16 bytes right-padded. |
| 94 | entryLen = 16 + 12 + 6 + 6 + 8 + 10 + 1 + 1 |
| 95 | timeFormat = "Jan _2 15:04 2006" |
| 96 | ) |
| 97 | |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 98 | var ( |
| 99 | archiveHeader = []byte("!<arch>\n") |
| 100 | archiveMagic = []byte("`\n") |
| 101 | goobjHeader = []byte("go objec") // truncated to size of archiveHeader |
| 102 | |
| 103 | errCorruptArchive = errors.New("corrupt archive") |
| 104 | errTruncatedArchive = errors.New("truncated archive") |
Dave Cheney | bce9747 | 2016-04-09 15:04:45 +1000 | [diff] [blame] | 105 | errCorruptObject = errors.New("corrupt object file") |
| 106 | errNotObject = errors.New("unrecognized object file format") |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 107 | ) |
| 108 | |
| 109 | // An objReader is an object file reader. |
| 110 | type objReader struct { |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 111 | a *Archive |
| 112 | b *bio.Reader |
| 113 | err error |
| 114 | offset int64 |
| 115 | limit int64 |
| 116 | tmp [256]byte |
Russ Cox | 2404b7f | 2013-12-18 19:00:52 -0500 | [diff] [blame] | 117 | } |
| 118 | |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 119 | func (r *objReader) init(f *os.File) { |
| 120 | r.a = &Archive{f, nil} |
Joe Tsai | acc757f | 2016-04-05 11:22:53 -0700 | [diff] [blame] | 121 | r.offset, _ = f.Seek(0, io.SeekCurrent) |
| 122 | r.limit, _ = f.Seek(0, io.SeekEnd) |
| 123 | f.Seek(r.offset, io.SeekStart) |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 124 | r.b = bio.NewReader(f) |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 125 | } |
| 126 | |
| 127 | // error records that an error occurred. |
| 128 | // It returns only the first error, so that an error |
| 129 | // caused by an earlier error does not discard information |
| 130 | // about the earlier error. |
| 131 | func (r *objReader) error(err error) error { |
| 132 | if r.err == nil { |
| 133 | if err == io.EOF { |
| 134 | err = io.ErrUnexpectedEOF |
| 135 | } |
| 136 | r.err = err |
| 137 | } |
| 138 | // panic("corrupt") // useful for debugging |
| 139 | return r.err |
| 140 | } |
| 141 | |
Hiroshi Ioka | 1053ae5 | 2017-09-10 09:45:49 +0900 | [diff] [blame] | 142 | // peek returns the next n bytes without advancing the reader. |
| 143 | func (r *objReader) peek(n int) ([]byte, error) { |
| 144 | if r.err != nil { |
| 145 | return nil, r.err |
| 146 | } |
| 147 | if r.offset >= r.limit { |
| 148 | r.error(io.ErrUnexpectedEOF) |
| 149 | return nil, r.err |
| 150 | } |
| 151 | b, err := r.b.Peek(n) |
| 152 | if err != nil { |
| 153 | if err != bufio.ErrBufferFull { |
| 154 | r.error(err) |
| 155 | } |
| 156 | } |
| 157 | return b, err |
| 158 | } |
| 159 | |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 160 | // readByte reads and returns a byte from the input file. |
| 161 | // On I/O error or EOF, it records the error but returns byte 0. |
| 162 | // A sequence of 0 bytes will eventually terminate any |
| 163 | // parsing state in the object file. In particular, it ends the |
| 164 | // reading of a varint. |
| 165 | func (r *objReader) readByte() byte { |
| 166 | if r.err != nil { |
| 167 | return 0 |
| 168 | } |
| 169 | if r.offset >= r.limit { |
| 170 | r.error(io.ErrUnexpectedEOF) |
| 171 | return 0 |
| 172 | } |
| 173 | b, err := r.b.ReadByte() |
| 174 | if err != nil { |
| 175 | if err == io.EOF { |
| 176 | err = io.ErrUnexpectedEOF |
| 177 | } |
| 178 | r.error(err) |
| 179 | b = 0 |
| 180 | } else { |
| 181 | r.offset++ |
| 182 | } |
| 183 | return b |
| 184 | } |
| 185 | |
| 186 | // read reads exactly len(b) bytes from the input file. |
| 187 | // If an error occurs, read returns the error but also |
| 188 | // records it, so it is safe for callers to ignore the result |
| 189 | // as long as delaying the report is not a problem. |
| 190 | func (r *objReader) readFull(b []byte) error { |
| 191 | if r.err != nil { |
| 192 | return r.err |
| 193 | } |
| 194 | if r.offset+int64(len(b)) > r.limit { |
| 195 | return r.error(io.ErrUnexpectedEOF) |
| 196 | } |
| 197 | n, err := io.ReadFull(r.b, b) |
| 198 | r.offset += int64(n) |
| 199 | if err != nil { |
| 200 | return r.error(err) |
| 201 | } |
| 202 | return nil |
| 203 | } |
| 204 | |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 205 | // skip skips n bytes in the input. |
| 206 | func (r *objReader) skip(n int64) { |
| 207 | if n < 0 { |
| 208 | r.error(fmt.Errorf("debug/goobj: internal error: misuse of skip")) |
| 209 | } |
| 210 | if n < int64(len(r.tmp)) { |
| 211 | // Since the data is so small, a just reading from the buffered |
| 212 | // reader is better than flushing the buffer and seeking. |
| 213 | r.readFull(r.tmp[:n]) |
| 214 | } else if n <= int64(r.b.Buffered()) { |
| 215 | // Even though the data is not small, it has already been read. |
| 216 | // Advance the buffer instead of seeking. |
| 217 | for n > int64(len(r.tmp)) { |
| 218 | r.readFull(r.tmp[:]) |
| 219 | n -= int64(len(r.tmp)) |
| 220 | } |
| 221 | r.readFull(r.tmp[:n]) |
| 222 | } else { |
| 223 | // Seek, giving up buffered data. |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 224 | r.b.MustSeek(r.offset+n, io.SeekStart) |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 225 | r.offset += n |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 226 | } |
| 227 | } |
| 228 | |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 229 | // New writes to f to make a new archive. |
| 230 | func New(f *os.File) (*Archive, error) { |
| 231 | _, err := f.Write(archiveHeader) |
| 232 | if err != nil { |
| 233 | return nil, err |
| 234 | } |
| 235 | return &Archive{f: f}, nil |
| 236 | } |
| 237 | |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 238 | // Parse parses an object file or archive from f. |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 239 | func Parse(f *os.File, verbose bool) (*Archive, error) { |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 240 | var r objReader |
| 241 | r.init(f) |
| 242 | t, err := r.peek(8) |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 243 | if err != nil { |
| 244 | if err == io.EOF { |
| 245 | err = io.ErrUnexpectedEOF |
| 246 | } |
| 247 | return nil, err |
| 248 | } |
| 249 | |
| 250 | switch { |
| 251 | default: |
| 252 | return nil, errNotObject |
| 253 | |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 254 | case bytes.Equal(t, archiveHeader): |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 255 | if err := r.parseArchive(verbose); err != nil { |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 256 | return nil, err |
| 257 | } |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 258 | case bytes.Equal(t, goobjHeader): |
| 259 | off := r.offset |
| 260 | o := &GoObj{} |
| 261 | if err := r.parseObject(o, r.limit-off); err != nil { |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 262 | return nil, err |
| 263 | } |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 264 | r.a.Entries = []Entry{{ |
| 265 | Name: f.Name(), |
| 266 | Type: EntryGoObj, |
| 267 | Data: Data{off, r.limit - off}, |
| 268 | Obj: o, |
| 269 | }} |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 270 | } |
| 271 | |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 272 | return r.a, nil |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 273 | } |
| 274 | |
| 275 | // trimSpace removes trailing spaces from b and returns the corresponding string. |
| 276 | // This effectively parses the form used in archive headers. |
| 277 | func trimSpace(b []byte) string { |
| 278 | return string(bytes.TrimRight(b, " ")) |
| 279 | } |
| 280 | |
| 281 | // parseArchive parses a Unix archive of Go object files. |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 282 | func (r *objReader) parseArchive(verbose bool) error { |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 283 | r.readFull(r.tmp[:8]) // consume header (already checked) |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 284 | for r.offset < r.limit { |
| 285 | if err := r.readFull(r.tmp[:60]); err != nil { |
| 286 | return err |
| 287 | } |
| 288 | data := r.tmp[:60] |
| 289 | |
| 290 | // Each file is preceded by this text header (slice indices in first column): |
| 291 | // 0:16 name |
| 292 | // 16:28 date |
| 293 | // 28:34 uid |
| 294 | // 34:40 gid |
| 295 | // 40:48 mode |
| 296 | // 48:58 size |
| 297 | // 58:60 magic - `\n |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 298 | // We only care about name, size, and magic, unless in verbose mode. |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 299 | // The fields are space-padded on the right. |
| 300 | // The size is in decimal. |
| 301 | // The file data - size bytes - follows the header. |
| 302 | // Headers are 2-byte aligned, so if size is odd, an extra padding |
| 303 | // byte sits between the file data and the next header. |
| 304 | // The file data that follows is padded to an even number of bytes: |
| 305 | // if size is odd, an extra padding byte is inserted betw the next header. |
| 306 | if len(data) < 60 { |
| 307 | return errTruncatedArchive |
| 308 | } |
| 309 | if !bytes.Equal(data[58:60], archiveMagic) { |
| 310 | return errCorruptArchive |
| 311 | } |
| 312 | name := trimSpace(data[0:16]) |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 313 | var err error |
| 314 | get := func(start, end, base, bitsize int) int64 { |
| 315 | if err != nil { |
| 316 | return 0 |
| 317 | } |
| 318 | var v int64 |
| 319 | v, err = strconv.ParseInt(trimSpace(data[start:end]), base, bitsize) |
| 320 | return v |
| 321 | } |
| 322 | size := get(48, 58, 10, 64) |
| 323 | var ( |
| 324 | mtime int64 |
| 325 | uid, gid int |
| 326 | mode os.FileMode |
| 327 | ) |
| 328 | if verbose { |
| 329 | mtime = get(16, 28, 10, 64) |
| 330 | uid = int(get(28, 34, 10, 32)) |
| 331 | gid = int(get(34, 40, 10, 32)) |
| 332 | mode = os.FileMode(get(40, 48, 8, 32)) |
| 333 | } |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 334 | if err != nil { |
| 335 | return errCorruptArchive |
| 336 | } |
| 337 | data = data[60:] |
| 338 | fsize := size + size&1 |
| 339 | if fsize < 0 || fsize < size { |
| 340 | return errCorruptArchive |
| 341 | } |
| 342 | switch name { |
Matthew Dempsky | 67dbde0 | 2016-02-25 14:58:03 -0800 | [diff] [blame] | 343 | case "__.PKGDEF": |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 344 | r.a.Entries = append(r.a.Entries, Entry{ |
| 345 | Name: name, |
| 346 | Type: EntryPkgDef, |
| 347 | Mtime: mtime, |
| 348 | Uid: uid, |
| 349 | Gid: gid, |
| 350 | Mode: mode, |
| 351 | Data: Data{r.offset, size}, |
| 352 | }) |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 353 | r.skip(size) |
| 354 | default: |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 355 | var typ EntryType |
| 356 | var o *GoObj |
| 357 | offset := r.offset |
Hiroshi Ioka | 1053ae5 | 2017-09-10 09:45:49 +0900 | [diff] [blame] | 358 | p, err := r.peek(8) |
| 359 | if err != nil { |
| 360 | return err |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 361 | } |
Hiroshi Ioka | 1053ae5 | 2017-09-10 09:45:49 +0900 | [diff] [blame] | 362 | if bytes.Equal(p, goobjHeader) { |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 363 | typ = EntryGoObj |
| 364 | o = &GoObj{} |
| 365 | r.parseObject(o, size) |
Hiroshi Ioka | 1053ae5 | 2017-09-10 09:45:49 +0900 | [diff] [blame] | 366 | } else { |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 367 | typ = EntryNativeObj |
| 368 | r.skip(size) |
Hiroshi Ioka | 1053ae5 | 2017-09-10 09:45:49 +0900 | [diff] [blame] | 369 | } |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 370 | r.a.Entries = append(r.a.Entries, Entry{ |
| 371 | Name: name, |
| 372 | Type: typ, |
| 373 | Mtime: mtime, |
| 374 | Uid: uid, |
| 375 | Gid: gid, |
| 376 | Mode: mode, |
| 377 | Data: Data{offset, size}, |
| 378 | Obj: o, |
| 379 | }) |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 380 | } |
| 381 | if size&1 != 0 { |
| 382 | r.skip(1) |
| 383 | } |
| 384 | } |
| 385 | return nil |
| 386 | } |
| 387 | |
| 388 | // parseObject parses a single Go object file. |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 389 | // The object file consists of a textual header ending in "\n!\n" |
| 390 | // and then the part we want to parse begins. |
| 391 | // The format of that part is defined in a comment at the top |
| 392 | // of src/liblink/objfile.c. |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 393 | func (r *objReader) parseObject(o *GoObj, size int64) error { |
Keith Randall | a99f812 | 2016-07-02 17:19:25 -0700 | [diff] [blame] | 394 | h := make([]byte, 0, 256) |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 395 | var c1, c2, c3 byte |
| 396 | for { |
| 397 | c1, c2, c3 = c2, c3, r.readByte() |
Keith Randall | a99f812 | 2016-07-02 17:19:25 -0700 | [diff] [blame] | 398 | h = append(h, c3) |
Robert Griesemer | 4e777c8 | 2015-10-22 10:08:45 -0700 | [diff] [blame] | 399 | // The new export format can contain 0 bytes. |
| 400 | // Don't consider them errors, only look for r.err != nil. |
| 401 | if r.err != nil { |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 402 | return errCorruptObject |
| 403 | } |
| 404 | if c1 == '\n' && c2 == '!' && c3 == '\n' { |
| 405 | break |
| 406 | } |
| 407 | } |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 408 | o.TextHeader = h |
Tao Qingyun | 31da1d9 | 2020-08-19 01:38:43 +0000 | [diff] [blame] | 409 | hs := strings.Fields(string(h)) |
| 410 | if len(hs) >= 4 { |
| 411 | o.Arch = hs[3] |
| 412 | } |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 413 | o.Offset = r.offset |
| 414 | o.Size = size - int64(len(h)) |
Keith Randall | a99f812 | 2016-07-02 17:19:25 -0700 | [diff] [blame] | 415 | |
Than McIntosh | e5acb58 | 2019-10-16 12:31:33 -0400 | [diff] [blame] | 416 | p, err := r.peek(8) |
| 417 | if err != nil { |
| 418 | return err |
| 419 | } |
Cherry Zhang | 27e3778 | 2020-08-02 19:36:28 -0400 | [diff] [blame] | 420 | if !bytes.Equal(p, []byte(goobj.Magic)) { |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 421 | return r.error(errCorruptObject) |
| 422 | } |
Cherry Zhang | 9559877 | 2020-07-30 20:49:29 -0400 | [diff] [blame] | 423 | r.skip(o.Size) |
Russ Cox | 08b846b | 2013-12-16 12:52:21 -0500 | [diff] [blame] | 424 | return nil |
| 425 | } |
Cherry Zhang | 3a185d7 | 2020-08-05 21:16:52 -0400 | [diff] [blame] | 426 | |
| 427 | // AddEntry adds an entry to the end of a, with the content from r. |
| 428 | func (a *Archive) AddEntry(typ EntryType, name string, mtime int64, uid, gid int, mode os.FileMode, size int64, r io.Reader) { |
| 429 | off, err := a.f.Seek(0, io.SeekEnd) |
| 430 | if err != nil { |
| 431 | log.Fatal(err) |
| 432 | } |
| 433 | n, err := fmt.Fprintf(a.f, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size) |
| 434 | if err != nil || n != entryLen { |
| 435 | log.Fatal("writing entry header: ", err) |
| 436 | } |
| 437 | n1, _ := io.CopyN(a.f, r, size) |
| 438 | if n1 != size { |
| 439 | log.Fatal(err) |
| 440 | } |
| 441 | if (off+size)&1 != 0 { |
| 442 | a.f.Write([]byte{0}) // pad to even byte |
| 443 | } |
| 444 | a.Entries = append(a.Entries, Entry{ |
| 445 | Name: name, |
| 446 | Type: typ, |
| 447 | Mtime: mtime, |
| 448 | Uid: uid, |
| 449 | Gid: gid, |
| 450 | Mode: mode, |
| 451 | Data: Data{off + entryLen, size}, |
| 452 | }) |
| 453 | } |
| 454 | |
| 455 | // exactly16Bytes truncates the string if necessary so it is at most 16 bytes long, |
| 456 | // then pads the result with spaces to be exactly 16 bytes. |
| 457 | // Fmt uses runes for its width calculation, but we need bytes in the entry header. |
| 458 | func exactly16Bytes(s string) string { |
| 459 | for len(s) > 16 { |
| 460 | _, wid := utf8.DecodeLastRuneInString(s) |
| 461 | s = s[:len(s)-wid] |
| 462 | } |
| 463 | const sixteenSpaces = " " |
| 464 | s += sixteenSpaces[:16-len(s)] |
| 465 | return s |
| 466 | } |