opencode-provider-switch/internal/fileutil/fileutil.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
}