65 lines
1.6 KiB
Go
65 lines
1.6 KiB
Go
package fileutil
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// WriteFileAtomic replaces path only after data has been written and synced.
|
|
func WriteFileAtomic(path string, data []byte, perm os.FileMode) (err error) {
|
|
dir := filepath.Dir(path)
|
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
|
return fmt.Errorf("create directory: %w", err)
|
|
}
|
|
if err := os.Chmod(dir, 0o700); err != nil {
|
|
return fmt.Errorf("secure directory: %w", err)
|
|
}
|
|
if info, statErr := os.Stat(path); statErr == nil {
|
|
perm &= info.Mode().Perm()
|
|
} else if !os.IsNotExist(statErr) {
|
|
return fmt.Errorf("inspect existing file: %w", statErr)
|
|
}
|
|
|
|
tmp, err := os.CreateTemp(dir, ".tmp-*")
|
|
if err != nil {
|
|
return fmt.Errorf("create temporary file: %w", err)
|
|
}
|
|
tmpPath := tmp.Name()
|
|
defer func() {
|
|
if tmp != nil {
|
|
if closeErr := tmp.Close(); err == nil && closeErr != nil {
|
|
err = fmt.Errorf("close temporary file: %w", closeErr)
|
|
}
|
|
}
|
|
_ = os.Remove(tmpPath)
|
|
}()
|
|
|
|
if err = tmp.Chmod(perm); err != nil {
|
|
return fmt.Errorf("set temporary file permissions: %w", err)
|
|
}
|
|
if _, err = tmp.Write(data); err != nil {
|
|
return fmt.Errorf("write temporary file: %w", err)
|
|
}
|
|
if err = tmp.Sync(); err != nil {
|
|
return fmt.Errorf("sync temporary file: %w", err)
|
|
}
|
|
if err = tmp.Close(); err != nil {
|
|
return fmt.Errorf("close temporary file: %w", err)
|
|
}
|
|
tmp = nil
|
|
if err = os.Rename(tmpPath, path); err != nil {
|
|
return fmt.Errorf("replace file: %w", err)
|
|
}
|
|
|
|
d, openErr := os.Open(dir)
|
|
if openErr != nil {
|
|
return fmt.Errorf("open parent directory: %w", openErr)
|
|
}
|
|
defer d.Close()
|
|
if err = d.Sync(); err != nil {
|
|
return fmt.Errorf("sync parent directory: %w", err)
|
|
}
|
|
return nil
|
|
}
|