Source file src/cmd/pack/pack.go
1
2
3
4
5 package main
6
7 import (
8 "fmt"
9 "io"
10 "log"
11 "os"
12 "path/filepath"
13 "strconv"
14 "strings"
15 "time"
16 "unicode/utf8"
17 )
18
19
34
35 const usageMessage = `Usage: pack op file.a [name....]
36 Where op is one of cprtx optionally followed by v for verbose output.
37 For compatibility with old Go build environments the op string grc is
38 accepted as a synonym for c.
39
40 For more information, run
41 go doc cmd/pack`
42
43 func usage() {
44 fmt.Fprintln(os.Stderr, usageMessage)
45 os.Exit(2)
46 }
47
48 func main() {
49 log.SetFlags(0)
50 log.SetPrefix("pack: ")
51
52 if len(os.Args) < 3 {
53 log.Print("not enough arguments")
54 fmt.Fprintln(os.Stderr)
55 usage()
56 }
57 setOp(os.Args[1])
58 var ar *Archive
59 switch op {
60 case 'p':
61 ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
62 ar.scan(ar.printContents)
63 case 'r':
64 ar = archive(os.Args[2], os.O_RDWR, os.Args[3:])
65 ar.scan(ar.skipContents)
66 ar.addFiles()
67 case 'c':
68 ar = archive(os.Args[2], os.O_RDWR|os.O_TRUNC, os.Args[3:])
69 ar.addPkgdef()
70 ar.addFiles()
71 case 't':
72 ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
73 ar.scan(ar.tableOfContents)
74 case 'x':
75 ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
76 ar.scan(ar.extractContents)
77 default:
78 log.Printf("invalid operation %q", os.Args[1])
79 fmt.Fprintln(os.Stderr)
80 usage()
81 }
82 if len(ar.files) > 0 {
83 log.Fatalf("file %q not in archive", ar.files[0])
84 }
85 }
86
87
88
89
90
91 var (
92 op rune
93 verbose bool
94 )
95
96
97 func setOp(arg string) {
98
99
100
101
102 if arg == "grc" {
103 arg = "c"
104 }
105
106 for _, r := range arg {
107 switch r {
108 case 'c', 'p', 'r', 't', 'x':
109 if op != 0 {
110
111 usage()
112 }
113 op = r
114 case 'v':
115 if verbose {
116
117 usage()
118 }
119 verbose = true
120 default:
121 usage()
122 }
123 }
124 }
125
126 const (
127 arHeader = "!<arch>\n"
128 entryHeader = "%s%-12d%-6d%-6d%-8o%-10d`\n"
129
130 entryLen = 16 + 12 + 6 + 6 + 8 + 10 + 1 + 1
131 timeFormat = "Jan _2 15:04 2006"
132 )
133
134
135
136 type Archive struct {
137 fd *os.File
138 files []string
139 pad int
140 matchAll bool
141 }
142
143
144 func archive(name string, mode int, files []string) *Archive {
145
146
147 if !existingArchive(name) || mode&os.O_TRUNC != 0 {
148 create(name)
149 mode &^= os.O_TRUNC
150 }
151 fd, err := os.OpenFile(name, mode, 0)
152 if err != nil {
153 log.Fatal(err)
154 }
155 checkHeader(fd)
156 return &Archive{
157 fd: fd,
158 files: files,
159 matchAll: len(files) == 0,
160 }
161 }
162
163
164 func create(name string) {
165 fd, err := os.Create(name)
166 if err != nil {
167 log.Fatal(err)
168 }
169 _, err = fmt.Fprint(fd, arHeader)
170 if err != nil {
171 log.Fatal(err)
172 }
173 fd.Close()
174 }
175
176
177
178 func existingArchive(name string) bool {
179 fd, err := os.Open(name)
180 if err != nil {
181 if os.IsNotExist(err) {
182 return false
183 }
184 log.Fatalf("cannot open file: %s", err)
185 }
186 checkHeader(fd)
187 fd.Close()
188 return true
189 }
190
191
192
193 func checkHeader(fd *os.File) {
194 buf := make([]byte, len(arHeader))
195 _, err := io.ReadFull(fd, buf)
196 if err != nil || string(buf) != arHeader {
197 log.Fatalf("%s is not an archive: bad header", fd.Name())
198 }
199 }
200
201
202 type Entry struct {
203 name string
204 mtime int64
205 uid int
206 gid int
207 mode os.FileMode
208 size int64
209 }
210
211 func (e *Entry) String() string {
212 return fmt.Sprintf("%s %6d/%-6d %12d %s %s",
213 (e.mode & 0777).String(),
214 e.uid,
215 e.gid,
216 e.size,
217 time.Unix(e.mtime, 0).Format(timeFormat),
218 e.name)
219 }
220
221
222 func (ar *Archive) readMetadata() *Entry {
223 buf := make([]byte, entryLen)
224 _, err := io.ReadFull(ar.fd, buf)
225 if err == io.EOF {
226
227 return nil
228 }
229 if err != nil || buf[entryLen-2] != '`' || buf[entryLen-1] != '\n' {
230 log.Fatal("file is not an archive: bad entry")
231 }
232 entry := new(Entry)
233 entry.name = strings.TrimRight(string(buf[:16]), " ")
234 if len(entry.name) == 0 {
235 log.Fatal("file is not an archive: bad name")
236 }
237 buf = buf[16:]
238 str := string(buf)
239 get := func(width, base, bitsize int) int64 {
240 v, err := strconv.ParseInt(strings.TrimRight(str[:width], " "), base, bitsize)
241 if err != nil {
242 log.Fatal("file is not an archive: bad number in entry: ", err)
243 }
244 str = str[width:]
245 return v
246 }
247
248 entry.mtime = get(12, 10, 64)
249 entry.uid = int(get(6, 10, 32))
250 entry.gid = int(get(6, 10, 32))
251 entry.mode = os.FileMode(get(8, 8, 32))
252 entry.size = get(10, 10, 64)
253 return entry
254 }
255
256
257
258 func (ar *Archive) scan(action func(*Entry)) {
259 for {
260 entry := ar.readMetadata()
261 if entry == nil {
262 break
263 }
264 action(entry)
265 }
266 }
267
268
269 func listEntry(entry *Entry, verbose bool) {
270 if verbose {
271 fmt.Fprintf(stdout, "%s\n", entry)
272 } else {
273 fmt.Fprintf(stdout, "%s\n", entry.name)
274 }
275 }
276
277
278 func (ar *Archive) output(entry *Entry, w io.Writer) {
279 n, err := io.Copy(w, io.LimitReader(ar.fd, entry.size))
280 if err != nil {
281 log.Fatal(err)
282 }
283 if n != entry.size {
284 log.Fatal("short file")
285 }
286 if entry.size&1 == 1 {
287 _, err := ar.fd.Seek(1, io.SeekCurrent)
288 if err != nil {
289 log.Fatal(err)
290 }
291 }
292 }
293
294
295 func (ar *Archive) skip(entry *Entry) {
296 size := entry.size
297 if size&1 == 1 {
298 size++
299 }
300 _, err := ar.fd.Seek(size, io.SeekCurrent)
301 if err != nil {
302 log.Fatal(err)
303 }
304 }
305
306
307
308 func (ar *Archive) match(entry *Entry) bool {
309 if ar.matchAll {
310 return true
311 }
312 for i, name := range ar.files {
313 if entry.name == name {
314 copy(ar.files[i:], ar.files[i+1:])
315 ar.files = ar.files[:len(ar.files)-1]
316 return true
317 }
318 }
319 return false
320 }
321
322
323
324
325 func (ar *Archive) addFiles() {
326 if len(ar.files) == 0 {
327 usage()
328 }
329 for _, file := range ar.files {
330 if verbose {
331 fmt.Printf("%s\n", file)
332 }
333
334 if !isGoCompilerObjFile(file) {
335 fd, err := os.Open(file)
336 if err != nil {
337 log.Fatal(err)
338 }
339 ar.addFile(fd)
340 continue
341 }
342
343 aro := archive(file, os.O_RDONLY, nil)
344 aro.scan(func(entry *Entry) {
345 if entry.name != "_go_.o" {
346 aro.skip(entry)
347 return
348 }
349 ar.startFile(filepath.Base(file), 0, 0, 0, 0644, entry.size)
350 aro.output(entry, ar.fd)
351 ar.endFile()
352 })
353 }
354 ar.files = nil
355 }
356
357
358 type FileLike interface {
359 Name() string
360 Stat() (os.FileInfo, error)
361 Read([]byte) (int, error)
362 Close() error
363 }
364
365
366 func (ar *Archive) addFile(fd FileLike) {
367 defer fd.Close()
368
369
370 info, err := fd.Stat()
371 if err != nil {
372 log.Fatal(err)
373 }
374
375 mtime := int64(0)
376 uid := 0
377 gid := 0
378 ar.startFile(info.Name(), mtime, uid, gid, info.Mode(), info.Size())
379 n64, err := io.Copy(ar.fd, fd)
380 if err != nil {
381 log.Fatal("writing file: ", err)
382 }
383 if n64 != info.Size() {
384 log.Fatalf("writing file: wrote %d bytes; file is size %d", n64, info.Size())
385 }
386 ar.endFile()
387 }
388
389
390 func (ar *Archive) startFile(name string, mtime int64, uid, gid int, mode os.FileMode, size int64) {
391 n, err := fmt.Fprintf(ar.fd, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size)
392 if err != nil || n != entryLen {
393 log.Fatal("writing entry header: ", err)
394 }
395 ar.pad = int(size & 1)
396 }
397
398
399 func (ar *Archive) endFile() {
400 if ar.pad != 0 {
401 _, err := ar.fd.Write([]byte{0})
402 if err != nil {
403 log.Fatal("writing archive: ", err)
404 }
405 ar.pad = 0
406 }
407 }
408
409
410
411
412 func (ar *Archive) addPkgdef() {
413 done := false
414 for _, file := range ar.files {
415 if !isGoCompilerObjFile(file) {
416 continue
417 }
418 aro := archive(file, os.O_RDONLY, nil)
419 aro.scan(func(entry *Entry) {
420 if entry.name != "__.PKGDEF" {
421 aro.skip(entry)
422 return
423 }
424 if verbose {
425 fmt.Printf("__.PKGDEF # %s\n", file)
426 }
427 ar.startFile("__.PKGDEF", 0, 0, 0, 0644, entry.size)
428 aro.output(entry, ar.fd)
429 ar.endFile()
430 done = true
431 })
432 if done {
433 break
434 }
435 }
436 }
437
438
439
440
441 func exactly16Bytes(s string) string {
442 for len(s) > 16 {
443 _, wid := utf8.DecodeLastRuneInString(s)
444 s = s[:len(s)-wid]
445 }
446 const sixteenSpaces = " "
447 s += sixteenSpaces[:16-len(s)]
448 return s
449 }
450
451
452
453
454 var stdout io.Writer = os.Stdout
455
456
457 func (ar *Archive) printContents(entry *Entry) {
458 if ar.match(entry) {
459 if verbose {
460 listEntry(entry, false)
461 }
462 ar.output(entry, stdout)
463 } else {
464 ar.skip(entry)
465 }
466 }
467
468
469
470 func (ar *Archive) skipContents(entry *Entry) {
471 ar.skip(entry)
472 }
473
474
475 func (ar *Archive) tableOfContents(entry *Entry) {
476 if ar.match(entry) {
477 listEntry(entry, verbose)
478 }
479 ar.skip(entry)
480 }
481
482
483 func (ar *Archive) extractContents(entry *Entry) {
484 if ar.match(entry) {
485 if verbose {
486 listEntry(entry, false)
487 }
488 fd, err := os.OpenFile(entry.name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, entry.mode)
489 if err != nil {
490 log.Fatal(err)
491 }
492 ar.output(entry, fd)
493 fd.Close()
494 } else {
495 ar.skip(entry)
496 }
497 }
498
499
500
501 func isGoCompilerObjFile(file string) bool {
502 fd, err := os.Open(file)
503 if err != nil {
504 log.Fatal(err)
505 }
506
507
508 buf := make([]byte, len(arHeader))
509 _, err = io.ReadFull(fd, buf)
510 if err != nil {
511 if err == io.EOF {
512 return false
513 }
514 log.Fatal(err)
515 }
516 if string(buf) != arHeader {
517 return false
518 }
519
520
521 match := []string{"__.PKGDEF", "_go_.o"}
522 buf = make([]byte, entryLen)
523 for {
524 _, err := io.ReadFull(fd, buf)
525 if err != nil {
526 if err == io.EOF {
527
528 return true
529 }
530 log.Fatal(err)
531 }
532 if buf[entryLen-2] != '`' || buf[entryLen-1] != '\n' {
533 return false
534 }
535
536 name := strings.TrimRight(string(buf[:16]), " ")
537 for {
538 if len(match) == 0 {
539 return false
540 }
541 var next string
542 next, match = match[0], match[1:]
543 if name == next {
544 break
545 }
546 }
547
548 size, err := strconv.ParseInt(strings.TrimRight(string(buf[48:58]), " "), 10, 64)
549 if err != nil {
550 return false
551 }
552 if size&1 != 0 {
553 size++
554 }
555
556 _, err = fd.Seek(size, io.SeekCurrent)
557 if err != nil {
558 log.Fatal(err)
559 }
560 }
561 }
562
View as plain text