You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/cmd/datekeys/atomic.go

75 lines
1.7 KiB

package main
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
)
// checkNew fails if path already exists.
func checkNew(path string) error {
if _, err := os.Lstat(path); err == nil {
return fmt.Errorf("%s already exists; outputs are never overwritten", path)
} else if !errors.Is(err, os.ErrNotExist) {
return err
}
return nil
}
// writeAtomic stages the output in a private temporary file next to path and
// publishes it only after fn succeeded (spec §56). Publication creates path
// without replacing an existing file: a hard link where the file system
// supports it, otherwise an exclusive create and copy. On any failure no
// partial output remains.
func writeAtomic(path string, fn func(io.Writer) error) (err error) {
if err := checkNew(path); err != nil {
return err
}
tmp, err := os.CreateTemp(filepath.Dir(path), ".datekeys-*")
if err != nil {
return err
}
name := tmp.Name()
defer func() {
tmp.Close()
os.Remove(name)
}()
if err := fn(tmp); err != nil {
return err
}
if err := tmp.Sync(); err != nil {
return err
}
if err := tmp.Close(); err != nil {
return err
}
if err := os.Link(name, path); err == nil {
return nil
} else if errors.Is(err, os.ErrExist) {
return fmt.Errorf("%s already exists; outputs are never overwritten", path)
}
return copyExclusive(name, path)
}
func copyExclusive(from, to string) error {
src, err := os.Open(from)
if err != nil {
return err
}
defer src.Close()
dst, err := os.OpenFile(to, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
if err != nil {
return err
}
_, copyErr := io.Copy(dst, src)
syncErr := dst.Sync()
closeErr := dst.Close()
if err := errors.Join(copyErr, syncErr, closeErr); err != nil {
os.Remove(to)
return err
}
return nil
}

Powered by TurnKey Linux.