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