Skip to content

Commit

Permalink
cmd/age: lazily open output file at first write
Browse files Browse the repository at this point in the history
This avoids leaving behind an empty file when an error occurs before we
write the header (for example, because the passphrase is invalid). Do a
best-effort check before taking user input for whether the file exists
so we don't waste user effort. An error might still happen after user
input if other kind of open errors happen (for example, a permission
issue, or disk full).

Fixes #159
Fixes #57
Closes #169
  • Loading branch information
FiloSottile committed Jan 3, 2021
1 parent 02ee8b9 commit 97b6569
Showing 1 changed file with 31 additions and 4 deletions.
35 changes: 31 additions & 4 deletions cmd/age/age.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,11 @@ func main() {
stdinInUse = true
}
if name := outFlag; name != "" && name != "-" {
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
logFatalf("Error: failed to open output file %q: %v", name, err)
if _, err := os.Stat(name); err == nil {
logFatalf("Error: output file %q exists", name)
}
defer f.Close()
f, close := lazyOpener(name)
defer close()
out = f
} else if terminal.IsTerminal(int(os.Stdout.Fd())) {
if armorFlag {
Expand Down Expand Up @@ -330,6 +330,33 @@ func passphrasePrompt() (string, error) {
return string(pass), nil
}

type WriterFunc func(p []byte) (n int, err error)

func (f WriterFunc) Write(p []byte) (n int, err error) { return f(p) }

// lazyOpener returns a Writer that opens the named file upon the first Write,
// and a function that calls Close on the file if it has been successfully
// opened, and returns nil otherwise.
func lazyOpener(name string) (w io.Writer, close func() error) {
var f *os.File
var openErr error
write := func(p []byte) (n int, err error) {
if f == nil && openErr == nil {
f, openErr = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
}
if openErr != nil {
return 0, openErr
}
return f.Write(p)
}
return WriterFunc(write), func() error {
if f != nil {
return f.Close()
}
return nil
}
}

func logFatalf(format string, v ...interface{}) {
_log.Printf(format, v...)
_log.Fatalf("[ Did age not do what you expected? Could an error be more useful?" +
Expand Down

0 comments on commit 97b6569

Please sign in to comment.