atomic-write.go 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  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. f.Close()
  54. if !removed {
  55. os.Remove(f.Name())
  56. removed = true
  57. }
  58. }()
  59. if _, err = io.Copy(f, data); err == nil {
  60. if err = f.Chmod(perm); err == nil {
  61. if err = f.Sync(); err == nil { // Sync before rename to ensure we dont end up with a zero sized file
  62. if err = os.Rename(f.Name(), path); err == nil {
  63. removed = true
  64. }
  65. }
  66. }
  67. }
  68. }
  69. }
  70. }
  71. return
  72. }
  73. func AtomicUpdateFile(path string, data io.Reader, perms ...fs.FileMode) (err error) {
  74. perm := fs.FileMode(0o644)
  75. if len(perms) > 0 {
  76. perm = perms[0]
  77. }
  78. s, err := os.Stat(path)
  79. if err == nil {
  80. perm = s.Mode().Perm()
  81. }
  82. return AtomicWriteFile(path, data, perm)
  83. }