* Added new SafeFile (io.WriteCloser) class + methods to atomically write a file. * Teach core lib to use SafeFile for all file I/O * Teach sigtool to use SafeFile for all file I/O * Cleaned up the public interfaces of sign/ to be more coherent: - with uniform APIs for marshaling, unmarshaling, serialization. - removed KeyPair class/interface and stick to PrivateKey as the primary interface. * collected common rand utility functions into rand.go * Teach sigtool to NOT overwrite existing output files (keys, signatures etc.) * Teach sigtool to use a new --overwrite option for every command that creates files (generate, sign, encrypt, decrypt) * encrypt/decrypt will try to use the input file mode/perm where possible (unless input is stdin). * Added more tests
124 lines
2.8 KiB
Go
124 lines
2.8 KiB
Go
// safefile.go - safe file creation and unwinding on error
|
|
//
|
|
// (c) 2021 Sudhi Herle <sudhi@herle.net>
|
|
//
|
|
// Licensing Terms: GPLv2
|
|
//
|
|
// If you need a commercial license for this work, please contact
|
|
// the author.
|
|
//
|
|
// This software does not come with any express or implied
|
|
// warranty; it is provided "as is". No claim is made to its
|
|
// suitability for any purpose.
|
|
|
|
package sign
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
)
|
|
|
|
// SafeFile is an io.WriteCloser which uses a temporary file that
|
|
// will be atomically renamed when there are no errors and
|
|
// caller invokes Close(). Callers are advised to call
|
|
// Abort() in the appropriate error handling (defer) context
|
|
// so that the temporary file is properly deleted.
|
|
type SafeFile struct {
|
|
*os.File
|
|
|
|
// error for writes recorded once
|
|
err error
|
|
name string // actual filename
|
|
|
|
closed bool // set if the file is closed properly
|
|
}
|
|
|
|
var _ io.WriteCloser = &SafeFile{}
|
|
|
|
// NewSafeFile creates a new temporary file that would either be
|
|
// aborted or safely renamed to the correct name.
|
|
// 'nm' is the name of the final file; if 'ovwrite' is true,
|
|
// then the file is overwritten if it exists.
|
|
func NewSafeFile(nm string, ovwrite bool, flag int, perm os.FileMode) (*SafeFile, error) {
|
|
if _, err := os.Stat(nm); err == nil && !ovwrite {
|
|
return nil, fmt.Errorf("safefile: won't overwrite existing %s", nm)
|
|
}
|
|
|
|
// forcibly unlink the old file - so previous artifacts don't exist
|
|
os.Remove(nm)
|
|
|
|
tmp := fmt.Sprintf("%s.tmp.%d.%x", nm, os.Getpid(), randu32())
|
|
fd, err := os.OpenFile(tmp, flag, perm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sf := &SafeFile{
|
|
File: fd,
|
|
name: nm,
|
|
}
|
|
return sf, nil
|
|
}
|
|
|
|
// Attempt to write everything in 'b' and don't proceed if there was
|
|
// a previous error or the file was already closed.
|
|
func (sf *SafeFile) Write(b []byte) (int, error) {
|
|
if sf.err != nil {
|
|
return 0, sf.err
|
|
}
|
|
|
|
if sf.closed {
|
|
return 0, fmt.Errorf("safefile: %s is closed", sf.Name())
|
|
}
|
|
|
|
var z, nw int
|
|
n := len(b)
|
|
for n > 0 {
|
|
if nw, sf.err = sf.File.Write(b); sf.err != nil {
|
|
return z, sf.err
|
|
}
|
|
z += nw
|
|
n -= nw
|
|
b = b[nw:]
|
|
}
|
|
return z, nil
|
|
}
|
|
|
|
// Abort the file write and remove any temporary artifacts
|
|
func (sf *SafeFile) Abort() {
|
|
// if we've successfully closed, nothing to do!
|
|
if sf.closed {
|
|
return
|
|
}
|
|
|
|
sf.closed = true
|
|
sf.File.Close()
|
|
os.Remove(sf.Name())
|
|
}
|
|
|
|
// Close flushes all file data & metadata to disk, closes the file and atomically renames
|
|
// the temp file to the actual file - ONLY if there were no intervening errors.
|
|
func (sf *SafeFile) Close() error {
|
|
if sf.err != nil {
|
|
sf.Abort()
|
|
return sf.err
|
|
}
|
|
|
|
// mark this file as closed!
|
|
sf.closed = true
|
|
|
|
if sf.err = sf.Sync(); sf.err != nil {
|
|
return sf.err
|
|
}
|
|
|
|
if sf.err = sf.File.Close(); sf.err != nil {
|
|
return sf.err
|
|
}
|
|
|
|
if sf.err = os.Rename(sf.Name(), sf.name); sf.err != nil {
|
|
return sf.err
|
|
}
|
|
|
|
return nil
|
|
}
|