atomic-write.go 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package utils
  3. import (
  4. "errors"
  5. "fmt"
  6. "io/fs"
  7. "os"
  8. "path/filepath"
  9. )
  10. var _ = fmt.Print
  11. func AtomicCreateSymlink(oldname, newname string) (err error) {
  12. err = os.Symlink(oldname, newname)
  13. if err == nil {
  14. return nil
  15. }
  16. if !errors.Is(err, fs.ErrExist) {
  17. return err
  18. }
  19. if et, err := os.Readlink(newname); err == nil && et == oldname {
  20. return nil
  21. }
  22. for {
  23. tempname := newname + RandomFilename()
  24. err = os.Symlink(oldname, tempname)
  25. if err == nil {
  26. err = os.Rename(tempname, newname)
  27. if err != nil {
  28. os.Remove(tempname)
  29. }
  30. return err
  31. }
  32. if !errors.Is(err, fs.ErrExist) {
  33. return err
  34. }
  35. }
  36. }
  37. func AtomicWriteFile(path string, data []byte, perm os.FileMode) (err error) {
  38. npath, err := filepath.EvalSymlinks(path)
  39. if errors.Is(err, fs.ErrNotExist) {
  40. err = nil
  41. npath = path
  42. }
  43. if err == nil {
  44. path = npath
  45. path, err = filepath.Abs(path)
  46. if err == nil {
  47. var f *os.File
  48. f, err = os.CreateTemp(filepath.Dir(path), filepath.Base(path)+".atomic-write-")
  49. if err == nil {
  50. removed := false
  51. defer func() {
  52. f.Close()
  53. if !removed {
  54. os.Remove(f.Name())
  55. removed = true
  56. }
  57. }()
  58. _, err = f.Write(data)
  59. if err == nil {
  60. err = f.Chmod(perm)
  61. if err == nil {
  62. err = os.Rename(f.Name(), path)
  63. if err == nil {
  64. removed = true
  65. }
  66. }
  67. }
  68. }
  69. }
  70. }
  71. return
  72. }
  73. func AtomicUpdateFile(path string, data []byte, 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. }