Commit fdbf3d90 authored by Rob Pike's avatar Rob Pike

cmd/pack: rewrite in Go

Replace the pack command, a C program, with a clean reimplementation in Go.
It does not need to reproduce the full feature set and it is no longer used by
the build chain, but has a role in looking inside archives created by the build
chain directly.

Since it's not in C, it is no longer build by dist, so remove it from cmd/dist and
make it a "tool" in cmd/go terminology.

Fixes #2705

R=rsc, dave, minux.ma, josharian
CC=golang-codereviews
https://golang.org/cl/52310044
parent b3a3afc9
...@@ -1300,7 +1300,6 @@ static char *buildorder[] = { ...@@ -1300,7 +1300,6 @@ static char *buildorder[] = {
"cmd/addr2line", "cmd/addr2line",
"cmd/objdump", "cmd/objdump",
"cmd/pack",
"cmd/prof", "cmd/prof",
"cmd/cc", // must be before c "cmd/cc", // must be before c
...@@ -1379,7 +1378,6 @@ static char *cleantab[] = { ...@@ -1379,7 +1378,6 @@ static char *cleantab[] = {
"cmd/gc", "cmd/gc",
"cmd/go", "cmd/go",
"cmd/objdump", "cmd/objdump",
"cmd/pack",
"cmd/prof", "cmd/prof",
"lib9", "lib9",
"libbio", "libbio",
......
...@@ -309,6 +309,7 @@ var goTools = map[string]targetDir{ ...@@ -309,6 +309,7 @@ var goTools = map[string]targetDir{
"cmd/fix": toTool, "cmd/fix": toTool,
"cmd/link": toTool, "cmd/link": toTool,
"cmd/nm": toTool, "cmd/nm": toTool,
"cmd/pack": toTool,
"cmd/yacc": toTool, "cmd/yacc": toTool,
"code.google.com/p/go.tools/cmd/cover": toTool, "code.google.com/p/go.tools/cmd/cover": toTool,
"code.google.com/p/go.tools/cmd/godoc": toBin, "code.google.com/p/go.tools/cmd/godoc": toBin,
......
# Copyright 2012 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.
include ../../Make.dist
// Inferno utils/iar/ar.c
// http://code.google.com/p/inferno-os/source/browse/utils/iar/ar.c
//
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
// Portions Copyright © 1997-1999 Vita Nuova Limited
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
// Portions Copyright © 2004,2006 Bruce Ellis
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
// Portions Copyright © 2009 The Go Authors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
/*
* ar - portable (ascii) format version
*/
/* protect a couple of our names */
#define select your_select
#define rcmd your_rcmd
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <mach.h>
#include "../../libmach/obj.h"
#include <ar.h>
#undef select
#undef rcmd
/*
* The algorithm uses up to 3 temp files. The "pivot member" is the
* archive member specified by and a, b, or i option. The temp files are
* astart - contains existing members up to and including the pivot member.
* amiddle - contains new files moved or inserted behind the pivot.
* aend - contains the existing members that follow the pivot member.
* When all members have been processed, function 'install' streams the
* temp files, in order, back into the archive.
*/
typedef struct Arsymref
{
char *name;
char *file;
int type;
int len;
vlong offset;
struct Arsymref *next;
} Arsymref;
typedef struct Armember /* Temp file entry - one per archive member */
{
struct Armember *next;
struct ar_hdr hdr;
long size;
long date;
void *member;
} Armember;
typedef struct Arfile /* Temp file control block - one per tempfile */
{
char *fname; /* paging file name */
vlong size;
Armember *head; /* head of member chain */
Armember *tail; /* tail of member chain */
Arsymref *sym; /* head of defined symbol chain */
} Arfile;
typedef struct Hashchain
{
char *name;
char *file;
struct Hashchain *next;
} Hashchain;
#define NHASH 1024
/*
* macro to portably read/write archive header.
* 'cmd' is read/write/Bread/Bwrite, etc.
*/
#define HEADER_IO(cmd, f, h) cmd(f, h.name, sizeof(h.name)) != sizeof(h.name)\
|| cmd(f, h.date, sizeof(h.date)) != sizeof(h.date)\
|| cmd(f, h.uid, sizeof(h.uid)) != sizeof(h.uid)\
|| cmd(f, h.gid, sizeof(h.gid)) != sizeof(h.gid)\
|| cmd(f, h.mode, sizeof(h.mode)) != sizeof(h.mode)\
|| cmd(f, h.size, sizeof(h.size)) != sizeof(h.size)\
|| cmd(f, h.fmag, sizeof(h.fmag)) != sizeof(h.fmag)
/* constants and flags */
char *man = "mrxtdpq";
char *opt = "uvnbailogS";
char artemp[] = "/tmp/vXXXXX";
char movtemp[] = "/tmp/v1XXXXX";
char tailtemp[] = "/tmp/v2XXXXX";
char symdef[] = "__.GOSYMDEF";
char pkgdef[] = "__.PKGDEF";
int aflag; /* command line flags */
int bflag;
int cflag;
int gflag;
int oflag;
int uflag;
int vflag;
int Pflag; /* remove leading file prefix */
int Sflag; /* force mark Go package as safe */
int errors;
Arfile *astart, *amiddle, *aend; /* Temp file control block pointers */
int allobj = 1; /* set when all members are object files of the same type */
int symdefsize; /* size of symdef file */
char *pkgstmt; /* string "package foo" */
char *objhdr; /* string "go object darwin 386 release.2010-01-01 2345+" */
int dupfound; /* flag for duplicate symbol */
Hashchain *hash[NHASH]; /* hash table of text symbols */
#define ARNAMESIZE sizeof(astart->tail->hdr.name)
char poname[ARNAMESIZE+1]; /* name of pivot member */
char *file; /* current file or member being worked on */
Biobuf bout;
Biobuf bar;
char *prefix;
int pkgdefsafe; /* was __.PKGDEF marked safe? */
void arcopy(Biobuf*, Arfile*, Armember*);
int arcreate(char*);
void arfree(Arfile*);
void arinsert(Arfile*, Armember*);
void *armalloc(int);
char *arstrdup(char*);
void armove(Biobuf*, Arfile*, Armember*);
void arread(Biobuf*, Armember*);
void arstream(int, Arfile*);
int arwrite(int, Armember*);
int bamatch(char*, char*);
int duplicate(char*, char**);
Armember *getdir(Biobuf*);
void getpkgdef(char**, int*);
void install(char*, Arfile*, Arfile*, Arfile*, int);
void loadpkgdata(char*, int);
void longt(Armember*);
int match(int, char**);
void mesg(int, char*);
Arfile *newtempfile(char*);
Armember *newmember(void);
void objsym(Sym*, void*);
int openar(char*, int, int);
void pmode(long);
void rl(int);
void scanobj(Biobuf*, Arfile*, long);
void scanpkg(Biobuf*, long);
void select(int*, long);
void setcom(void(*)(char*, int, char**));
void skip(Biobuf*, vlong);
void checksafe(Biobuf*, vlong);
int symcomp(void*, void*);
void trim(char*, char*, int);
void usage(void);
void wrerr(void);
void wrsym(Biobuf*, long, Arsymref*);
int arread_cutprefix(Biobuf*, Armember*);
void rcmd(char*, int, char**); /* command processing */
void dcmd(char*, int, char**);
void xcmd(char*, int, char**);
void tcmd(char*, int, char**);
void pcmd(char*, int, char**);
void mcmd(char*, int, char**);
void qcmd(char*, int, char**);
void (*comfun)(char*, int, char**);
void
main(int argc, char *argv[])
{
char *cp;
Binit(&bout, 1, OWRITE);
if(argc < 3)
usage();
for (cp = argv[1]; *cp; cp++) {
switch(*cp) {
case 'a': aflag = 1; break;
case 'b': bflag = 1; break;
case 'c': cflag = 1; break;
case 'd': setcom(dcmd); break;
case 'g': gflag = 1; break;
case 'i': bflag = 1; break;
case 'l':
strcpy(artemp, "vXXXXX");
strcpy(movtemp, "v1XXXXX");
strcpy(tailtemp, "v2XXXXX");
break;
case 'm': setcom(mcmd); break;
case 'o': oflag = 1; break;
case 'p': setcom(pcmd); break;
case 'q': setcom(qcmd); break;
case 'r': setcom(rcmd); break;
case 't': setcom(tcmd); break;
case 'u': uflag = 1; break;
case 'v': vflag = 1; break;
case 'x': setcom(xcmd); break;
case 'S': Sflag = 1; break;
case 'P': Pflag = 1; break;
default:
fprint(2, "pack: bad option `%c'\n", *cp);
exits("error");
}
}
if (aflag && bflag) {
fprint(2, "pack: only one of 'a' and 'b' can be specified\n");
usage();
}
if(aflag || bflag) {
trim(argv[2], poname, sizeof(poname));
argv++;
argc--;
if(argc < 3)
usage();
}
if(Pflag) {
if(argc < 4) {
fprint(2, "pack: P flag requires prefix argument\n");
usage();
}
prefix = argv[2];
argv++;
argc--;
}
if(comfun == 0) {
if(uflag == 0) {
fprint(2, "pack: one of [%s] must be specified\n", man);
usage();
}
setcom(rcmd);
}
cp = argv[2];
argc -= 3;
argv += 3;
(*comfun)(cp, argc, argv); /* do the command */
if(errors && cflag)
remove(cp);
cp = 0;
while (argc--) {
if (*argv) {
fprint(2, "pack: %s not found\n", *argv);
cp = "error";
}
argv++;
}
if (errors)
cp = "error";
exits(cp);
}
/*
* select a command
*/
void
setcom(void (*fun)(char *, int, char**))
{
if(comfun != 0) {
fprint(2, "pack: only one of [%s] allowed\n", man);
usage();
}
comfun = fun;
}
/*
* perform the 'r' and 'u' commands
*/
void
rcmd(char *arname, int count, char **files)
{
int fd;
int i;
Arfile *ap;
Armember *bp;
Dir *d;
Biobuf *bfile;
fd = openar(arname, ORDWR, 1);
if (fd >= 0) {
Binit(&bar, fd, OREAD);
Bseek(&bar,seek(fd,0,1), 1);
}
astart = newtempfile(artemp);
ap = astart;
aend = 0;
for(i = 0; fd >= 0; i++) {
bp = getdir(&bar);
if (!bp)
break;
if (bamatch(file, poname)) { /* check for pivot */
aend = newtempfile(tailtemp);
ap = aend;
}
/* pitch symdef file */
if (i == 0 && strcmp(file, symdef) == 0) {
skip(&bar, bp->size);
continue;
}
/* pitch pkgdef file but remember whether it was marked safe */
if (gflag && strcmp(file, pkgdef) == 0) {
checksafe(&bar, bp->size);
continue;
}
/*
* the plan 9 ar treats count == 0 as equivalent
* to listing all the archive's files on the command line:
* it will try to open every file name in the archive
* and copy that file into the archive if it exists.
* for go we disable that behavior, because we use
* r with no files to make changes to the archive itself,
* using the S or P flags.
*/
if (!match(count, files)) {
scanobj(&bar, ap, bp->size);
arcopy(&bar, ap, bp);
continue;
}
bfile = Bopen(file, OREAD);
if (!bfile) {
if (count != 0) {
fprint(2, "pack: cannot open %s\n", file);
errors++;
}
scanobj(&bar, ap, bp->size);
arcopy(&bar, ap, bp);
continue;
}
d = dirfstat(Bfildes(bfile));
if(d == nil)
fprint(2, "pack: cannot stat %s: %r\n", file);
if (uflag && (d==nil || d->mtime <= bp->date)) {
scanobj(&bar, ap, bp->size);
arcopy(&bar, ap, bp);
Bterm(bfile);
free(d);
continue;
}
mesg('r', file);
skip(&bar, bp->size);
scanobj(bfile, ap, d->length);
free(d);
armove(bfile, ap, bp);
Bterm(bfile);
}
if(fd >= 0)
close(fd);
/* copy in remaining files named on command line */
for (i = 0; i < count; i++) {
file = files[i];
if(file == 0)
continue;
files[i] = 0;
bfile = Bopen(file, OREAD);
if (!bfile) {
fprint(2, "pack: cannot open %s\n", file);
errors++;
} else {
mesg('a', file);
d = dirfstat(Bfildes(bfile));
if (d == nil)
fprint(2, "can't stat %s\n", file);
else {
scanobj(bfile, astart, d->length);
armove(bfile, astart, newmember());
free(d);
}
Bterm(bfile);
}
}
if(fd < 0 && !cflag)
install(arname, astart, 0, aend, 1); /* issue 'creating' msg */
else
install(arname, astart, 0, aend, 0);
}
void
dcmd(char *arname, int count, char **files)
{
Armember *bp;
int fd, i;
if (!count)
return;
fd = openar(arname, ORDWR, 0);
Binit(&bar, fd, OREAD);
Bseek(&bar,seek(fd,0,1), 1);
astart = newtempfile(artemp);
for (i = 0; bp = getdir(&bar); i++) {
if(match(count, files)) {
mesg('d', file);
skip(&bar, bp->size);
if (strcmp(file, symdef) == 0)
allobj = 0;
} else if (i == 0 && strcmp(file, symdef) == 0) {
skip(&bar, bp->size);
} else if (gflag && strcmp(file, pkgdef) == 0) {
skip(&bar, bp->size);
} else {
scanobj(&bar, astart, bp->size);
arcopy(&bar, astart, bp);
}
}
close(fd);
install(arname, astart, 0, 0, 0);
}
void
xcmd(char *arname, int count, char **files)
{
int fd, f, mode, i;
Armember *bp;
Dir dx;
fd = openar(arname, OREAD, 0);
Binit(&bar, fd, OREAD);
Bseek(&bar,seek(fd,0,1), 1);
i = 0;
while (bp = getdir(&bar)) {
if(count == 0 || match(count, files)) {
mode = strtoul(bp->hdr.mode, 0, 8) & 0777;
f = create(file, OWRITE, mode);
if(f < 0) {
fprint(2, "pack: %s cannot create\n", file);
skip(&bar, bp->size);
} else {
mesg('x', file);
arcopy(&bar, 0, bp);
if (write(f, bp->member, bp->size) < 0)
wrerr();
if(oflag && bp->date != 0) {
nulldir(&dx);
dx.atime = bp->date;
dx.mtime = bp->date;
if(dirwstat(file, &dx) < 0)
perror(file);
}
free(bp->member);
close(f);
}
free(bp);
if (count && ++i >= count)
break;
} else {
skip(&bar, bp->size);
free(bp);
}
}
close(fd);
}
void
pcmd(char *arname, int count, char **files)
{
int fd;
Armember *bp;
fd = openar(arname, OREAD, 0);
Binit(&bar, fd, OREAD);
Bseek(&bar,seek(fd,0,1), 1);
while(bp = getdir(&bar)) {
if(count == 0 || match(count, files)) {
if(vflag)
print("\n<%s>\n\n", file);
arcopy(&bar, 0, bp);
if (write(1, bp->member, bp->size) < 0)
wrerr();
} else
skip(&bar, bp->size);
free(bp);
}
close(fd);
}
void
mcmd(char *arname, int count, char **files)
{
int fd, i;
Arfile *ap;
Armember *bp;
if (count == 0)
return;
fd = openar(arname, ORDWR, 0);
Binit(&bar, fd, OREAD);
Bseek(&bar,seek(fd,0,1), 1);
astart = newtempfile(artemp);
amiddle = newtempfile(movtemp);
aend = 0;
ap = astart;
for (i = 0; bp = getdir(&bar); i++) {
if (bamatch(file, poname)) {
aend = newtempfile(tailtemp);
ap = aend;
}
if(match(count, files)) {
mesg('m', file);
scanobj(&bar, amiddle, bp->size);
arcopy(&bar, amiddle, bp);
} else if (ap == astart && i == 0 && strcmp(file, symdef) == 0) {
/*
* pitch the symdef file if it is at the beginning
* of the archive and we aren't inserting in front
* of it (ap == astart).
*/
skip(&bar, bp->size);
} else if (ap == astart && gflag && strcmp(file, pkgdef) == 0) {
/*
* pitch the pkgdef file if we aren't inserting in front
* of it (ap == astart).
*/
skip(&bar, bp->size);
} else {
scanobj(&bar, ap, bp->size);
arcopy(&bar, ap, bp);
}
}
close(fd);
if (poname[0] && aend == 0)
fprint(2, "pack: %s not found - files moved to end.\n", poname);
install(arname, astart, amiddle, aend, 0);
}
void
tcmd(char *arname, int count, char **files)
{
int fd;
Armember *bp;
char name[ARNAMESIZE+1];
fd = openar(arname, OREAD, 0);
Binit(&bar, fd, OREAD);
Bseek(&bar,seek(fd,0,1), 1);
while(bp = getdir(&bar)) {
if(count == 0 || match(count, files)) {
if(vflag)
longt(bp);
trim(file, name, ARNAMESIZE);
Bprint(&bout, "%s\n", name);
}
skip(&bar, bp->size);
free(bp);
}
close(fd);
}
void
qcmd(char *arname, int count, char **files)
{
int fd, i;
Armember *bp;
Biobuf *bfile;
if(aflag || bflag) {
fprint(2, "pack: abi not allowed with q\n");
exits("error");
}
fd = openar(arname, ORDWR, 1);
if (fd < 0) {
if(!cflag)
fprint(2, "pack: creating %s\n", arname);
fd = arcreate(arname);
}
Binit(&bar, fd, OREAD);
Bseek(&bar,seek(fd,0,1), 1);
/* leave note group behind when writing archive; i.e. sidestep interrupts */
rfork(RFNOTEG);
Bseek(&bar, 0, 2);
bp = newmember();
for(i=0; i<count && files[i]; i++) {
file = files[i];
files[i] = 0;
bfile = Bopen(file, OREAD);
if(!bfile) {
fprint(2, "pack: cannot open %s\n", file);
errors++;
} else {
mesg('q', file);
armove(bfile, 0, bp);
if (!arwrite(fd, bp))
wrerr();
free(bp->member);
bp->member = 0;
Bterm(bfile);
}
}
free(bp);
close(fd);
}
/*
* does the object header line p match the last one we saw?
* update *lastp if it gets more specific.
*/
int
matchhdr(char *p, char **lastp)
{
int n;
char *last;
// no information?
last = *lastp;
if(last == nil) {
*lastp = strdup(p);
return 1;
}
// identical match?
if(strcmp(last, p) == 0)
return 1;
// last has extra fields
n = strlen(p);
if(n < strlen(last) && last[n] == ' ')
return 1;
// p has extra fields - save in last
n = strlen(last);
if(n < strlen(p) && p[n] == ' ') {
free(last);
*lastp = strdup(p);
return 1;
}
return 0;
}
/*
* extract the symbol references from an object file
*/
void
scanobj(Biobuf *b, Arfile *ap, long size)
{
int goobject;
vlong offset;
uchar buf[4];
char *p;
if (!allobj) /* non-object file encountered */
return;
offset = Boffset(b);
memset(buf, 0, sizeof buf);
Bread(b, buf, 4);
/* maybe a foreign object file? that's okay */
if((buf[0] == 0x7F && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F') || // ELF
(buf[0] == 0x4c && buf[1] == 0x01 || buf[0] == 0x64 && buf[1] == 0x86) || // Windows PE
(buf[0] == 0xFE && buf[1] == 0xED && buf[2] == 0xFA && (buf[3]&~1) == 0xCE) || // Mach-O big-endian
(buf[3] == 0xFE && buf[2] == 0xED && buf[1] == 0xFA && (buf[0]&~1) == 0xCE)) { // Mach-O little-endian
Bseek(b, offset, 0);
return;
}
goobject = 1;
Bseek(b, offset, 0);
p = Brdstr(b, '\n', 1);
// After the go object header comes the Go metadata,
// followed by ! on a line by itself. If this is not a Go object,
// the ! comes immediately. Catch that so we can avoid
// the call to scanpkg below, since scanpkg assumes that the
// Go metadata is present.
if(BGETC(b) == '!')
goobject = 0;
Bseek(b, offset, 0);
if(p == nil || strncmp(p, "go object ", 10) != 0) {
fprint(2, "pack: malformed object file %s\n", file);
errors++;
free(p);
return;
}
if (!matchhdr(p, &objhdr)) {
fprint(2, "pack: inconsistent object file %s: [%s] vs [%s]\n", file, p, objhdr);
errors++;
allobj = 0;
free(p);
return;
}
free(p);
USED(ap);
if (gflag && goobject) {
scanpkg(b, size);
Bseek(b, offset, 0);
}
}
/*
* does line contain substring (length-limited)
*/
int
strstrn(char *line, int len, char *sub)
{
int i;
int sublen;
sublen = strlen(sub);
for (i = 0; i < len - sublen; i++)
if (memcmp(line+i, sub, sublen) == 0)
return 1;
return 0;
}
/*
* package import data
*/
int safe = 1;
char* pkgname;
char* importblock;
void
getpkgdef(char **datap, int *lenp)
{
char *tag, *hdr;
if(pkgname == nil) {
pkgname = "__emptyarchive__";
importblock = "";
}
tag = "";
if(safe || Sflag)
tag = "safe";
hdr = "empty archive";
if(objhdr != nil)
hdr = objhdr;
*datap = smprint("%s\nimport\n$$\npackage %s %s\n%s\n$$\n", hdr, pkgname, tag, importblock);
*lenp = strlen(*datap);
}
/*
* extract the package definition data from an object file.
* there can be only one.
*/
void
scanpkg(Biobuf *b, long size)
{
long n;
int c;
long start, end, pkgsize;
char *data, *line, pkgbuf[1024], *pkg;
int first;
/*
* scan until $$
*/
for (n=0; n<size; ) {
c = BGETC(b);
if(c == Beof)
break;
n++;
if(c != '$')
continue;
c = BGETC(b);
if(c == Beof)
break;
n++;
if(c != '$')
continue;
goto foundstart;
}
// fprint(2, "pack: warning: no package import section in %s\n", file);
if(b != &bar || !pkgdefsafe)
safe = 0; // non-Go file (C or assembly)
return;
foundstart:
/* found $$; skip rest of line */
while((c = BGETC(b)) != '\n')
if(c == Beof)
goto bad;
/* how big is it? */
first = 1;
start = end = 0;
for (n=0; n<size; n+=Blinelen(b)) {
line = Brdstr(b, '\n', 0);
if (line == nil)
goto bad;
if (first && strstrn(line, Blinelen(b), "package ")) {
if (Blinelen(b) > sizeof(pkgbuf)-1)
goto bad;
memmove(pkgbuf, line, Blinelen(b));
pkgbuf[Blinelen(b)] = '\0';
pkg = pkgbuf;
while(*pkg == ' ' || *pkg == '\t')
pkg++;
if(strncmp(pkg, "package ", 8) != 0)
goto bad;
pkg += 8;
data = pkg;
while(*pkg != ' ' && *pkg != '\n' && *pkg != '\0')
pkg++;
pkgname = armalloc(pkg - data + 1);
memmove(pkgname, data, pkg - data);
pkgname[pkg-data] = '\0';
if(strcmp(pkg, " safe\n") != 0 && (b != &bar || !pkgdefsafe))
safe = 0;
start = Boffset(b); // after package statement
first = 0;
free(line);
continue;
}
if(line[0] == '$' && line[1] == '$') {
free(line);
goto foundend;
}
end = Boffset(b); // before closing $$
free(line);
}
bad:
fprint(2, "pack: bad package import section in %s\n", file);
errors++;
return;
foundend:
if (start == 0)
return;
if (end == 0)
goto bad;
if(importblock != nil) {
fprint(2, "pack: multiple Go object files\n");
errors++;
return;
}
pkgsize = end-start;
data = armalloc(end - start + 1);
Bseek(b, start, 0);
if (Bread(b, data, pkgsize) != pkgsize) {
fprint(2, "pack: error reading package import section in %s\n", file);
errors++;
return;
}
data[end-start] = '\0';
importblock = data;
}
/*
* add text and data symbols to the symbol list
*/
void
objsym(Sym *s, void *p)
{
int n;
Arsymref *as;
Arfile *ap;
char *ofile;
if (s->type != 'T' && s->type != 'D')
return;
ap = (Arfile*)p;
as = armalloc(sizeof(Arsymref));
as->offset = ap->size;
as->name = arstrdup(s->name);
as->file = arstrdup(file);
if(s->type == 'T' && duplicate(as->name, &ofile)) {
dupfound = 1;
fprint(2, "duplicate text symbol: %s and %s: %s\n", as->file, ofile, as->name);
errors++;
free(as->name);
free(as);
return;
}
as->type = s->type;
n = strlen(s->name);
symdefsize += 4+(n+1)+1;
as->len = n;
as->next = ap->sym;
ap->sym = as;
}
/*
* Check the symbol table for duplicate text symbols
*/
int
hashstr(char *name)
{
uint32 h;
char *cp;
h = 0;
for(cp = name; *cp; h += *cp++)
h *= 1119;
return h & 0xfffffff;
}
int
duplicate(char *name, char **ofile)
{
Hashchain *p;
int h;
h = hashstr(name) % NHASH;
for(p = hash[h]; p; p = p->next)
if(strcmp(p->name, name) == 0) {
*ofile = p->file;
return 1;
}
p = armalloc(sizeof(Hashchain));
p->next = hash[h];
p->name = name;
p->file = file;
hash[h] = p;
*ofile = nil;
return 0;
}
/*
* open an archive and validate its header
*/
int
openar(char *arname, int mode, int errok)
{
int fd;
char mbuf[SARMAG];
fd = open(arname, mode);
if(fd >= 0){
if(read(fd, mbuf, SARMAG) != SARMAG || strncmp(mbuf, ARMAG, SARMAG)) {
fprint(2, "pack: %s not in archive format\n", arname);
exits("error");
}
}else if(!errok){
fprint(2, "pack: cannot open %s: %r\n", arname);
exits("error");
}
return fd;
}
/*
* create an archive and set its header
*/
int
arcreate(char *arname)
{
int fd;
fd = create(arname, OWRITE, 0664);
if(fd < 0){
fprint(2, "pack: cannot create %s: %r\n", arname);
exits("error");
}
if(write(fd, ARMAG, SARMAG) != SARMAG)
wrerr();
return fd;
}
/*
* error handling
*/
void
wrerr(void)
{
perror("pack: write error");
exits("error");
}
void
rderr(void)
{
perror("pack: read error");
exits("error");
}
void
phaseerr(int offset)
{
fprint(2, "pack: phase error at offset %d\n", offset);
exits("error");
}
void
usage(void)
{
fprint(2, "usage: pack [%s][%s][P prefix] archive files ...\n", opt, man);
exits("error");
}
/*
* read the header for the next archive member
*/
Armember *
getdir(Biobuf *b)
{
Armember *bp;
char *cp;
static char name[ARNAMESIZE+1];
bp = newmember();
if(HEADER_IO(Bread, b, bp->hdr)) {
free(bp);
return 0;
}
if(strncmp(bp->hdr.fmag, ARFMAG, sizeof(bp->hdr.fmag)))
phaseerr(Boffset(b));
strncpy(name, bp->hdr.name, sizeof(bp->hdr.name));
cp = name+sizeof(name)-1;
while(*--cp==' ')
;
cp[1] = '\0';
file = arstrdup(name);
bp->date = strtol(bp->hdr.date, 0, 0);
bp->size = strtol(bp->hdr.size, 0, 0);
return bp;
}
/*
* Copy the file referenced by fd to the temp file
*/
void
armove(Biobuf *b, Arfile *ap, Armember *bp)
{
char *cp;
Dir *d;
vlong n;
d = dirfstat(Bfildes(b));
if (d == nil) {
fprint(2, "pack: cannot stat %s\n", file);
return;
}
trim(file, bp->hdr.name, sizeof(bp->hdr.name));
for (cp = strchr(bp->hdr.name, 0); /* blank pad on right */
cp < bp->hdr.name+sizeof(bp->hdr.name); cp++)
*cp = ' ';
sprint(bp->hdr.date, "%-12ld", 0L); // was d->mtime but removed for idempotent builds
sprint(bp->hdr.uid, "%-6d", 0);
sprint(bp->hdr.gid, "%-6d", 0);
sprint(bp->hdr.mode, "%-8lo", d->mode);
sprint(bp->hdr.size, "%-10lld", d->length);
strncpy(bp->hdr.fmag, ARFMAG, 2);
bp->size = d->length;
arread(b, bp);
n = bp->size;
if (n&1)
n++;
if (ap) {
arinsert(ap, bp);
ap->size += n+SAR_HDR;
}
free(d);
}
/*
* Copy the archive member at the current offset into the temp file.
*/
void
arcopy(Biobuf *b, Arfile *ap, Armember *bp)
{
long n;
arread(b, bp);
n = bp->size;
if (n & 01)
n++;
if (ap) {
arinsert(ap, bp);
ap->size += n+SAR_HDR;
}
}
/*
* Skip an archive member
*/
void
skip(Biobuf *bp, vlong len)
{
if (len & 01)
len++;
Bseek(bp, len, 1);
}
void
checksafe(Biobuf *bp, vlong len)
{
char *p;
vlong end;
if (len & 01)
len++;
end = Boffset(bp) + len;
p = Brdline(bp, '\n');
if(p == nil || strncmp(p, "go object ", 10) != 0)
goto done;
for(;;) {
p = Brdline(bp, '\n');
if(p == nil || Boffset(bp) >= end)
goto done;
if(strncmp(p, "$$\n", 3) == 0)
break;
}
p = Brdline(bp, '\n');
if(p == nil || Boffset(bp) > end)
goto done;
if(Blinelen(bp) > 8+6 && strncmp(p, "package ", 8) == 0 && strncmp(p+Blinelen(bp)-6, " safe\n", 6) == 0)
pkgdefsafe = 1;
done:
Bseek(bp, end, 0);
}
/*
* Stream the three temp files to an archive
*/
void
install(char *arname, Arfile *astart, Arfile *amiddle, Arfile *aend, int createflag)
{
int fd;
if(allobj && dupfound) {
fprint(2, "%s not changed\n", arname);
return;
}
/* leave note group behind when copying back; i.e. sidestep interrupts */
rfork(RFNOTEG);
if(createflag)
fprint(2, "pack: creating %s\n", arname);
fd = arcreate(arname);
if(allobj)
rl(fd);
if (astart) {
arstream(fd, astart);
arfree(astart);
}
if (amiddle) {
arstream(fd, amiddle);
arfree(amiddle);
}
if (aend) {
arstream(fd, aend);
arfree(aend);
}
close(fd);
}
void
rl(int fd)
{
Biobuf b;
char *cp;
struct ar_hdr a;
long len;
int headlen;
char *pkgdefdata;
int pkgdefsize;
pkgdefdata = nil;
pkgdefsize = 0;
Binit(&b, fd, OWRITE);
Bseek(&b,seek(fd,0,1), 0);
len = symdefsize;
if(len&01)
len++;
sprint(a.date, "%-12ld", 0L); // time(0)
sprint(a.uid, "%-6d", 0);
sprint(a.gid, "%-6d", 0);
sprint(a.mode, "%-8lo", 0644L);
sprint(a.size, "%-10ld", len);
strncpy(a.fmag, ARFMAG, 2);
strcpy(a.name, symdef);
for (cp = strchr(a.name, 0); /* blank pad on right */
cp < a.name+sizeof(a.name); cp++)
*cp = ' ';
if(HEADER_IO(Bwrite, &b, a))
wrerr();
headlen = Boffset(&b);
len += headlen;
if (gflag) {
getpkgdef(&pkgdefdata, &pkgdefsize);
len += SAR_HDR + pkgdefsize;
if (len & 1)
len++;
}
if (astart) {
wrsym(&b, len, astart->sym);
len += astart->size;
}
if(amiddle) {
wrsym(&b, len, amiddle->sym);
len += amiddle->size;
}
if(aend)
wrsym(&b, len, aend->sym);
if(symdefsize&0x01)
BPUTC(&b, 0);
if (gflag) {
len = pkgdefsize;
sprint(a.date, "%-12ld", 0L); // time(0)
sprint(a.uid, "%-6d", 0);
sprint(a.gid, "%-6d", 0);
sprint(a.mode, "%-8lo", 0644L);
sprint(a.size, "%-10ld", (len + 1) & ~1);
strncpy(a.fmag, ARFMAG, 2);
strcpy(a.name, pkgdef);
for (cp = strchr(a.name, 0); /* blank pad on right */
cp < a.name+sizeof(a.name); cp++)
*cp = ' ';
if(HEADER_IO(Bwrite, &b, a))
wrerr();
if (Bwrite(&b, pkgdefdata, pkgdefsize) != pkgdefsize)
wrerr();
if(len&0x01)
BPUTC(&b, 0);
}
Bterm(&b);
}
/*
* Write the defined symbols to the symdef file
*/
void
wrsym(Biobuf *bp, long offset, Arsymref *as)
{
int off;
while(as) {
BPUTC(bp, as->type);
off = as->offset+offset;
BPUTLE4(bp, off);
if (Bwrite(bp, as->name, as->len+1) != as->len+1)
wrerr();
as = as->next;
}
}
/*
* Check if the archive member matches an entry on the command line.
*/
int
match(int count, char **files)
{
int i;
char name[ARNAMESIZE+1];
for(i=0; i<count; i++) {
if(files[i] == 0)
continue;
trim(files[i], name, ARNAMESIZE);
if(strncmp(name, file, ARNAMESIZE) == 0) {
file = files[i];
files[i] = 0;
return 1;
}
}
return 0;
}
/*
* compare the current member to the name of the pivot member
*/
int
bamatch(char *file, char *pivot)
{
static int state = 0;
switch(state)
{
case 0: /* looking for position file */
if (aflag) {
if (strncmp(file, pivot, ARNAMESIZE) == 0)
state = 1;
} else if (bflag) {
if (strncmp(file, pivot, ARNAMESIZE) == 0) {
state = 2; /* found */
return 1;
}
}
break;
case 1: /* found - after previous file */
state = 2;
return 1;
case 2: /* already found position file */
break;
}
return 0;
}
/*
* output a message, if 'v' option was specified
*/
void
mesg(int c, char *file)
{
if(vflag)
Bprint(&bout, "%c - %s\n", c, file);
}
/*
* isolate file name by stripping leading directories and trailing slashes
*/
void
trim(char *s, char *buf, int n)
{
char *p, *q;
for(;;) {
p = strrchr(s, '/');
q = strrchr(s, '\\');
if (q > p)
p = q;
if (!p) { /* no (back)slash in name */
strncpy(buf, s, n);
return;
}
if (p[1] != 0) { /* p+1 is first char of file name */
strncpy(buf, p+1, n);
return;
}
*p = 0; /* strip trailing (back)slash */
}
}
/*
* utilities for printing long form of 't' command
*/
#define SUID 04000
#define SGID 02000
#define ROWN 0400
#define WOWN 0200
#define XOWN 0100
#define RGRP 040
#define WGRP 020
#define XGRP 010
#define ROTH 04
#define WOTH 02
#define XOTH 01
#define STXT 01000
void
longt(Armember *bp)
{
char *cp;
pmode(strtoul(bp->hdr.mode, 0, 8));
Bprint(&bout, "%3ld/%1ld", strtol(bp->hdr.uid, 0, 0), strtol(bp->hdr.gid, 0, 0));
Bprint(&bout, "%7ld", bp->size);
cp = ctime(bp->date);
Bprint(&bout, " %-12.12s %-4.4s ", cp+4, cp+24);
}
int m1[] = { 1, ROWN, 'r', '-' };
int m2[] = { 1, WOWN, 'w', '-' };
int m3[] = { 2, SUID, 's', XOWN, 'x', '-' };
int m4[] = { 1, RGRP, 'r', '-' };
int m5[] = { 1, WGRP, 'w', '-' };
int m6[] = { 2, SGID, 's', XGRP, 'x', '-' };
int m7[] = { 1, ROTH, 'r', '-' };
int m8[] = { 1, WOTH, 'w', '-' };
int m9[] = { 2, STXT, 't', XOTH, 'x', '-' };
int *m[] = { m1, m2, m3, m4, m5, m6, m7, m8, m9};
void
pmode(long mode)
{
int **mp;
for(mp = &m[0]; mp < &m[9];)
select(*mp++, mode);
}
void
select(int *ap, long mode)
{
int n;
n = *ap++;
while(--n>=0 && (mode&*ap++)==0)
ap++;
BPUTC(&bout, *ap);
}
/*
* Temp file I/O subsystem. We attempt to cache all three temp files in
* core. When we run out of memory we spill to disk.
* The I/O model assumes that temp files:
* 1) are only written on the end
* 2) are only read from the beginning
* 3) are only read after all writing is complete.
* The architecture uses one control block per temp file. Each control
* block anchors a chain of buffers, each containing an archive member.
*/
Arfile *
newtempfile(char *name) /* allocate a file control block */
{
Arfile *ap;
ap = armalloc(sizeof(Arfile));
ap->fname = name;
return ap;
}
Armember *
newmember(void) /* allocate a member buffer */
{
return armalloc(sizeof(Armember));
}
void
arread(Biobuf *b, Armember *bp) /* read an image into a member buffer */
{
int i;
vlong off;
bp->member = armalloc(bp->size);
// If P flag is set, let arread_cutprefix try.
// If it succeeds, we're done. If not, fall back
// to a direct copy.
off = Boffset(b);
if(Pflag && arread_cutprefix(b, bp))
return;
Bseek(b, off, 0);
i = Bread(b, bp->member, bp->size);
if (i < 0) {
free(bp->member);
bp->member = 0;
rderr();
}
if(bp->size&1)
BGETC(b);
}
/*
* insert a member buffer into the member chain
*/
void
arinsert(Arfile *ap, Armember *bp)
{
bp->next = 0;
if (!ap->tail)
ap->head = bp;
else
ap->tail->next = bp;
ap->tail = bp;
}
/*
* stream the members in a temp file to the file referenced by 'fd'.
*/
void
arstream(int fd, Arfile *ap)
{
Armember *bp;
/* dump the in-core buffers */
for (bp = ap->head; bp; bp = bp->next) {
if (!arwrite(fd, bp))
wrerr();
}
}
/*
* write a member to 'fd'.
*/
int
arwrite(int fd, Armember *bp)
{
int len;
if(HEADER_IO(write, fd, bp->hdr))
return 0;
len = bp->size;
if (len & 01)
len++;
if (write(fd, bp->member, len) != len)
return 0;
return 1;
}
void
arfree(Arfile *ap) /* free a member buffer */
{
Armember *bp, *next;
for (bp = ap->head; bp; bp = next) {
next = bp->next;
if (bp->member)
free(bp->member);
free(bp);
}
free(ap);
}
/*
* allocate space for a control block or member buffer. if the malloc
* fails we try to reclaim space by spilling previously allocated
* member buffers.
*/
void *
armalloc(int n)
{
char *cp;
// bump so that arwrite can do the same
if(n&1)
n++;
cp = malloc(n);
if (cp) {
memset(cp, 0, n);
return cp;
}
fprint(2, "pack: out of memory\n");
exits("malloc");
return 0;
}
char *
arstrdup(char *s)
{
char *t;
t = armalloc(strlen(s) + 1);
strcpy(t, s);
return t;
}
#define isdelim(c) ((c) == '/' || (c) == '\\')
/*
* check if p is start of windows full path, like C:\ or c:/.
* return 1 if so. also set drive parameter to its
* upper-case drive letter.
*/
int
iswinpathstart(char *p, char *drive)
{
if('A' <= p[0] || p[0] <= 'Z')
*drive = p[0];
else if('a' <= p[0] || p[0] <= 'z')
*drive = p[0] - ('a' - 'A');
else
return 0;
return p[1] == ':' && isdelim(p[2]);
}
/*
* copy b into bp->member but rewrite object
* during copy to drop prefix from all file names.
* return 1 if b was recognized as an object file
* and copied successfully, 0 otherwise.
*/
int
arread_cutprefix(Biobuf *b, Armember *bp)
{
// TODO: reimplement
USED(b);
USED(bp);
return 0;
}
// Copyright 2009 The Go Authors. All rights reserved. // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build ignore
/* /*
Pack is a variant of the Plan 9 ar tool. The original is documented at Pack is a simple version of the traditional Unix ar tool.
It implements only the operations needed by Go.
http://plan9.bell-labs.com/magic/man2html/1/ar Usage:
go tool pack op file.a [name...]
It adds a special Go-specific section __.PKGDEF that collects all the Pack applies the operation to the archive, using the names as arguments to the operation.
Go type information from the files in the archive; that section is
used by the compiler when importing the package during compilation.
Usage: The operation op is given by one of these letters:
go tool pack [uvnbailogS][mrxtdpq][P prefix] archive files ...
p print files from the archive
r append files (from the file system) to the archive
t list files from the archive
x extract files from the archive
For the p, t, and x commands, listing no names on the command line
causes the operation to apply to all files in the archive.
The new option 'g' causes pack to maintain the __.PKGDEF section In contrast to Unix ar, the r operation always appends to the archive,
as files are added to the archive. even if a file with the given name already exists in the archive. In this way
pack's r operation is more like Unix ar's rq operation.
The new option 'S' forces pack to mark the archive as safe. Adding the letter v to an operation, as in pv or rv, enables verbose operation:
For the p command, each file is prefixed by the name on a line by itself.
For the r command, names are printed as files are added.
For the t command, the listing includes additional file metadata.
For the x command, names are printed as files are extracted.
The new option 'P' causes pack to remove the given prefix
from file names in the line number information in object files
that are already stored in or added to the archive.
*/ */
package main package main
// 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 (
"fmt"
"io"
"log"
"os"
"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....]
Where op is one of prtx optionally followed by v for verbose output.
For more information, run
godoc cmd/pack`
func usage() {
fmt.Fprintln(os.Stderr, usageMessage)
os.Exit(2)
}
func main() {
log.SetFlags(0)
// need "pack op archive" at least.
if len(os.Args) < 3 {
usage()
}
setOp(os.Args[1])
var ar *Archive
switch op {
case 'p':
ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
ar.scan(ar.printContents)
case 'r':
ar = archive(os.Args[2], os.O_RDWR, os.Args[3:])
ar.scan(ar.skipContents)
ar.addFiles()
case 't':
ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
ar.scan(ar.tableOfContents)
case 'x':
ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
ar.scan(ar.extractContents)
default:
usage()
}
if len(ar.files) > 0 {
log.Fatalf("pack: 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) {
for _, r := range arg {
switch r {
case '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"
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
// from start to end, without backing up.
type Archive struct {
fd *os.File // Open file descriptor.
files []string // Explicit list of files to be processed.
}
// archive opens (or if necessary creates) the named archive.
func archive(name string, mode int, files []string) *Archive {
fd, err := os.OpenFile(name, mode, 0)
if err != nil && mode == os.O_RDWR && os.IsNotExist(err) {
fd, err = create(name)
}
if err != nil {
log.Fatal("pack: ", err)
}
mustBeArchive(fd)
return &Archive{
fd: fd,
files: files,
}
}
// create creates and initializes an archive that does not exist.
func create(name string) (*os.File, error) {
fd, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return nil, err
}
fmt.Fprint(fd, arHeader)
fd.Seek(0, 0)
return fd, nil
}
// mustBeArchive verifies the header of the file. It assumes the file offset
// is 0 coming in, and leaves it positioned immediately after the header.
func mustBeArchive(fd *os.File) {
buf := make([]byte, len(arHeader))
_, err := io.ReadFull(fd, buf)
if err != nil || string(buf) != arHeader {
log.Fatal("pack: file is not an archive: bad header")
}
}
// 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("pack: file is not an archive: bad entry")
}
entry := new(Entry)
entry.name = strings.TrimRight(string(buf[:16]), " ")
if len(entry.name) == 0 {
log.Fatal("pack: 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("pack: 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.
// When action returns, the file offset is at the start of the next entry.
func (ar *Archive) scan(action func(*Entry)) {
for {
entry := ar.readMetadata()
if entry == nil {
break
}
action(entry)
}
}
// listEntry prints to standard output a line describing the entry.
func listEntry(ar *Archive, entry *Entry, verbose bool) {
if verbose {
fmt.Fprintf(stdout, "%s\n", entry)
} else {
fmt.Fprintf(stdout, "%s\n", entry.name)
}
}
// output copies the entry to the specified writer.
func (ar *Archive) output(entry *Entry, w io.Writer) {
n, err := io.Copy(w, io.LimitReader(ar.fd, entry.size))
if err != nil {
log.Fatal("pack: ", err)
}
if n != entry.size {
log.Fatal("pack: short file")
}
if entry.size&1 == 1 {
_, err := ar.fd.Seek(1, 1)
if err != nil {
log.Fatal("pack: ", 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, 1)
if err != nil {
log.Fatal("pack: ", err)
}
}
// 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(entry *Entry) bool {
if len(ar.files) == 0 {
return true
}
for i, name := range ar.files {
if entry.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)
}
fd, err := os.Open(file)
if err != nil {
log.Fatal("pack: ", err)
}
ar.addFile(fd)
}
ar.files = nil
}
// FileLike abstracts the few methods we need, so we can test without needing real files.
type FileLike interface {
Name() string
Stat() (os.FileInfo, error)
Read([]byte) (int, error)
Close() error
}
// addFile adds a single file to the archive
func (ar *Archive) addFile(fd FileLike) {
defer fd.Close()
// Format the entry.
// First, get its info.
info, err := fd.Stat()
if err != nil {
log.Fatal("pack: ", err)
}
// mtime, uid, gid are all zero so repeated builds produce identical output.
mtime := int64(0)
uid := 0
gid := 0
n, err := fmt.Fprintf(ar.fd, entryHeader, exactly16Bytes(info.Name()), mtime, uid, gid, info.Mode(), info.Size())
if err != nil || n != entryLen {
log.Fatal("pack: writing entry header: ", err)
}
n64, err := io.Copy(ar.fd, fd)
if err != nil {
log.Fatal("pack: writing file: ", err)
}
if n64 != info.Size() {
log.Fatal("pack: writing file: wrote %d bytes; file is size %d", n64, info.Size())
}
if info.Size()&1 == 1 {
_, err = ar.fd.Write([]byte{0})
if err != nil {
log.Fatal("pack: writing archive: ", err)
}
}
}
// 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.
// can be modified for testing.
var stdout io.Writer = os.Stdout
// printContents implements the 'p' command.
func (ar *Archive) printContents(entry *Entry) {
if ar.match(entry) {
if verbose {
listEntry(ar, 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.
func (ar *Archive) tableOfContents(entry *Entry) {
if ar.match(entry) {
listEntry(ar, entry, verbose)
}
ar.skip(entry)
}
// extractContents implements the 'x' command.
func (ar *Archive) extractContents(entry *Entry) {
if ar.match(entry) {
if verbose {
listEntry(ar, entry, false)
}
fd, err := os.OpenFile(entry.name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, entry.mode)
if err != nil {
log.Fatal("pack: ", err)
}
ar.output(entry, fd)
fd.Close()
} else {
ar.skip(entry)
}
}
// 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 (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
"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.
func tmpDir(t *testing.T) string {
name, err := ioutil.TempDir("", "pack")
if err != nil {
t.Fatal(err)
}
return name
}
// Test that we can create an archive, write to it, and get the same contents back.
// Tests the rv and then the pv command on a new archive.
func TestCreate(t *testing.T) {
dir := tmpDir(t)
defer os.RemoveAll(dir)
name := filepath.Join(dir, "pack.a")
ar := archive(name, os.O_RDWR, nil)
// Add an entry by hand.
ar.addFile(helloFile.Reset())
ar.fd.Close()
// Now check it.
ar = archive(name, os.O_RDONLY, []string{helloFile.name})
var buf bytes.Buffer
stdout = &buf
verbose = true
defer func() {
stdout = os.Stdout
verbose = false
}()
ar.scan(ar.printContents)
ar.fd.Close()
result := buf.String()
// Expect verbose output plus file contents.
expect := fmt.Sprintf("%s\n%s", helloFile.name, helloFile.contents)
if result != expect {
t.Fatalf("expected %q got %q", expect, result)
}
}
// Test that we can create an archive, put some files in it, and get back a correct listing.
// Tests the tv command.
func TestTableOfContents(t *testing.T) {
dir := tmpDir(t)
defer os.RemoveAll(dir)
name := filepath.Join(dir, "pack.a")
ar := archive(name, os.O_RDWR, nil)
// Add some entries by hand.
ar.addFile(helloFile.Reset())
ar.addFile(goodbyeFile.Reset())
ar.fd.Close()
// Now print it.
ar = archive(name, os.O_RDONLY, nil)
var buf bytes.Buffer
stdout = &buf
verbose = true
defer func() {
stdout = os.Stdout
verbose = false
}()
ar.scan(ar.tableOfContents)
ar.fd.Close()
result := buf.String()
// Expect verbose listing.
expect := fmt.Sprintf("%s\n%s\n", helloFile.Entry(), goodbyeFile.Entry())
if result != expect {
t.Fatalf("expected %q got %q", expect, result)
}
// Do it again without verbose.
verbose = false
buf.Reset()
ar = archive(name, os.O_RDONLY, nil)
ar.scan(ar.tableOfContents)
ar.fd.Close()
result = buf.String()
// Expect non-verbose listing.
expect = fmt.Sprintf("%s\n%s\n", helloFile.name, goodbyeFile.name)
if result != expect {
t.Fatalf("expected %q got %q", expect, result)
}
}
// Test that we can create an archive, put some files in it, and get back a file.
// Tests the x command.
func TestExtract(t *testing.T) {
dir := tmpDir(t)
defer os.RemoveAll(dir)
name := filepath.Join(dir, "pack.a")
ar := archive(name, os.O_RDWR, nil)
// Add some entries by hand.
ar.addFile(helloFile.Reset())
ar.addFile(goodbyeFile.Reset())
ar.fd.Close()
// Now extract one file. We chdir to the directory of the archive for simplicity.
pwd, err := os.Getwd()
if err != nil {
t.Fatal("os.Getwd: ", err)
}
err = os.Chdir(dir)
if err != nil {
t.Fatal("os.Chdir: ", err)
}
defer func() {
err := os.Chdir(pwd)
if err != nil {
t.Fatal("os.Chdir: ", err)
}
}()
ar = archive(name, os.O_RDONLY, []string{goodbyeFile.name})
ar.scan(ar.extractContents)
ar.fd.Close()
data, err := ioutil.ReadFile(goodbyeFile.name)
if err != nil {
t.Fatal(err)
}
// Expect contents of file.
result := string(data)
expect := goodbyeFile.contents
if result != expect {
t.Fatalf("expected %q got %q", expect, result)
}
}
// Fake implementation of files.
var helloFile = &FakeFile{
name: "hello",
contents: "hello world", // 11 bytes, an odd number.
mode: 0644,
}
var goodbyeFile = &FakeFile{
name: "goodbye",
contents: "Sayonara, Jim", // 13 bytes, another odd number.
mode: 0644,
}
// FakeFile implements FileLike and also os.FileInfo.
type FakeFile struct {
name string
contents string
mode os.FileMode
offset int
}
// Reset prepares a FakeFile for reuse.
func (f *FakeFile) Reset() *FakeFile {
f.offset = 0
return f
}
// FileLike methods.
func (f *FakeFile) Name() string {
// A bit of a cheat: we only have a basename, so that's also ok for FileInfo.
return f.name
}
func (f *FakeFile) Stat() (os.FileInfo, error) {
return f, nil
}
func (f *FakeFile) Read(p []byte) (int, error) {
if f.offset >= len(f.contents) {
return 0, io.EOF
}
n := copy(p, f.contents[f.offset:])
f.offset += n
return n, nil
}
func (f *FakeFile) Close() error {
return nil
}
// os.FileInfo methods.
func (f *FakeFile) Size() int64 {
return int64(len(f.contents))
}
func (f *FakeFile) Mode() os.FileMode {
return f.mode
}
func (f *FakeFile) ModTime() time.Time {
return time.Time{}
}
func (f *FakeFile) IsDir() bool {
return false
}
func (f *FakeFile) Sys() interface{} {
return nil
}
// Special helpers.
func (f *FakeFile) Entry() *Entry {
return &Entry{
name: f.name,
mtime: 0, // Defined to be zero.
uid: 0, // Ditto.
gid: 0, // Ditto.
mode: f.mode,
size: int64(len(f.contents)),
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment