atomic-write.go 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package utils
  3. import (
  4. "errors"
  5. "fmt"
  6. "io"
  7. "io/fs"
  8. "os"
  9. "path/filepath"
  10. )
  11. var _ = fmt.Print
  12. func AtomicCreateSymlink(oldname, newname string) (err error) {
  13. err = os.Symlink(oldname, newname)
  14. if err == nil {
  15. return nil
  16. }
  17. if !errors.Is(err, fs.ErrExist) {
  18. return err
  19. }
  20. if et, err := os.Readlink(newname); err == nil && et == oldname {
  21. return nil
  22. }
  23. for {
  24. tempname := newname + RandomFilename()
  25. err = os.Symlink(oldname, tempname)
  26. if err == nil {
  27. err = os.Rename(tempname, newname)
  28. if err != nil {
  29. os.Remove(tempname)
  30. }
  31. return err
  32. }
  33. if !errors.Is(err, fs.ErrExist) {
  34. return err
  35. }
  36. }
  37. }
  38. func AtomicWriteFile(path string, data io.Reader, perm os.FileMode) (err error) {
  39. npath, err := filepath.EvalSymlinks(path)
  40. if errors.Is(err, fs.ErrNotExist) {
  41. err = nil
  42. npath = path
  43. }
  44. if err == nil {
  45. path = npath
  46. path, err = filepath.Abs(path)
  47. if err == nil {
  48. var f *os.File
  49. f, err = os.CreateTemp(filepath.Dir(path), filepath.Base(path)+".atomic-write-")
  50. if err == nil {
  51. removed := false
  52. defer func() {
  53. if err == nil {
  54. err = f.Close()
  55. } else {
  56. f.Close()
  57. }
  58. if !removed {
  59. os.Remove(f.Name())
  60. removed = true
  61. }
  62. }()
  63. if _, err = io.Copy(f, data); err == nil {
  64. if err = f.Chmod(perm); err == nil {
  65. if err = f.Sync(); err == nil { // Sync before rename to ensure we dont end up with a zero sized file
  66. if err = os.Rename(f.Name(), path); err == nil {
  67. removed = true
  68. }
  69. }
  70. }
  71. }
  72. }
  73. }
  74. }
  75. return
  76. }
  77. func AtomicUpdateFile(path string, data io.Reader, perms ...fs.FileMode) (err error) {
  78. perm := fs.FileMode(0o644)
  79. if len(perms) > 0 {
  80. perm = perms[0]
  81. }
  82. s, err := os.Stat(path)
  83. if err == nil {
  84. perm = s.Mode().Perm()
  85. }
  86. return AtomicWriteFile(path, data, perm)
  87. }