Smail/server/utils/atomicfile/atomicfile.go

99 lines
2.1 KiB
Go

package atomicfile
import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"runtime"
)
func Write(path string, data []byte, perm os.FileMode) error {
directory := filepath.Dir(path)
root, err := os.OpenRoot(directory)
if err != nil {
return err
}
defer root.Close()
return WriteRoot(root, filepath.Base(path), data, perm)
}
func WriteRoot(root *os.Root, name string, data []byte, perm os.FileMode) error {
if root == nil || name == "" || name == "." || filepath.IsAbs(name) {
return fmt.Errorf("invalid atomic file path")
}
directory := filepath.Dir(name)
base := filepath.Base(name)
token := make([]byte, 16)
if _, err := rand.Read(token); err != nil {
return err
}
temporaryName := filepath.Join(directory, "."+base+"-"+hex.EncodeToString(token))
temporary, err := root.OpenFile(temporaryName, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
if err != nil {
return err
}
closed := false
committed := false
defer func() {
if !closed {
_ = temporary.Close()
}
if !committed {
_ = root.Remove(temporaryName)
}
}()
if err = temporary.Chmod(perm); err != nil {
return err
}
written, err := temporary.Write(data)
if err != nil {
return err
}
if written != len(data) {
return fmt.Errorf("short atomic file write: wrote %d of %d bytes", written, len(data))
}
if err = temporary.Sync(); err != nil {
return err
}
if err = temporary.Close(); err != nil {
closed = true
return err
}
closed = true
if err = root.Rename(temporaryName, name); err != nil {
return err
}
committed = true
if err = root.Chmod(name, perm); err != nil {
return err
}
return syncRootDirectory(root, directory)
}
func SyncDirectory(path string) error {
if runtime.GOOS == "windows" {
return nil
}
root, err := os.OpenRoot(path)
if err != nil {
return err
}
defer root.Close()
return syncRootDirectory(root, ".")
}
func syncRootDirectory(root *os.Root, name string) error {
if runtime.GOOS == "windows" {
return nil
}
directory, err := root.Open(name)
if err != nil {
return err
}
defer directory.Close()
return directory.Sync()
}