97 lines
2.0 KiB
Go
97 lines
2.0 KiB
Go
package fileutil
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
)
|
|
|
|
type LockedFile struct {
|
|
file *os.File
|
|
}
|
|
|
|
func WithLockedFile(path string, fn func() error) error {
|
|
lock, err := acquireLock(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer lock.Close()
|
|
return fn()
|
|
}
|
|
|
|
func AtomicWriteFile(path string, data []byte, perm os.FileMode) error {
|
|
dir := filepath.Dir(path)
|
|
base := filepath.Base(path)
|
|
tmp, err := os.CreateTemp(dir, base+".*.tmp")
|
|
if err != nil {
|
|
return fmt.Errorf("create tmp: %w", err)
|
|
}
|
|
tmpPath := tmp.Name()
|
|
defer func() {
|
|
_ = os.Remove(tmpPath)
|
|
}()
|
|
if err := tmp.Chmod(perm); err != nil {
|
|
_ = tmp.Close()
|
|
return fmt.Errorf("chmod tmp: %w", err)
|
|
}
|
|
if _, err := tmp.Write(data); err != nil {
|
|
_ = tmp.Close()
|
|
return fmt.Errorf("write tmp: %w", err)
|
|
}
|
|
if err := tmp.Sync(); err != nil {
|
|
_ = tmp.Close()
|
|
return fmt.Errorf("sync tmp: %w", err)
|
|
}
|
|
if err := tmp.Close(); err != nil {
|
|
return fmt.Errorf("close tmp: %w", err)
|
|
}
|
|
if err := os.Rename(tmpPath, path); err != nil {
|
|
return fmt.Errorf("rename tmp: %w", err)
|
|
}
|
|
if err := syncDir(dir); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func acquireLock(path string) (*LockedFile, error) {
|
|
lockPath := path + ".lock"
|
|
file, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0o600)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open lock: %w", err)
|
|
}
|
|
if err := syscall.Flock(int(file.Fd()), syscall.LOCK_EX); err != nil {
|
|
_ = file.Close()
|
|
return nil, fmt.Errorf("lock file: %w", err)
|
|
}
|
|
return &LockedFile{file: file}, nil
|
|
}
|
|
|
|
func (l *LockedFile) Close() error {
|
|
if l == nil || l.file == nil {
|
|
return nil
|
|
}
|
|
unlockErr := syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN)
|
|
closeErr := l.file.Close()
|
|
if unlockErr != nil {
|
|
return fmt.Errorf("unlock file: %w", unlockErr)
|
|
}
|
|
if closeErr != nil {
|
|
return fmt.Errorf("close lock: %w", closeErr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func syncDir(dir string) error {
|
|
f, err := os.Open(dir)
|
|
if err != nil {
|
|
return fmt.Errorf("open dir: %w", err)
|
|
}
|
|
defer f.Close()
|
|
if err := f.Sync(); err != nil {
|
|
return fmt.Errorf("sync dir: %w", err)
|
|
}
|
|
return nil
|
|
}
|