atomicfile.go 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package atomicfile contains code related to writing to filesystems
  4. // atomically.
  5. //
  6. // This package should be considered internal; its API is not stable.
  7. package atomicfile // import "tailscale.com/atomicfile"
  8. import (
  9. "fmt"
  10. "os"
  11. "path/filepath"
  12. "runtime"
  13. )
  14. // WriteFile writes data to filename+some suffix, then renames it into filename.
  15. // The perm argument is ignored on Windows, but if the target filename already
  16. // exists then the target file's attributes and ACLs are preserved. If the target
  17. // filename already exists but is not a regular file, WriteFile returns an error.
  18. func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
  19. fi, err := os.Stat(filename)
  20. if err == nil && !fi.Mode().IsRegular() {
  21. return fmt.Errorf("%s already exists and is not a regular file", filename)
  22. }
  23. f, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename)+".tmp")
  24. if err != nil {
  25. return err
  26. }
  27. tmpName := f.Name()
  28. defer func() {
  29. if err != nil {
  30. f.Close()
  31. os.Remove(tmpName)
  32. }
  33. }()
  34. if _, err := f.Write(data); err != nil {
  35. return err
  36. }
  37. if runtime.GOOS != "windows" {
  38. if err := f.Chmod(perm); err != nil {
  39. return err
  40. }
  41. }
  42. if err := f.Sync(); err != nil {
  43. return err
  44. }
  45. if err := f.Close(); err != nil {
  46. return err
  47. }
  48. return Rename(tmpName, filename)
  49. }
  50. // Rename srcFile to dstFile, similar to [os.Rename] but preserving file
  51. // attributes and ACLs on Windows.
  52. func Rename(srcFile, dstFile string) error { return rename(srcFile, dstFile) }