1
2
3
4
5
6
7 package exportdata
8
9
10
11 import (
12 "bufio"
13 "bytes"
14 "errors"
15 "fmt"
16 "go/build"
17 "internal/saferio"
18 "io"
19 "os"
20 "os/exec"
21 "path/filepath"
22 "strings"
23 "sync"
24 )
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 func ReadUnified(r *bufio.Reader) (data []byte, err error) {
56
57
58 const minBufferSize = 4096
59 r = bufio.NewReaderSize(r, minBufferSize)
60
61 size, err := FindPackageDefinition(r)
62 if err != nil {
63 return
64 }
65 n := size
66
67 objapi, headers, err := ReadObjectHeaders(r)
68 if err != nil {
69 return
70 }
71 n -= len(objapi)
72 for _, h := range headers {
73 n -= len(h)
74 }
75
76 hdrlen, err := ReadExportDataHeader(r)
77 if err != nil {
78 return
79 }
80 n -= hdrlen
81
82
83 const marker = "\n$$\n"
84 n -= len(marker)
85
86 if n < 0 {
87 err = fmt.Errorf("invalid size (%d) in the archive file: %d bytes remain without section headers (recompile package)", size, n)
88 }
89
90
91 data, err = saferio.ReadData(r, uint64(n))
92 if err != nil {
93 return
94 }
95
96
97 var suffix [len(marker)]byte
98 _, err = io.ReadFull(r, suffix[:])
99 if err != nil {
100 return
101 }
102 if s := string(suffix[:]); s != marker {
103 err = fmt.Errorf("read %q instead of end-of-section marker (%q)", s, marker)
104 return
105 }
106
107 return
108 }
109
110
111
112
113
114
115
116
117
118 func FindPackageDefinition(r *bufio.Reader) (size int, err error) {
119
120
121
122 line, err := r.ReadSlice('\n')
123 if err != nil {
124 err = fmt.Errorf("can't find export data (%v)", err)
125 return
126 }
127
128
129 if string(line) != "!<arch>\n" {
130 err = fmt.Errorf("not the start of an archive file (%q)", line)
131 return
132 }
133
134
135 size = readArchiveHeader(r, "__.PKGDEF")
136 if size <= 0 {
137 err = fmt.Errorf("not a package file")
138 return
139 }
140
141 return
142 }
143
144
145
146
147
148
149
150 func ReadObjectHeaders(r *bufio.Reader) (objapi string, headers []string, err error) {
151
152
153 var line []byte
154
155
156 if line, err = r.ReadSlice('\n'); err != nil {
157 err = fmt.Errorf("can't find export data (%v)", err)
158 return
159 }
160 objapi = string(line)
161
162
163 if !strings.HasPrefix(objapi, "go object ") {
164 err = fmt.Errorf("not a go object file: %s", objapi)
165 return
166 }
167
168
169 for {
170
171 line, err = r.Peek(2)
172 if err != nil {
173 return
174 }
175 if string(line) == "$$" {
176 return
177 }
178
179
180 line, err = r.ReadSlice('\n')
181 if err != nil {
182 return
183 }
184 headers = append(headers, string(line))
185 }
186 }
187
188
189
190
191
192
193
194 func ReadExportDataHeader(r *bufio.Reader) (n int, err error) {
195
196 line, err := r.ReadSlice('\n')
197 if err != nil {
198 return
199 }
200
201 hdr := string(line)
202 switch hdr {
203 case "$$\n":
204 err = fmt.Errorf("old textual export format no longer supported (recompile package)")
205 return
206
207 case "$$B\n":
208 var format byte
209 format, err = r.ReadByte()
210 if err != nil {
211 return
212 }
213
214 switch format {
215 case 'u':
216 default:
217
218
219
220
221 err = fmt.Errorf("binary export format %q is no longer supported (recompile package)", format)
222 return
223 }
224
225 default:
226 err = fmt.Errorf("unknown export data header: %q", hdr)
227 return
228 }
229
230 n = len(hdr) + 1
231 return
232 }
233
234
235
236
237
238 func FindPkg(path, srcDir string) (filename, id string, err error) {
239 if path == "" {
240 return "", "", errors.New("path is empty")
241 }
242
243 var noext string
244 switch {
245 default:
246
247
248 if abs, err := filepath.Abs(srcDir); err == nil {
249 srcDir = abs
250 }
251 var bp *build.Package
252 bp, err = build.Import(path, srcDir, build.FindOnly|build.AllowBinary)
253 if bp.PkgObj == "" {
254 if bp.Goroot && bp.Dir != "" {
255 filename, err = lookupGorootExport(bp.Dir)
256 if err == nil {
257 _, err = os.Stat(filename)
258 }
259 if err == nil {
260 return filename, bp.ImportPath, nil
261 }
262 }
263 goto notfound
264 } else {
265 noext = strings.TrimSuffix(bp.PkgObj, ".a")
266 }
267 id = bp.ImportPath
268
269 case build.IsLocalImport(path):
270
271 noext = filepath.Join(srcDir, path)
272 id = noext
273
274 case filepath.IsAbs(path):
275
276
277
278 noext = path
279 id = path
280 }
281
282 if false {
283 if path != id {
284 fmt.Printf("%s -> %s\n", path, id)
285 }
286 }
287
288
289 for _, ext := range pkgExts {
290 filename = noext + ext
291 f, statErr := os.Stat(filename)
292 if statErr == nil && !f.IsDir() {
293 return filename, id, nil
294 }
295 if err == nil {
296 err = statErr
297 }
298 }
299
300 notfound:
301 if err == nil {
302 return "", path, fmt.Errorf("can't find import: %q", path)
303 }
304 return "", path, fmt.Errorf("can't find import: %q: %w", path, err)
305 }
306
307 var pkgExts = [...]string{".a", ".o"}
308
309 var exportMap sync.Map
310
311
312
313
314
315
316
317
318 func lookupGorootExport(pkgDir string) (string, error) {
319 f, ok := exportMap.Load(pkgDir)
320 if !ok {
321 var (
322 listOnce sync.Once
323 exportPath string
324 err error
325 )
326 f, _ = exportMap.LoadOrStore(pkgDir, func() (string, error) {
327 listOnce.Do(func() {
328 cmd := exec.Command(filepath.Join(build.Default.GOROOT, "bin", "go"), "list", "-export", "-f", "{{.Export}}", pkgDir)
329 cmd.Dir = build.Default.GOROOT
330 cmd.Env = append(os.Environ(), "PWD="+cmd.Dir, "GOROOT="+build.Default.GOROOT)
331 var output []byte
332 output, err = cmd.Output()
333 if err != nil {
334 if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
335 err = errors.New(string(ee.Stderr))
336 }
337 return
338 }
339
340 exports := strings.Split(string(bytes.TrimSpace(output)), "\n")
341 if len(exports) != 1 {
342 err = fmt.Errorf("go list reported %d exports; expected 1", len(exports))
343 return
344 }
345
346 exportPath = exports[0]
347 })
348
349 return exportPath, err
350 })
351 }
352
353 return f.(func() (string, error))()
354 }
355
View as plain text