Commit 1193993c authored by Rob Pike's avatar Rob Pike

cmd/pack: fix c command for existing file

There were at least two bugs:
1) It would overwrite a non-archive.
2) It would truncate a non-archive and then fail.
In general the file handling was too clever to be correct.
Make it more straightforward, doing the creation
separately from archive management.

Fixes #8369.

LGTM=adg, iant
R=golang-codereviews, adg, iant
CC=golang-codereviews
https://golang.org/cl/147010046
parent db492b8d
...@@ -20,6 +20,10 @@ The operation op is given by one of these letters: ...@@ -20,6 +20,10 @@ The operation op is given by one of these letters:
t list files from the archive t list files from the archive
x extract files from the archive x extract files from the archive
The archive argument to the c command must be non-existent or a
valid archive file, which will be cleared before adding new entries. It
is an error if the file exists but is not an archive.
For the p, t, and x commands, listing no names on the command line 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. causes the operation to apply to all files in the archive.
......
...@@ -142,16 +142,19 @@ type Archive struct { ...@@ -142,16 +142,19 @@ type Archive struct {
matchAll bool // match all files in archive matchAll bool // match all files in archive
} }
// archive opens (or 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 archive(name string, mode int, files []string) *Archive {
fd, err := os.OpenFile(name, mode, 0) // If the file exists, it must be an archive. If it doesn't exist, or if
if err != nil && mode&^os.O_TRUNC == os.O_RDWR && os.IsNotExist(err) { // we're doing the c command, indicated by O_TRUNC, truncate the archive.
fd, err = create(name) if !existingArchive(name) || mode&os.O_TRUNC != 0 {
create(name)
mode &^= os.O_TRUNC
} }
fd, err := os.OpenFile(name, mode, 0)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
mustBeArchive(fd) checkHeader(fd)
return &Archive{ return &Archive{
fd: fd, fd: fd,
files: files, files: files,
...@@ -160,23 +163,40 @@ func archive(name string, mode int, files []string) *Archive { ...@@ -160,23 +163,40 @@ func archive(name string, mode int, files []string) *Archive {
} }
// create creates and initializes an archive that does not exist. // create creates and initializes an archive that does not exist.
func create(name string) (*os.File, error) { func create(name string) {
fd, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) fd, err := os.Create(name)
if err != nil { if err != nil {
return nil, err 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.Fatal("cannot open file: %s", err)
} }
fmt.Fprint(fd, arHeader) checkHeader(fd)
fd.Seek(0, 0) fd.Close()
return fd, nil return true
} }
// mustBeArchive verifies the header of the file. It assumes the file offset // checkHeader verifies the header of the file. It assumes the file
// is 0 coming in, and leaves it positioned immediately after the header. // is positioned at 0 and leaves it positioned at the end of the header.
func mustBeArchive(fd *os.File) { func checkHeader(fd *os.File) {
buf := make([]byte, len(arHeader)) buf := make([]byte, len(arHeader))
_, err := io.ReadFull(fd, buf) _, err := io.ReadFull(fd, buf)
if err != nil || string(buf) != arHeader { if err != nil || string(buf) != arHeader {
log.Fatal("file is not an archive: bad header") log.Fatal("%s is not an archive: bad header", fd.Name())
} }
} }
......
...@@ -56,11 +56,8 @@ func tmpDir(t *testing.T) string { ...@@ -56,11 +56,8 @@ func tmpDir(t *testing.T) string {
return name return name
} }
// Test that we can create an archive, write to it, and get the same contents back. // testCreate creates an archive in the specified directory.
// Tests the rv and then the pv command on a new archive. func testCreate(t *testing.T, dir string) {
func TestCreate(t *testing.T) {
dir := tmpDir(t)
defer os.RemoveAll(dir)
name := filepath.Join(dir, "pack.a") name := filepath.Join(dir, "pack.a")
ar := archive(name, os.O_RDWR, nil) ar := archive(name, os.O_RDWR, nil)
// Add an entry by hand. // Add an entry by hand.
...@@ -85,6 +82,22 @@ func TestCreate(t *testing.T) { ...@@ -85,6 +82,22 @@ func TestCreate(t *testing.T) {
} }
} }
// 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)
testCreate(t, dir)
}
// Test that we can create an archive twice with the same name (Issue 8369).
func TestCreateTwice(t *testing.T) {
dir := tmpDir(t)
defer os.RemoveAll(dir)
testCreate(t, dir)
testCreate(t, dir)
}
// Test that we can create an archive, put some files in it, and get back a correct listing. // Test that we can create an archive, put some files in it, and get back a correct listing.
// Tests the tv command. // Tests the tv command.
func TestTableOfContents(t *testing.T) { func TestTableOfContents(t *testing.T) {
......
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