Skip to content

Instantly share code, notes, and snippets.

@sargun
Created March 24, 2020 01:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sargun/4cdea03aed811110901ff261ccd7ecc3 to your computer and use it in GitHub Desktop.
Save sargun/4cdea03aed811110901ff261ccd7ecc3 to your computer and use it in GitHub Desktop.
package main
import (
"fmt"
"math/rand"
"os"
"path/filepath"
"strconv"
"time"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
func main() {
rand.Seed(time.Now().UnixNano())
data := fmt.Sprintf("%d", rand.Int63())
err := atomicWriteOnce("file", []byte(data), 0644)
if err != nil {
panic(err)
}
}
func atomicWriteOnce(path string, data []byte, mode os.FileMode) error {
dir := filepath.Dir(path)
file, err := os.OpenFile(dir, unix.O_TMPFILE|os.O_RDWR, mode) // nolint: gosec
if err != nil {
return err
}
// warning: error return value not checked
defer file.Close()
_, err = file.Write(data)
if err != nil {
return err
}
err = file.Sync()
if err != nil {
return err
}
oldpath := filepath.Join("/proc", "self", "fd", strconv.Itoa(int(file.Fd())))
err = unix.Linkat(unix.AT_FDCWD, oldpath, unix.AT_FDCWD, path, unix.AT_SYMLINK_FOLLOW)
if err == nil {
return nil
} else if err != unix.EEXIST {
return err
}
// Woo, we gotta do the renameat exchange dance
tmpFileName := filepath.Join(filepath.Dir(path), fmt.Sprintf("%d", rand.Int63()))
defer unix.Unlink(tmpFileName)
err = unix.Linkat(unix.AT_FDCWD, oldpath, unix.AT_FDCWD, tmpFileName, unix.AT_SYMLINK_FOLLOW)
if err != nil {
return errors.Wrap(err, "Temp file name already existed!")
}
return unix.Renameat2(unix.AT_FDCWD, tmpFileName, unix.AT_FDCWD, path, unix.RENAME_EXCHANGE)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment