blob: 51e7aa5dfb027de97e84849d2addf53a93a0c950 [file] [log] [blame]
Robert Sloan572a4e22017-04-17 10:52:19 -07001// Copyright (c) 2017, Google Inc.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
14
15// ar.go contains functions for parsing .a archive files.
16
17package main
18
19import (
20 "bytes"
21 "errors"
22 "io"
23 "strconv"
24 "strings"
25)
26
27// ParseAR parses an archive file from r and returns a map from filename to
28// contents, or else an error.
29func ParseAR(r io.Reader) (map[string][]byte, error) {
30 // See https://en.wikipedia.org/wiki/Ar_(Unix)#File_format_details
31 const expectedMagic = "!<arch>\n"
32 var magic [len(expectedMagic)]byte
33 if _, err := io.ReadFull(r, magic[:]); err != nil {
34 return nil, err
35 }
36 if string(magic[:]) != expectedMagic {
37 return nil, errors.New("ar: not an archive file")
38 }
39
40 const filenameTableName = "//"
41 const symbolTableName = "/"
42 var longFilenameTable []byte
43 ret := make(map[string][]byte)
44
45 for {
46 var header [60]byte
47 if _, err := io.ReadFull(r, header[:]); err != nil {
48 if err == io.EOF {
49 break
50 }
51 return nil, errors.New("ar: error reading file header: " + err.Error())
52 }
53
54 name := strings.TrimRight(string(header[:16]), " ")
55 sizeStr := strings.TrimRight(string(header[48:58]), "\x00 ")
56 size, err := strconv.ParseUint(sizeStr, 10, 64)
57 if err != nil {
58 return nil, errors.New("ar: failed to parse file size: " + err.Error())
59 }
60
61 // File contents are padded to a multiple of two bytes
62 storedSize := size
63 if storedSize%2 == 1 {
64 storedSize++
65 }
66
67 contents := make([]byte, storedSize)
68 if _, err := io.ReadFull(r, contents); err != nil {
69 return nil, errors.New("ar: error reading file contents: " + err.Error())
70 }
71 contents = contents[:size]
72
73 switch {
74 case name == filenameTableName:
75 if longFilenameTable != nil {
76 return nil, errors.New("ar: two filename tables found")
77 }
78 longFilenameTable = contents
79 continue
80
81 case name == symbolTableName:
82 continue
83
84 case len(name) > 1 && name[0] == '/':
85 if longFilenameTable == nil {
86 return nil, errors.New("ar: long filename reference found before filename table")
87 }
88
89 // A long filename is stored as "/" followed by a
90 // base-10 offset in the filename table.
91 offset, err := strconv.ParseUint(name[1:], 10, 64)
92 if err != nil {
93 return nil, errors.New("ar: failed to parse filename offset: " + err.Error())
94 }
95 if offset > uint64((^uint(0))>>1) {
96 return nil, errors.New("ar: filename offset overflow")
97 }
98
99 if int(offset) > len(longFilenameTable) {
100 return nil, errors.New("ar: filename offset out of bounds")
101 }
102
103 filename := longFilenameTable[offset:]
104 if i := bytes.IndexByte(filename, '/'); i < 0 {
105 return nil, errors.New("ar: unterminated filename in table")
106 } else {
107 filename = filename[:i]
108 }
109
110 name = string(filename)
111
112 default:
113 name = strings.TrimRight(name, "/")
114 }
115
116 ret[name] = contents
117 }
118
119 return ret, nil
120}