From 97b6569a66c5fa08ab2b79620bea5501d994c3a7 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sat, 2 Jan 2021 17:58:29 +0100 Subject: [PATCH] cmd/age: lazily open output file at first write 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 --- cmd/age/age.go | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/cmd/age/age.go b/cmd/age/age.go index 5b2ae81..d4ec087 100644 --- a/cmd/age/age.go +++ b/cmd/age/age.go @@ -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 { @@ -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?" +