mirror of https://github.com/golang/go.git
343 lines
8.1 KiB
Go
343 lines
8.1 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"cmd/internal/archive"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
const usageMessage = `Usage: pack op file.a [name....]
|
|
Where op is one of cprtx optionally followed by v for verbose output.
|
|
For compatibility with old Go build environments the op string grc is
|
|
accepted as a synonym for c.
|
|
|
|
For more information, run
|
|
go doc cmd/pack`
|
|
|
|
func usage() {
|
|
fmt.Fprintln(os.Stderr, usageMessage)
|
|
os.Exit(2)
|
|
}
|
|
|
|
func main() {
|
|
log.SetFlags(0)
|
|
log.SetPrefix("pack: ")
|
|
// need "pack op archive" at least.
|
|
if len(os.Args) < 3 {
|
|
log.Print("not enough arguments")
|
|
fmt.Fprintln(os.Stderr)
|
|
usage()
|
|
}
|
|
setOp(os.Args[1])
|
|
var ar *Archive
|
|
switch op {
|
|
case 'p':
|
|
ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
|
|
ar.scan(ar.printContents)
|
|
case 'r':
|
|
ar = openArchive(os.Args[2], os.O_RDWR|os.O_CREATE, os.Args[3:])
|
|
ar.addFiles()
|
|
case 'c':
|
|
ar = openArchive(os.Args[2], os.O_RDWR|os.O_TRUNC|os.O_CREATE, os.Args[3:])
|
|
ar.addPkgdef()
|
|
ar.addFiles()
|
|
case 't':
|
|
ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
|
|
ar.scan(ar.tableOfContents)
|
|
case 'x':
|
|
ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
|
|
ar.scan(ar.extractContents)
|
|
default:
|
|
log.Printf("invalid operation %q", os.Args[1])
|
|
fmt.Fprintln(os.Stderr)
|
|
usage()
|
|
}
|
|
if len(ar.files) > 0 {
|
|
log.Fatalf("file %q not in archive", ar.files[0])
|
|
}
|
|
}
|
|
|
|
// The unusual ancestry means the arguments are not Go-standard.
|
|
// These variables hold the decoded operation specified by the first argument.
|
|
// op holds the operation we are doing (prtx).
|
|
// verbose tells whether the 'v' option was specified.
|
|
var (
|
|
op rune
|
|
verbose bool
|
|
)
|
|
|
|
// setOp parses the operation string (first argument).
|
|
func setOp(arg string) {
|
|
// Recognize 'go tool pack grc' because that was the
|
|
// formerly canonical way to build a new archive
|
|
// from a set of input files. Accepting it keeps old
|
|
// build systems working with both Go 1.2 and Go 1.3.
|
|
if arg == "grc" {
|
|
arg = "c"
|
|
}
|
|
|
|
for _, r := range arg {
|
|
switch r {
|
|
case 'c', 'p', 'r', 't', 'x':
|
|
if op != 0 {
|
|
// At most one can be set.
|
|
usage()
|
|
}
|
|
op = r
|
|
case 'v':
|
|
if verbose {
|
|
// Can be set only once.
|
|
usage()
|
|
}
|
|
verbose = true
|
|
default:
|
|
usage()
|
|
}
|
|
}
|
|
}
|
|
|
|
const (
|
|
arHeader = "!<arch>\n"
|
|
)
|
|
|
|
// An Archive represents an open archive file. It is always scanned sequentially
|
|
// from start to end, without backing up.
|
|
type Archive struct {
|
|
a *archive.Archive
|
|
files []string // Explicit list of files to be processed.
|
|
pad int // Padding bytes required at end of current archive file
|
|
matchAll bool // match all files in archive
|
|
}
|
|
|
|
// archive opens (and if necessary creates) the named archive.
|
|
func openArchive(name string, mode int, files []string) *Archive {
|
|
f, err := os.OpenFile(name, mode, 0666)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
var a *archive.Archive
|
|
if mode&os.O_TRUNC != 0 { // the c command
|
|
a, err = archive.New(f)
|
|
} else {
|
|
a, err = archive.Parse(f, verbose)
|
|
if err != nil && mode&os.O_CREATE != 0 { // the r command
|
|
a, err = archive.New(f)
|
|
}
|
|
}
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return &Archive{
|
|
a: a,
|
|
files: files,
|
|
matchAll: len(files) == 0,
|
|
}
|
|
}
|
|
|
|
// scan scans the archive and executes the specified action on each entry.
|
|
func (ar *Archive) scan(action func(*archive.Entry)) {
|
|
for i := range ar.a.Entries {
|
|
e := &ar.a.Entries[i]
|
|
action(e)
|
|
}
|
|
}
|
|
|
|
// listEntry prints to standard output a line describing the entry.
|
|
func listEntry(e *archive.Entry, verbose bool) {
|
|
if verbose {
|
|
fmt.Fprintf(stdout, "%s\n", e.String())
|
|
} else {
|
|
fmt.Fprintf(stdout, "%s\n", e.Name)
|
|
}
|
|
}
|
|
|
|
// output copies the entry to the specified writer.
|
|
func (ar *Archive) output(e *archive.Entry, w io.Writer) {
|
|
r := io.NewSectionReader(ar.a.File(), e.Offset, e.Size)
|
|
n, err := io.Copy(w, r)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if n != e.Size {
|
|
log.Fatal("short file")
|
|
}
|
|
}
|
|
|
|
// match reports whether the entry matches the argument list.
|
|
// If it does, it also drops the file from the to-be-processed list.
|
|
func (ar *Archive) match(e *archive.Entry) bool {
|
|
if ar.matchAll {
|
|
return true
|
|
}
|
|
for i, name := range ar.files {
|
|
if e.Name == name {
|
|
copy(ar.files[i:], ar.files[i+1:])
|
|
ar.files = ar.files[:len(ar.files)-1]
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// addFiles adds files to the archive. The archive is known to be
|
|
// sane and we are positioned at the end. No attempt is made
|
|
// to check for existing files.
|
|
func (ar *Archive) addFiles() {
|
|
if len(ar.files) == 0 {
|
|
usage()
|
|
}
|
|
for _, file := range ar.files {
|
|
if verbose {
|
|
fmt.Printf("%s\n", file)
|
|
}
|
|
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
aro, err := archive.Parse(f, false)
|
|
if err != nil || !isGoCompilerObjFile(aro) {
|
|
f.Seek(0, io.SeekStart)
|
|
ar.addFile(f)
|
|
goto close
|
|
}
|
|
|
|
for _, e := range aro.Entries {
|
|
if e.Type != archive.EntryGoObj || e.Name != "_go_.o" {
|
|
continue
|
|
}
|
|
ar.a.AddEntry(archive.EntryGoObj, filepath.Base(file), 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size))
|
|
}
|
|
close:
|
|
f.Close()
|
|
}
|
|
ar.files = nil
|
|
}
|
|
|
|
// FileLike abstracts the few methods we need, so we can test without needing real files.
|
|
type FileLike interface {
|
|
Name() string
|
|
Stat() (fs.FileInfo, error)
|
|
Read([]byte) (int, error)
|
|
Close() error
|
|
}
|
|
|
|
// addFile adds a single file to the archive
|
|
func (ar *Archive) addFile(fd FileLike) {
|
|
// Format the entry.
|
|
// First, get its info.
|
|
info, err := fd.Stat()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// mtime, uid, gid are all zero so repeated builds produce identical output.
|
|
mtime := int64(0)
|
|
uid := 0
|
|
gid := 0
|
|
ar.a.AddEntry(archive.EntryNativeObj, info.Name(), mtime, uid, gid, info.Mode(), info.Size(), fd)
|
|
}
|
|
|
|
// addPkgdef adds the __.PKGDEF file to the archive, copied
|
|
// from the first Go object file on the file list, if any.
|
|
// The archive is known to be empty.
|
|
func (ar *Archive) addPkgdef() {
|
|
done := false
|
|
for _, file := range ar.files {
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
aro, err := archive.Parse(f, false)
|
|
if err != nil || !isGoCompilerObjFile(aro) {
|
|
goto close
|
|
}
|
|
|
|
for _, e := range aro.Entries {
|
|
if e.Type != archive.EntryPkgDef {
|
|
continue
|
|
}
|
|
if verbose {
|
|
fmt.Printf("__.PKGDEF # %s\n", file)
|
|
}
|
|
ar.a.AddEntry(archive.EntryPkgDef, "__.PKGDEF", 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size))
|
|
done = true
|
|
}
|
|
close:
|
|
f.Close()
|
|
if done {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally, the actual commands. Each is an action.
|
|
|
|
// can be modified for testing.
|
|
var stdout io.Writer = os.Stdout
|
|
|
|
// printContents implements the 'p' command.
|
|
func (ar *Archive) printContents(e *archive.Entry) {
|
|
ar.extractContents1(e, stdout)
|
|
}
|
|
|
|
// tableOfContents implements the 't' command.
|
|
func (ar *Archive) tableOfContents(e *archive.Entry) {
|
|
if ar.match(e) {
|
|
listEntry(e, verbose)
|
|
}
|
|
}
|
|
|
|
// extractContents implements the 'x' command.
|
|
func (ar *Archive) extractContents(e *archive.Entry) {
|
|
ar.extractContents1(e, nil)
|
|
}
|
|
|
|
func (ar *Archive) extractContents1(e *archive.Entry, out io.Writer) {
|
|
if ar.match(e) {
|
|
if verbose {
|
|
listEntry(e, false)
|
|
}
|
|
if out == nil {
|
|
f, err := os.OpenFile(e.Name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0444 /*e.Mode*/)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
out = f
|
|
}
|
|
ar.output(e, out)
|
|
}
|
|
}
|
|
|
|
// isGoCompilerObjFile reports whether file is an object file created
|
|
// by the Go compiler, which is an archive file with exactly one entry
|
|
// of __.PKGDEF, or _go_.o, or both entries.
|
|
func isGoCompilerObjFile(a *archive.Archive) bool {
|
|
switch len(a.Entries) {
|
|
case 1:
|
|
return (a.Entries[0].Type == archive.EntryGoObj && a.Entries[0].Name == "_go_.o") ||
|
|
(a.Entries[0].Type == archive.EntryPkgDef && a.Entries[0].Name == "__.PKGDEF")
|
|
case 2:
|
|
var foundPkgDef, foundGo bool
|
|
for _, e := range a.Entries {
|
|
if e.Type == archive.EntryPkgDef && e.Name == "__.PKGDEF" {
|
|
foundPkgDef = true
|
|
}
|
|
if e.Type == archive.EntryGoObj && e.Name == "_go_.o" {
|
|
foundGo = true
|
|
}
|
|
}
|
|
return foundPkgDef && foundGo
|
|
default:
|
|
return false
|
|
}
|
|
}
|