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