mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-18 14:47:03 +00:00
This patch extends WriteFile to allow arbitrary operations to be applied to the file before it is atomically renamed. This will be used in upcoming patches to change the mtime of the file before it is atomically renamed.
84 lines
2.1 KiB
Go
84 lines
2.1 KiB
Go
// Package safeio implements convenient I/O routines that provide additional
|
|
// levels of safety in the presence of unexpected failures.
|
|
package safeio
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"syscall"
|
|
)
|
|
|
|
// Type FileOp represents an operation on a file (passed by its name).
|
|
type FileOp func(fname string) error
|
|
|
|
// WriteFile writes data to a file named by filename, atomically.
|
|
//
|
|
// It's a wrapper to ioutil.WriteFile, but provides atomicity (and increased
|
|
// safety) by writing to a temporary file and renaming it at the end.
|
|
//
|
|
// Before the final rename, the given ops (if any) are called. They can be
|
|
// used to manipulate the file before it is atomically renamed.
|
|
// If any operation fails, the file is removed and the error is returned.
|
|
//
|
|
// Note this relies on same-directory Rename being atomic, which holds in most
|
|
// reasonably modern filesystems.
|
|
func WriteFile(filename string, data []byte, perm os.FileMode, ops ...FileOp) error {
|
|
// Note we create the temporary file in the same directory, otherwise we
|
|
// would have no expectation of Rename being atomic.
|
|
// We make the file names start with "." so there's no confusion with the
|
|
// originals.
|
|
tmpf, err := ioutil.TempFile(path.Dir(filename), "."+path.Base(filename))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = tmpf.Chmod(perm); err != nil {
|
|
tmpf.Close()
|
|
os.Remove(tmpf.Name())
|
|
return err
|
|
}
|
|
|
|
if uid, gid := getOwner(filename); uid >= 0 {
|
|
if err = tmpf.Chown(uid, gid); err != nil {
|
|
tmpf.Close()
|
|
os.Remove(tmpf.Name())
|
|
return err
|
|
}
|
|
}
|
|
|
|
if _, err = tmpf.Write(data); err != nil {
|
|
tmpf.Close()
|
|
os.Remove(tmpf.Name())
|
|
return err
|
|
}
|
|
|
|
if err = tmpf.Close(); err != nil {
|
|
os.Remove(tmpf.Name())
|
|
return err
|
|
}
|
|
|
|
for _, op := range ops {
|
|
if err = op(tmpf.Name()); err != nil {
|
|
os.Remove(tmpf.Name())
|
|
return err
|
|
}
|
|
}
|
|
|
|
return os.Rename(tmpf.Name(), filename)
|
|
}
|
|
|
|
func getOwner(fname string) (uid, gid int) {
|
|
uid = -1
|
|
gid = -1
|
|
stat, err := os.Stat(fname)
|
|
if err == nil {
|
|
if sysstat, ok := stat.Sys().(*syscall.Stat_t); ok {
|
|
uid = int(sysstat.Uid)
|
|
gid = int(sysstat.Gid)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|