mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
[dev.link] cmd/pack: use cmd/internal/archive package
Rewrite part of cmd/pack to use the cmd/internal/archive package. Change-Id: Ia7688810d3ea4d0277056870091f59cf09cffcad Reviewed-on: https://go-review.googlesource.com/c/go/+/247917 Run-TryBot: Cherry Zhang <cherryyz@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
parent
f900d6014e
commit
3a185d7468
5 changed files with 297 additions and 390 deletions
|
|
@ -14,10 +14,29 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
The archive format is:
|
||||||
|
|
||||||
|
First, on a line by itself
|
||||||
|
!<arch>
|
||||||
|
|
||||||
|
Then zero or more file records. Each file record has a fixed-size one-line header
|
||||||
|
followed by data bytes followed by an optional padding byte. The header is:
|
||||||
|
|
||||||
|
%-16s%-12d%-6d%-6d%-8o%-10d`
|
||||||
|
name mtime uid gid mode size
|
||||||
|
|
||||||
|
(note the trailing backquote). The %-16s here means at most 16 *bytes* of
|
||||||
|
the name, and if shorter, space padded on the right.
|
||||||
|
*/
|
||||||
|
|
||||||
// A Data is a reference to data stored in an object file.
|
// A Data is a reference to data stored in an object file.
|
||||||
// It records the offset and size of the data, so that a client can
|
// It records the offset and size of the data, so that a client can
|
||||||
// read the data only if necessary.
|
// read the data only if necessary.
|
||||||
|
|
@ -31,9 +50,15 @@ type Archive struct {
|
||||||
Entries []Entry
|
Entries []Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Archive) File() *os.File { return a.f }
|
||||||
|
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
Name string
|
Name string
|
||||||
Type EntryType
|
Type EntryType
|
||||||
|
Mtime int64
|
||||||
|
Uid int
|
||||||
|
Gid int
|
||||||
|
Mode os.FileMode
|
||||||
Data
|
Data
|
||||||
Obj *GoObj // nil if this entry is not a Go object file
|
Obj *GoObj // nil if this entry is not a Go object file
|
||||||
}
|
}
|
||||||
|
|
@ -46,11 +71,28 @@ const (
|
||||||
EntryNativeObj
|
EntryNativeObj
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (e *Entry) String() string {
|
||||||
|
return fmt.Sprintf("%s %6d/%-6d %12d %s %s",
|
||||||
|
(e.Mode & 0777).String(),
|
||||||
|
e.Uid,
|
||||||
|
e.Gid,
|
||||||
|
e.Size,
|
||||||
|
time.Unix(e.Mtime, 0).Format(timeFormat),
|
||||||
|
e.Name)
|
||||||
|
}
|
||||||
|
|
||||||
type GoObj struct {
|
type GoObj struct {
|
||||||
TextHeader []byte
|
TextHeader []byte
|
||||||
Data
|
Data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
entryHeader = "%s%-12d%-6d%-6d%-8o%-10d`\n"
|
||||||
|
// In entryHeader the first entry, the name, is always printed as 16 bytes right-padded.
|
||||||
|
entryLen = 16 + 12 + 6 + 6 + 8 + 10 + 1 + 1
|
||||||
|
timeFormat = "Jan _2 15:04 2006"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
archiveHeader = []byte("!<arch>\n")
|
archiveHeader = []byte("!<arch>\n")
|
||||||
archiveMagic = []byte("`\n")
|
archiveMagic = []byte("`\n")
|
||||||
|
|
@ -182,8 +224,17 @@ func (r *objReader) skip(n int64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New writes to f to make a new archive.
|
||||||
|
func New(f *os.File) (*Archive, error) {
|
||||||
|
_, err := f.Write(archiveHeader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Archive{f: f}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Parse parses an object file or archive from f.
|
// Parse parses an object file or archive from f.
|
||||||
func Parse(f *os.File) (*Archive, error) {
|
func Parse(f *os.File, verbose bool) (*Archive, error) {
|
||||||
var r objReader
|
var r objReader
|
||||||
r.init(f)
|
r.init(f)
|
||||||
t, err := r.peek(8)
|
t, err := r.peek(8)
|
||||||
|
|
@ -199,7 +250,7 @@ func Parse(f *os.File) (*Archive, error) {
|
||||||
return nil, errNotObject
|
return nil, errNotObject
|
||||||
|
|
||||||
case bytes.Equal(t, archiveHeader):
|
case bytes.Equal(t, archiveHeader):
|
||||||
if err := r.parseArchive(); err != nil {
|
if err := r.parseArchive(verbose); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case bytes.Equal(t, goobjHeader):
|
case bytes.Equal(t, goobjHeader):
|
||||||
|
|
@ -208,7 +259,12 @@ func Parse(f *os.File) (*Archive, error) {
|
||||||
if err := r.parseObject(o, r.limit-off); err != nil {
|
if err := r.parseObject(o, r.limit-off); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
r.a.Entries = []Entry{{f.Name(), EntryGoObj, Data{off, r.limit - off}, o}}
|
r.a.Entries = []Entry{{
|
||||||
|
Name: f.Name(),
|
||||||
|
Type: EntryGoObj,
|
||||||
|
Data: Data{off, r.limit - off},
|
||||||
|
Obj: o,
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.a, nil
|
return r.a, nil
|
||||||
|
|
@ -221,7 +277,7 @@ func trimSpace(b []byte) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseArchive parses a Unix archive of Go object files.
|
// parseArchive parses a Unix archive of Go object files.
|
||||||
func (r *objReader) parseArchive() error {
|
func (r *objReader) parseArchive(verbose bool) error {
|
||||||
r.readFull(r.tmp[:8]) // consume header (already checked)
|
r.readFull(r.tmp[:8]) // consume header (already checked)
|
||||||
for r.offset < r.limit {
|
for r.offset < r.limit {
|
||||||
if err := r.readFull(r.tmp[:60]); err != nil {
|
if err := r.readFull(r.tmp[:60]); err != nil {
|
||||||
|
|
@ -237,7 +293,7 @@ func (r *objReader) parseArchive() error {
|
||||||
// 40:48 mode
|
// 40:48 mode
|
||||||
// 48:58 size
|
// 48:58 size
|
||||||
// 58:60 magic - `\n
|
// 58:60 magic - `\n
|
||||||
// We only care about name, size, and magic.
|
// We only care about name, size, and magic, unless in verbose mode.
|
||||||
// The fields are space-padded on the right.
|
// The fields are space-padded on the right.
|
||||||
// The size is in decimal.
|
// The size is in decimal.
|
||||||
// The file data - size bytes - follows the header.
|
// The file data - size bytes - follows the header.
|
||||||
|
|
@ -252,7 +308,27 @@ func (r *objReader) parseArchive() error {
|
||||||
return errCorruptArchive
|
return errCorruptArchive
|
||||||
}
|
}
|
||||||
name := trimSpace(data[0:16])
|
name := trimSpace(data[0:16])
|
||||||
size, err := strconv.ParseInt(trimSpace(data[48:58]), 10, 64)
|
var err error
|
||||||
|
get := func(start, end, base, bitsize int) int64 {
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var v int64
|
||||||
|
v, err = strconv.ParseInt(trimSpace(data[start:end]), base, bitsize)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
size := get(48, 58, 10, 64)
|
||||||
|
var (
|
||||||
|
mtime int64
|
||||||
|
uid, gid int
|
||||||
|
mode os.FileMode
|
||||||
|
)
|
||||||
|
if verbose {
|
||||||
|
mtime = get(16, 28, 10, 64)
|
||||||
|
uid = int(get(28, 34, 10, 32))
|
||||||
|
gid = int(get(34, 40, 10, 32))
|
||||||
|
mode = os.FileMode(get(40, 48, 8, 32))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errCorruptArchive
|
return errCorruptArchive
|
||||||
}
|
}
|
||||||
|
|
@ -263,7 +339,15 @@ func (r *objReader) parseArchive() error {
|
||||||
}
|
}
|
||||||
switch name {
|
switch name {
|
||||||
case "__.PKGDEF":
|
case "__.PKGDEF":
|
||||||
r.a.Entries = append(r.a.Entries, Entry{name, EntryPkgDef, Data{r.offset, size}, nil})
|
r.a.Entries = append(r.a.Entries, Entry{
|
||||||
|
Name: name,
|
||||||
|
Type: EntryPkgDef,
|
||||||
|
Mtime: mtime,
|
||||||
|
Uid: uid,
|
||||||
|
Gid: gid,
|
||||||
|
Mode: mode,
|
||||||
|
Data: Data{r.offset, size},
|
||||||
|
})
|
||||||
r.skip(size)
|
r.skip(size)
|
||||||
default:
|
default:
|
||||||
var typ EntryType
|
var typ EntryType
|
||||||
|
|
@ -281,7 +365,16 @@ func (r *objReader) parseArchive() error {
|
||||||
typ = EntryNativeObj
|
typ = EntryNativeObj
|
||||||
r.skip(size)
|
r.skip(size)
|
||||||
}
|
}
|
||||||
r.a.Entries = append(r.a.Entries, Entry{name, typ, Data{offset, size}, o})
|
r.a.Entries = append(r.a.Entries, Entry{
|
||||||
|
Name: name,
|
||||||
|
Type: typ,
|
||||||
|
Mtime: mtime,
|
||||||
|
Uid: uid,
|
||||||
|
Gid: gid,
|
||||||
|
Mode: mode,
|
||||||
|
Data: Data{offset, size},
|
||||||
|
Obj: o,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if size&1 != 0 {
|
if size&1 != 0 {
|
||||||
r.skip(1)
|
r.skip(1)
|
||||||
|
|
@ -324,3 +417,44 @@ func (r *objReader) parseObject(o *GoObj, size int64) error {
|
||||||
r.skip(o.Size)
|
r.skip(o.Size)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddEntry adds an entry to the end of a, with the content from r.
|
||||||
|
func (a *Archive) AddEntry(typ EntryType, name string, mtime int64, uid, gid int, mode os.FileMode, size int64, r io.Reader) {
|
||||||
|
off, err := a.f.Seek(0, io.SeekEnd)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
n, err := fmt.Fprintf(a.f, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size)
|
||||||
|
if err != nil || n != entryLen {
|
||||||
|
log.Fatal("writing entry header: ", err)
|
||||||
|
}
|
||||||
|
n1, _ := io.CopyN(a.f, r, size)
|
||||||
|
if n1 != size {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if (off+size)&1 != 0 {
|
||||||
|
a.f.Write([]byte{0}) // pad to even byte
|
||||||
|
}
|
||||||
|
a.Entries = append(a.Entries, Entry{
|
||||||
|
Name: name,
|
||||||
|
Type: typ,
|
||||||
|
Mtime: mtime,
|
||||||
|
Uid: uid,
|
||||||
|
Gid: gid,
|
||||||
|
Mode: mode,
|
||||||
|
Data: Data{off + entryLen, size},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// exactly16Bytes truncates the string if necessary so it is at most 16 bytes long,
|
||||||
|
// then pads the result with spaces to be exactly 16 bytes.
|
||||||
|
// Fmt uses runes for its width calculation, but we need bytes in the entry header.
|
||||||
|
func exactly16Bytes(s string) string {
|
||||||
|
for len(s) > 16 {
|
||||||
|
_, wid := utf8.DecodeLastRuneInString(s)
|
||||||
|
s = s[:len(s)-wid]
|
||||||
|
}
|
||||||
|
const sixteenSpaces = " "
|
||||||
|
s += sixteenSpaces[:16-len(s)]
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -160,7 +161,7 @@ func TestParseGoobj(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
a, err := Parse(f)
|
a, err := Parse(f, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +190,7 @@ func TestParseArchive(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
a, err := Parse(f)
|
a, err := Parse(f, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -234,7 +235,7 @@ func TestParseCGOArchive(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
a, err := Parse(f)
|
a, err := Parse(f, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -346,3 +347,30 @@ func TestParseCGOArchive(t *testing.T) {
|
||||||
t.Errorf(`symbol %q not found`, c2)
|
t.Errorf(`symbol %q not found`, c2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExactly16Bytes(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
"",
|
||||||
|
"a",
|
||||||
|
"日本語",
|
||||||
|
"1234567890123456",
|
||||||
|
"12345678901234567890",
|
||||||
|
"1234567890123本語4567890",
|
||||||
|
"12345678901234日本語567890",
|
||||||
|
"123456789012345日本語67890",
|
||||||
|
"1234567890123456日本語7890",
|
||||||
|
"1234567890123456日本語7日本語890",
|
||||||
|
}
|
||||||
|
for _, str := range tests {
|
||||||
|
got := exactly16Bytes(str)
|
||||||
|
if len(got) != 16 {
|
||||||
|
t.Errorf("exactly16Bytes(%q) is %q, length %d", str, got, len(got))
|
||||||
|
}
|
||||||
|
// Make sure it is full runes.
|
||||||
|
for _, c := range got {
|
||||||
|
if c == utf8.RuneError {
|
||||||
|
t.Errorf("exactly16Bytes(%q) is %q, has partial rune", str, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ type goobjFile struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func openGoFile(f *os.File) (*File, error) {
|
func openGoFile(f *os.File) (*File, error) {
|
||||||
a, err := archive.Parse(f)
|
a, err := archive.Parse(f, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,33 +5,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmd/internal/archive"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
|
||||||
The archive format is:
|
|
||||||
|
|
||||||
First, on a line by itself
|
|
||||||
!<arch>
|
|
||||||
|
|
||||||
Then zero or more file records. Each file record has a fixed-size one-line header
|
|
||||||
followed by data bytes followed by an optional padding byte. The header is:
|
|
||||||
|
|
||||||
%-16s%-12d%-6d%-6d%-8o%-10d`
|
|
||||||
name mtime uid gid mode size
|
|
||||||
|
|
||||||
(note the trailing backquote). The %-16s here means at most 16 *bytes* of
|
|
||||||
the name, and if shorter, space padded on the right.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const usageMessage = `Usage: pack op file.a [name....]
|
const usageMessage = `Usage: pack op file.a [name....]
|
||||||
Where op is one of cprtx optionally followed by v for verbose output.
|
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
|
For compatibility with old Go build environments the op string grc is
|
||||||
|
|
@ -58,21 +39,20 @@ func main() {
|
||||||
var ar *Archive
|
var ar *Archive
|
||||||
switch op {
|
switch op {
|
||||||
case 'p':
|
case 'p':
|
||||||
ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
|
ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
|
||||||
ar.scan(ar.printContents)
|
ar.scan(ar.printContents)
|
||||||
case 'r':
|
case 'r':
|
||||||
ar = archive(os.Args[2], os.O_RDWR, os.Args[3:])
|
ar = openArchive(os.Args[2], os.O_RDWR, os.Args[3:])
|
||||||
ar.scan(ar.skipContents)
|
|
||||||
ar.addFiles()
|
ar.addFiles()
|
||||||
case 'c':
|
case 'c':
|
||||||
ar = archive(os.Args[2], os.O_RDWR|os.O_TRUNC, os.Args[3:])
|
ar = openArchive(os.Args[2], os.O_RDWR|os.O_TRUNC|os.O_CREATE, os.Args[3:])
|
||||||
ar.addPkgdef()
|
ar.addPkgdef()
|
||||||
ar.addFiles()
|
ar.addFiles()
|
||||||
case 't':
|
case 't':
|
||||||
ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
|
ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
|
||||||
ar.scan(ar.tableOfContents)
|
ar.scan(ar.tableOfContents)
|
||||||
case 'x':
|
case 'x':
|
||||||
ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
|
ar = openArchive(os.Args[2], os.O_RDONLY, os.Args[3:])
|
||||||
ar.scan(ar.extractContents)
|
ar.scan(ar.extractContents)
|
||||||
default:
|
default:
|
||||||
log.Printf("invalid operation %q", os.Args[1])
|
log.Printf("invalid operation %q", os.Args[1])
|
||||||
|
|
@ -125,192 +105,76 @@ func setOp(arg string) {
|
||||||
|
|
||||||
const (
|
const (
|
||||||
arHeader = "!<arch>\n"
|
arHeader = "!<arch>\n"
|
||||||
entryHeader = "%s%-12d%-6d%-6d%-8o%-10d`\n"
|
|
||||||
// In entryHeader the first entry, the name, is always printed as 16 bytes right-padded.
|
|
||||||
entryLen = 16 + 12 + 6 + 6 + 8 + 10 + 1 + 1
|
|
||||||
timeFormat = "Jan _2 15:04 2006"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// An Archive represents an open archive file. It is always scanned sequentially
|
// An Archive represents an open archive file. It is always scanned sequentially
|
||||||
// from start to end, without backing up.
|
// from start to end, without backing up.
|
||||||
type Archive struct {
|
type Archive struct {
|
||||||
fd *os.File // Open file descriptor.
|
a *archive.Archive
|
||||||
files []string // Explicit list of files to be processed.
|
files []string // Explicit list of files to be processed.
|
||||||
pad int // Padding bytes required at end of current archive file
|
pad int // Padding bytes required at end of current archive file
|
||||||
matchAll bool // match all files in archive
|
matchAll bool // match all files in archive
|
||||||
}
|
}
|
||||||
|
|
||||||
// archive opens (and if necessary creates) the named archive.
|
// archive opens (and if necessary creates) the named archive.
|
||||||
func archive(name string, mode int, files []string) *Archive {
|
func openArchive(name string, mode int, files []string) *Archive {
|
||||||
// If the file exists, it must be an archive. If it doesn't exist, or if
|
f, err := os.OpenFile(name, mode, 0666)
|
||||||
// we're doing the c command, indicated by O_TRUNC, truncate the archive.
|
if err != nil {
|
||||||
if !existingArchive(name) || mode&os.O_TRUNC != 0 {
|
log.Fatal(err)
|
||||||
create(name)
|
}
|
||||||
mode &^= os.O_TRUNC
|
var a *archive.Archive
|
||||||
}
|
if mode&os.O_CREATE != 0 { // the c command
|
||||||
fd, err := os.OpenFile(name, mode, 0)
|
a, err = archive.New(f)
|
||||||
|
} else {
|
||||||
|
a, err = archive.Parse(f, verbose)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
checkHeader(fd)
|
|
||||||
return &Archive{
|
return &Archive{
|
||||||
fd: fd,
|
a: a,
|
||||||
files: files,
|
files: files,
|
||||||
matchAll: len(files) == 0,
|
matchAll: len(files) == 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create creates and initializes an archive that does not exist.
|
|
||||||
func create(name string) {
|
|
||||||
fd, err := os.Create(name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = fmt.Fprint(fd, arHeader)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// existingArchive reports whether the file exists and is a valid archive.
|
|
||||||
// If it exists but is not an archive, existingArchive will exit.
|
|
||||||
func existingArchive(name string) bool {
|
|
||||||
fd, err := os.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
log.Fatalf("cannot open file: %s", err)
|
|
||||||
}
|
|
||||||
checkHeader(fd)
|
|
||||||
fd.Close()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkHeader verifies the header of the file. It assumes the file
|
|
||||||
// is positioned at 0 and leaves it positioned at the end of the header.
|
|
||||||
func checkHeader(fd *os.File) {
|
|
||||||
buf := make([]byte, len(arHeader))
|
|
||||||
_, err := io.ReadFull(fd, buf)
|
|
||||||
if err != nil || string(buf) != arHeader {
|
|
||||||
log.Fatalf("%s is not an archive: bad header", fd.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Entry is the internal representation of the per-file header information of one entry in the archive.
|
|
||||||
type Entry struct {
|
|
||||||
name string
|
|
||||||
mtime int64
|
|
||||||
uid int
|
|
||||||
gid int
|
|
||||||
mode os.FileMode
|
|
||||||
size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) String() string {
|
|
||||||
return fmt.Sprintf("%s %6d/%-6d %12d %s %s",
|
|
||||||
(e.mode & 0777).String(),
|
|
||||||
e.uid,
|
|
||||||
e.gid,
|
|
||||||
e.size,
|
|
||||||
time.Unix(e.mtime, 0).Format(timeFormat),
|
|
||||||
e.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readMetadata reads and parses the metadata for the next entry in the archive.
|
|
||||||
func (ar *Archive) readMetadata() *Entry {
|
|
||||||
buf := make([]byte, entryLen)
|
|
||||||
_, err := io.ReadFull(ar.fd, buf)
|
|
||||||
if err == io.EOF {
|
|
||||||
// No entries left.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil || buf[entryLen-2] != '`' || buf[entryLen-1] != '\n' {
|
|
||||||
log.Fatal("file is not an archive: bad entry")
|
|
||||||
}
|
|
||||||
entry := new(Entry)
|
|
||||||
entry.name = strings.TrimRight(string(buf[:16]), " ")
|
|
||||||
if len(entry.name) == 0 {
|
|
||||||
log.Fatal("file is not an archive: bad name")
|
|
||||||
}
|
|
||||||
buf = buf[16:]
|
|
||||||
str := string(buf)
|
|
||||||
get := func(width, base, bitsize int) int64 {
|
|
||||||
v, err := strconv.ParseInt(strings.TrimRight(str[:width], " "), base, bitsize)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("file is not an archive: bad number in entry: ", err)
|
|
||||||
}
|
|
||||||
str = str[width:]
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
// %-16s%-12d%-6d%-6d%-8o%-10d`
|
|
||||||
entry.mtime = get(12, 10, 64)
|
|
||||||
entry.uid = int(get(6, 10, 32))
|
|
||||||
entry.gid = int(get(6, 10, 32))
|
|
||||||
entry.mode = os.FileMode(get(8, 8, 32))
|
|
||||||
entry.size = get(10, 10, 64)
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
|
|
||||||
// scan scans the archive and executes the specified action on each entry.
|
// scan scans the archive and executes the specified action on each entry.
|
||||||
// When action returns, the file offset is at the start of the next entry.
|
func (ar *Archive) scan(action func(*archive.Entry)) {
|
||||||
func (ar *Archive) scan(action func(*Entry)) {
|
for i := range ar.a.Entries {
|
||||||
for {
|
e := &ar.a.Entries[i]
|
||||||
entry := ar.readMetadata()
|
action(e)
|
||||||
if entry == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
action(entry)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// listEntry prints to standard output a line describing the entry.
|
// listEntry prints to standard output a line describing the entry.
|
||||||
func listEntry(entry *Entry, verbose bool) {
|
func listEntry(e *archive.Entry, verbose bool) {
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Fprintf(stdout, "%s\n", entry)
|
fmt.Fprintf(stdout, "%s\n", e.String())
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(stdout, "%s\n", entry.name)
|
fmt.Fprintf(stdout, "%s\n", e.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// output copies the entry to the specified writer.
|
// output copies the entry to the specified writer.
|
||||||
func (ar *Archive) output(entry *Entry, w io.Writer) {
|
func (ar *Archive) output(e *archive.Entry, w io.Writer) {
|
||||||
n, err := io.Copy(w, io.LimitReader(ar.fd, entry.size))
|
r := io.NewSectionReader(ar.a.File(), e.Offset, e.Size)
|
||||||
|
n, err := io.Copy(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if n != entry.size {
|
if n != e.Size {
|
||||||
log.Fatal("short file")
|
log.Fatal("short file")
|
||||||
}
|
}
|
||||||
if entry.size&1 == 1 {
|
|
||||||
_, err := ar.fd.Seek(1, io.SeekCurrent)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip skips the entry without reading it.
|
|
||||||
func (ar *Archive) skip(entry *Entry) {
|
|
||||||
size := entry.size
|
|
||||||
if size&1 == 1 {
|
|
||||||
size++
|
|
||||||
}
|
|
||||||
_, err := ar.fd.Seek(size, io.SeekCurrent)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// match reports whether the entry matches the argument list.
|
// match reports whether the entry matches the argument list.
|
||||||
// If it does, it also drops the file from the to-be-processed list.
|
// If it does, it also drops the file from the to-be-processed list.
|
||||||
func (ar *Archive) match(entry *Entry) bool {
|
func (ar *Archive) match(e *archive.Entry) bool {
|
||||||
if ar.matchAll {
|
if ar.matchAll {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for i, name := range ar.files {
|
for i, name := range ar.files {
|
||||||
if entry.name == name {
|
if e.Name == name {
|
||||||
copy(ar.files[i:], ar.files[i+1:])
|
copy(ar.files[i:], ar.files[i+1:])
|
||||||
ar.files = ar.files[:len(ar.files)-1]
|
ar.files = ar.files[:len(ar.files)-1]
|
||||||
return true
|
return true
|
||||||
|
|
@ -331,25 +195,25 @@ func (ar *Archive) addFiles() {
|
||||||
fmt.Printf("%s\n", file)
|
fmt.Printf("%s\n", file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isGoCompilerObjFile(file) {
|
f, err := os.Open(file)
|
||||||
fd, err := os.Open(file)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
ar.addFile(fd)
|
aro, err := archive.Parse(f, false)
|
||||||
continue
|
if err != nil || !isGoCompilerObjFile(aro) {
|
||||||
|
f.Seek(0, io.SeekStart)
|
||||||
|
ar.addFile(f)
|
||||||
|
goto close
|
||||||
}
|
}
|
||||||
|
|
||||||
aro := archive(file, os.O_RDONLY, nil)
|
for _, e := range aro.Entries {
|
||||||
aro.scan(func(entry *Entry) {
|
if e.Type != archive.EntryGoObj || e.Name != "_go_.o" {
|
||||||
if entry.name != "_go_.o" {
|
continue
|
||||||
aro.skip(entry)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
ar.startFile(filepath.Base(file), 0, 0, 0, 0644, entry.size)
|
ar.a.AddEntry(archive.EntryGoObj, filepath.Base(file), 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size))
|
||||||
aro.output(entry, ar.fd)
|
}
|
||||||
ar.endFile()
|
close:
|
||||||
})
|
f.Close()
|
||||||
}
|
}
|
||||||
ar.files = nil
|
ar.files = nil
|
||||||
}
|
}
|
||||||
|
|
@ -364,7 +228,6 @@ type FileLike interface {
|
||||||
|
|
||||||
// addFile adds a single file to the archive
|
// addFile adds a single file to the archive
|
||||||
func (ar *Archive) addFile(fd FileLike) {
|
func (ar *Archive) addFile(fd FileLike) {
|
||||||
defer fd.Close()
|
|
||||||
// Format the entry.
|
// Format the entry.
|
||||||
// First, get its info.
|
// First, get its info.
|
||||||
info, err := fd.Stat()
|
info, err := fd.Stat()
|
||||||
|
|
@ -375,35 +238,7 @@ func (ar *Archive) addFile(fd FileLike) {
|
||||||
mtime := int64(0)
|
mtime := int64(0)
|
||||||
uid := 0
|
uid := 0
|
||||||
gid := 0
|
gid := 0
|
||||||
ar.startFile(info.Name(), mtime, uid, gid, info.Mode(), info.Size())
|
ar.a.AddEntry(archive.EntryNativeObj, info.Name(), mtime, uid, gid, info.Mode(), info.Size(), fd)
|
||||||
n64, err := io.Copy(ar.fd, fd)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("writing file: ", err)
|
|
||||||
}
|
|
||||||
if n64 != info.Size() {
|
|
||||||
log.Fatalf("writing file: wrote %d bytes; file is size %d", n64, info.Size())
|
|
||||||
}
|
|
||||||
ar.endFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
// startFile writes the archive entry header.
|
|
||||||
func (ar *Archive) startFile(name string, mtime int64, uid, gid int, mode os.FileMode, size int64) {
|
|
||||||
n, err := fmt.Fprintf(ar.fd, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size)
|
|
||||||
if err != nil || n != entryLen {
|
|
||||||
log.Fatal("writing entry header: ", err)
|
|
||||||
}
|
|
||||||
ar.pad = int(size & 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// endFile writes the archive entry tail (a single byte of padding, if the file size was odd).
|
|
||||||
func (ar *Archive) endFile() {
|
|
||||||
if ar.pad != 0 {
|
|
||||||
_, err := ar.fd.Write([]byte{0})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("writing archive: ", err)
|
|
||||||
}
|
|
||||||
ar.pad = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// addPkgdef adds the __.PKGDEF file to the archive, copied
|
// addPkgdef adds the __.PKGDEF file to the archive, copied
|
||||||
|
|
@ -412,150 +247,87 @@ func (ar *Archive) endFile() {
|
||||||
func (ar *Archive) addPkgdef() {
|
func (ar *Archive) addPkgdef() {
|
||||||
done := false
|
done := false
|
||||||
for _, file := range ar.files {
|
for _, file := range ar.files {
|
||||||
if !isGoCompilerObjFile(file) {
|
f, err := os.Open(file)
|
||||||
continue
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
aro := archive(file, os.O_RDONLY, nil)
|
aro, err := archive.Parse(f, false)
|
||||||
aro.scan(func(entry *Entry) {
|
if err != nil || !isGoCompilerObjFile(aro) {
|
||||||
if entry.name != "__.PKGDEF" {
|
goto close
|
||||||
aro.skip(entry)
|
}
|
||||||
return
|
|
||||||
|
for _, e := range aro.Entries {
|
||||||
|
if e.Type != archive.EntryPkgDef {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("__.PKGDEF # %s\n", file)
|
fmt.Printf("__.PKGDEF # %s\n", file)
|
||||||
}
|
}
|
||||||
ar.startFile("__.PKGDEF", 0, 0, 0, 0644, entry.size)
|
ar.a.AddEntry(archive.EntryPkgDef, "__.PKGDEF", 0, 0, 0, 0644, e.Size, io.NewSectionReader(f, e.Offset, e.Size))
|
||||||
aro.output(entry, ar.fd)
|
|
||||||
ar.endFile()
|
|
||||||
done = true
|
done = true
|
||||||
})
|
}
|
||||||
|
close:
|
||||||
|
f.Close()
|
||||||
if done {
|
if done {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// exactly16Bytes truncates the string if necessary so it is at most 16 bytes long,
|
|
||||||
// then pads the result with spaces to be exactly 16 bytes.
|
|
||||||
// Fmt uses runes for its width calculation, but we need bytes in the entry header.
|
|
||||||
func exactly16Bytes(s string) string {
|
|
||||||
for len(s) > 16 {
|
|
||||||
_, wid := utf8.DecodeLastRuneInString(s)
|
|
||||||
s = s[:len(s)-wid]
|
|
||||||
}
|
|
||||||
const sixteenSpaces = " "
|
|
||||||
s += sixteenSpaces[:16-len(s)]
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, the actual commands. Each is an action.
|
// Finally, the actual commands. Each is an action.
|
||||||
|
|
||||||
// can be modified for testing.
|
// can be modified for testing.
|
||||||
var stdout io.Writer = os.Stdout
|
var stdout io.Writer = os.Stdout
|
||||||
|
|
||||||
// printContents implements the 'p' command.
|
// printContents implements the 'p' command.
|
||||||
func (ar *Archive) printContents(entry *Entry) {
|
func (ar *Archive) printContents(e *archive.Entry) {
|
||||||
if ar.match(entry) {
|
ar.extractContents1(e, stdout)
|
||||||
if verbose {
|
|
||||||
listEntry(entry, false)
|
|
||||||
}
|
|
||||||
ar.output(entry, stdout)
|
|
||||||
} else {
|
|
||||||
ar.skip(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skipContents implements the first part of the 'r' command.
|
|
||||||
// It just scans the archive to make sure it's intact.
|
|
||||||
func (ar *Archive) skipContents(entry *Entry) {
|
|
||||||
ar.skip(entry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// tableOfContents implements the 't' command.
|
// tableOfContents implements the 't' command.
|
||||||
func (ar *Archive) tableOfContents(entry *Entry) {
|
func (ar *Archive) tableOfContents(e *archive.Entry) {
|
||||||
if ar.match(entry) {
|
if ar.match(e) {
|
||||||
listEntry(entry, verbose)
|
listEntry(e, verbose)
|
||||||
}
|
}
|
||||||
ar.skip(entry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractContents implements the 'x' command.
|
// extractContents implements the 'x' command.
|
||||||
func (ar *Archive) extractContents(entry *Entry) {
|
func (ar *Archive) extractContents(e *archive.Entry) {
|
||||||
if ar.match(entry) {
|
ar.extractContents1(e, nil)
|
||||||
if verbose {
|
|
||||||
listEntry(entry, false)
|
|
||||||
}
|
}
|
||||||
fd, err := os.OpenFile(entry.name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, entry.mode)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
ar.output(entry, fd)
|
defer f.Close()
|
||||||
fd.Close()
|
out = f
|
||||||
} else {
|
}
|
||||||
ar.skip(entry)
|
ar.output(e, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isGoCompilerObjFile reports whether file is an object file created
|
// isGoCompilerObjFile reports whether file is an object file created
|
||||||
// by the Go compiler.
|
// by the Go compiler, which is an archive file with exactly two entries:
|
||||||
func isGoCompilerObjFile(file string) bool {
|
// __.PKGDEF and _go_.o.
|
||||||
fd, err := os.Open(file)
|
func isGoCompilerObjFile(a *archive.Archive) bool {
|
||||||
if err != nil {
|
if len(a.Entries) != 2 {
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for "!<arch>\n" header.
|
|
||||||
buf := make([]byte, len(arHeader))
|
|
||||||
_, err = io.ReadFull(fd, buf)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
log.Fatal(err)
|
var foundPkgDef, foundGo bool
|
||||||
|
for _, e := range a.Entries {
|
||||||
|
if e.Type == archive.EntryPkgDef && e.Name == "__.PKGDEF" {
|
||||||
|
foundPkgDef = true
|
||||||
}
|
}
|
||||||
if string(buf) != arHeader {
|
if e.Type == archive.EntryGoObj && e.Name == "_go_.o" {
|
||||||
return false
|
foundGo = true
|
||||||
}
|
|
||||||
|
|
||||||
// Check for exactly two entries: "__.PKGDEF" and "_go_.o".
|
|
||||||
match := []string{"__.PKGDEF", "_go_.o"}
|
|
||||||
buf = make([]byte, entryLen)
|
|
||||||
for {
|
|
||||||
_, err := io.ReadFull(fd, buf)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
// No entries left.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if buf[entryLen-2] != '`' || buf[entryLen-1] != '\n' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
name := strings.TrimRight(string(buf[:16]), " ")
|
|
||||||
for {
|
|
||||||
if len(match) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
var next string
|
|
||||||
next, match = match[0], match[1:]
|
|
||||||
if name == next {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size, err := strconv.ParseInt(strings.TrimRight(string(buf[48:58]), " "), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if size&1 != 0 {
|
|
||||||
size++
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fd.Seek(size, io.SeekCurrent)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return foundPkgDef && foundGo
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"cmd/internal/archive"
|
||||||
"fmt"
|
"fmt"
|
||||||
"internal/testenv"
|
"internal/testenv"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -16,36 +17,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExactly16Bytes(t *testing.T) {
|
|
||||||
var tests = []string{
|
|
||||||
"",
|
|
||||||
"a",
|
|
||||||
"日本語",
|
|
||||||
"1234567890123456",
|
|
||||||
"12345678901234567890",
|
|
||||||
"1234567890123本語4567890",
|
|
||||||
"12345678901234日本語567890",
|
|
||||||
"123456789012345日本語67890",
|
|
||||||
"1234567890123456日本語7890",
|
|
||||||
"1234567890123456日本語7日本語890",
|
|
||||||
}
|
|
||||||
for _, str := range tests {
|
|
||||||
got := exactly16Bytes(str)
|
|
||||||
if len(got) != 16 {
|
|
||||||
t.Errorf("exactly16Bytes(%q) is %q, length %d", str, got, len(got))
|
|
||||||
}
|
|
||||||
// Make sure it is full runes.
|
|
||||||
for _, c := range got {
|
|
||||||
if c == utf8.RuneError {
|
|
||||||
t.Errorf("exactly16Bytes(%q) is %q, has partial rune", str, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tmpDir creates a temporary directory and returns its name.
|
// tmpDir creates a temporary directory and returns its name.
|
||||||
func tmpDir(t *testing.T) string {
|
func tmpDir(t *testing.T) string {
|
||||||
name, err := ioutil.TempDir("", "pack")
|
name, err := ioutil.TempDir("", "pack")
|
||||||
|
|
@ -58,12 +31,12 @@ func tmpDir(t *testing.T) string {
|
||||||
// testCreate creates an archive in the specified directory.
|
// testCreate creates an archive in the specified directory.
|
||||||
func testCreate(t *testing.T, dir string) {
|
func testCreate(t *testing.T, dir string) {
|
||||||
name := filepath.Join(dir, "pack.a")
|
name := filepath.Join(dir, "pack.a")
|
||||||
ar := archive(name, os.O_RDWR, nil)
|
ar := openArchive(name, os.O_RDWR|os.O_CREATE, nil)
|
||||||
// Add an entry by hand.
|
// Add an entry by hand.
|
||||||
ar.addFile(helloFile.Reset())
|
ar.addFile(helloFile.Reset())
|
||||||
ar.fd.Close()
|
ar.a.File().Close()
|
||||||
// Now check it.
|
// Now check it.
|
||||||
ar = archive(name, os.O_RDONLY, []string{helloFile.name})
|
ar = openArchive(name, os.O_RDONLY, []string{helloFile.name})
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
stdout = &buf
|
stdout = &buf
|
||||||
verbose = true
|
verbose = true
|
||||||
|
|
@ -72,7 +45,7 @@ func testCreate(t *testing.T, dir string) {
|
||||||
verbose = false
|
verbose = false
|
||||||
}()
|
}()
|
||||||
ar.scan(ar.printContents)
|
ar.scan(ar.printContents)
|
||||||
ar.fd.Close()
|
ar.a.File().Close()
|
||||||
result := buf.String()
|
result := buf.String()
|
||||||
// Expect verbose output plus file contents.
|
// Expect verbose output plus file contents.
|
||||||
expect := fmt.Sprintf("%s\n%s", helloFile.name, helloFile.contents)
|
expect := fmt.Sprintf("%s\n%s", helloFile.name, helloFile.contents)
|
||||||
|
|
@ -103,15 +76,14 @@ func TestTableOfContents(t *testing.T) {
|
||||||
dir := tmpDir(t)
|
dir := tmpDir(t)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
name := filepath.Join(dir, "pack.a")
|
name := filepath.Join(dir, "pack.a")
|
||||||
ar := archive(name, os.O_RDWR, nil)
|
ar := openArchive(name, os.O_RDWR|os.O_CREATE, nil)
|
||||||
|
|
||||||
// Add some entries by hand.
|
// Add some entries by hand.
|
||||||
ar.addFile(helloFile.Reset())
|
ar.addFile(helloFile.Reset())
|
||||||
ar.addFile(goodbyeFile.Reset())
|
ar.addFile(goodbyeFile.Reset())
|
||||||
ar.fd.Close()
|
ar.a.File().Close()
|
||||||
|
|
||||||
// Now print it.
|
// Now print it.
|
||||||
ar = archive(name, os.O_RDONLY, nil)
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
stdout = &buf
|
stdout = &buf
|
||||||
verbose = true
|
verbose = true
|
||||||
|
|
@ -119,8 +91,9 @@ func TestTableOfContents(t *testing.T) {
|
||||||
stdout = os.Stdout
|
stdout = os.Stdout
|
||||||
verbose = false
|
verbose = false
|
||||||
}()
|
}()
|
||||||
|
ar = openArchive(name, os.O_RDONLY, nil)
|
||||||
ar.scan(ar.tableOfContents)
|
ar.scan(ar.tableOfContents)
|
||||||
ar.fd.Close()
|
ar.a.File().Close()
|
||||||
result := buf.String()
|
result := buf.String()
|
||||||
// Expect verbose listing.
|
// Expect verbose listing.
|
||||||
expect := fmt.Sprintf("%s\n%s\n", helloFile.Entry(), goodbyeFile.Entry())
|
expect := fmt.Sprintf("%s\n%s\n", helloFile.Entry(), goodbyeFile.Entry())
|
||||||
|
|
@ -131,9 +104,9 @@ func TestTableOfContents(t *testing.T) {
|
||||||
// Do it again without verbose.
|
// Do it again without verbose.
|
||||||
verbose = false
|
verbose = false
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
ar = archive(name, os.O_RDONLY, nil)
|
ar = openArchive(name, os.O_RDONLY, nil)
|
||||||
ar.scan(ar.tableOfContents)
|
ar.scan(ar.tableOfContents)
|
||||||
ar.fd.Close()
|
ar.a.File().Close()
|
||||||
result = buf.String()
|
result = buf.String()
|
||||||
// Expect non-verbose listing.
|
// Expect non-verbose listing.
|
||||||
expect = fmt.Sprintf("%s\n%s\n", helloFile.name, goodbyeFile.name)
|
expect = fmt.Sprintf("%s\n%s\n", helloFile.name, goodbyeFile.name)
|
||||||
|
|
@ -144,9 +117,9 @@ func TestTableOfContents(t *testing.T) {
|
||||||
// Do it again with file list arguments.
|
// Do it again with file list arguments.
|
||||||
verbose = false
|
verbose = false
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
ar = archive(name, os.O_RDONLY, []string{helloFile.name})
|
ar = openArchive(name, os.O_RDONLY, []string{helloFile.name})
|
||||||
ar.scan(ar.tableOfContents)
|
ar.scan(ar.tableOfContents)
|
||||||
ar.fd.Close()
|
ar.a.File().Close()
|
||||||
result = buf.String()
|
result = buf.String()
|
||||||
// Expect only helloFile.
|
// Expect only helloFile.
|
||||||
expect = fmt.Sprintf("%s\n", helloFile.name)
|
expect = fmt.Sprintf("%s\n", helloFile.name)
|
||||||
|
|
@ -161,11 +134,11 @@ func TestExtract(t *testing.T) {
|
||||||
dir := tmpDir(t)
|
dir := tmpDir(t)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
name := filepath.Join(dir, "pack.a")
|
name := filepath.Join(dir, "pack.a")
|
||||||
ar := archive(name, os.O_RDWR, nil)
|
ar := openArchive(name, os.O_RDWR|os.O_CREATE, nil)
|
||||||
// Add some entries by hand.
|
// Add some entries by hand.
|
||||||
ar.addFile(helloFile.Reset())
|
ar.addFile(helloFile.Reset())
|
||||||
ar.addFile(goodbyeFile.Reset())
|
ar.addFile(goodbyeFile.Reset())
|
||||||
ar.fd.Close()
|
ar.a.File().Close()
|
||||||
// Now extract one file. We chdir to the directory of the archive for simplicity.
|
// Now extract one file. We chdir to the directory of the archive for simplicity.
|
||||||
pwd, err := os.Getwd()
|
pwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -181,9 +154,9 @@ func TestExtract(t *testing.T) {
|
||||||
t.Fatal("os.Chdir: ", err)
|
t.Fatal("os.Chdir: ", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
ar = archive(name, os.O_RDONLY, []string{goodbyeFile.name})
|
ar = openArchive(name, os.O_RDONLY, []string{goodbyeFile.name})
|
||||||
ar.scan(ar.extractContents)
|
ar.scan(ar.extractContents)
|
||||||
ar.fd.Close()
|
ar.a.File().Close()
|
||||||
data, err := ioutil.ReadFile(goodbyeFile.name)
|
data, err := ioutil.ReadFile(goodbyeFile.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -416,13 +389,13 @@ func (f *FakeFile) Sys() interface{} {
|
||||||
|
|
||||||
// Special helpers.
|
// Special helpers.
|
||||||
|
|
||||||
func (f *FakeFile) Entry() *Entry {
|
func (f *FakeFile) Entry() *archive.Entry {
|
||||||
return &Entry{
|
return &archive.Entry{
|
||||||
name: f.name,
|
Name: f.name,
|
||||||
mtime: 0, // Defined to be zero.
|
Mtime: 0, // Defined to be zero.
|
||||||
uid: 0, // Ditto.
|
Uid: 0, // Ditto.
|
||||||
gid: 0, // Ditto.
|
Gid: 0, // Ditto.
|
||||||
mode: f.mode,
|
Mode: f.mode,
|
||||||
size: int64(len(f.contents)),
|
Data: archive.Data{Size: int64(len(f.contents))},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue