atomic.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. // Copyright (C) 2015 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package osutil
  7. import (
  8. "errors"
  9. "path/filepath"
  10. "runtime"
  11. "github.com/syncthing/syncthing/lib/fs"
  12. )
  13. var (
  14. ErrClosed = errors.New("write to closed writer")
  15. TempPrefix = ".syncthing.tmp."
  16. )
  17. // An AtomicWriter is an *os.File that writes to a temporary file in the same
  18. // directory as the final path. On successful Close the file is renamed to
  19. // its final path. Any error on Write or during Close is accumulated and
  20. // returned on Close, so a lazy user can ignore errors until Close.
  21. type AtomicWriter struct {
  22. path string
  23. next fs.File
  24. fs fs.Filesystem
  25. err error
  26. }
  27. // CreateAtomic is like os.Create, except a temporary file name is used
  28. // instead of the given name. The file is created with secure (0600)
  29. // permissions.
  30. func CreateAtomic(path string) (*AtomicWriter, error) {
  31. fs := fs.NewFilesystem(fs.FilesystemTypeBasic, filepath.Dir(path))
  32. return CreateAtomicFilesystem(fs, filepath.Base(path))
  33. }
  34. // CreateAtomicFilesystem is like os.Create, except a temporary file name is used
  35. // instead of the given name. The file is created with secure (0600)
  36. // permissions.
  37. func CreateAtomicFilesystem(filesystem fs.Filesystem, path string) (*AtomicWriter, error) {
  38. // The security of this depends on the tempfile having secure
  39. // permissions, 0600, from the beginning. This is what ioutil.TempFile
  40. // does. We have a test that verifies that that is the case, should this
  41. // ever change in the standard library in the future.
  42. fd, err := TempFile(filesystem, filepath.Dir(path), TempPrefix)
  43. if err != nil {
  44. return nil, err
  45. }
  46. w := &AtomicWriter{
  47. path: path,
  48. next: fd,
  49. fs: filesystem,
  50. }
  51. return w, nil
  52. }
  53. // Write is like io.Writer, but is a no-op on an already failed AtomicWriter.
  54. func (w *AtomicWriter) Write(bs []byte) (int, error) {
  55. if w.err != nil {
  56. return 0, w.err
  57. }
  58. n, err := w.next.Write(bs)
  59. if err != nil {
  60. w.err = err
  61. w.next.Close()
  62. }
  63. return n, err
  64. }
  65. // Close closes the temporary file and renames it to the final path. It is
  66. // invalid to call Write() or Close() after Close().
  67. func (w *AtomicWriter) Close() error {
  68. if w.err != nil {
  69. return w.err
  70. }
  71. // Try to not leave temp file around, but ignore error.
  72. defer w.fs.Remove(w.next.Name())
  73. // sync() isn't supported everywhere, our best effort will suffice.
  74. _ = w.next.Sync()
  75. if err := w.next.Close(); err != nil {
  76. w.err = err
  77. return err
  78. }
  79. info, infoErr := w.fs.Lstat(w.path)
  80. if infoErr != nil && !fs.IsNotExist(infoErr) {
  81. w.err = infoErr
  82. return infoErr
  83. }
  84. err := w.fs.Rename(w.next.Name(), w.path)
  85. if runtime.GOOS == "windows" && fs.IsPermission(err) {
  86. // On Windows, we might not be allowed to rename over the file
  87. // because it's read-only. Get us some write permissions and try
  88. // again.
  89. _ = w.fs.Chmod(w.path, 0644)
  90. err = w.fs.Rename(w.next.Name(), w.path)
  91. }
  92. if err != nil {
  93. w.err = err
  94. return err
  95. }
  96. if infoErr == nil {
  97. if err := w.fs.Chmod(w.path, info.Mode()); err != nil {
  98. w.err = err
  99. return err
  100. }
  101. }
  102. // fsync the directory too
  103. if fd, err := w.fs.Open(filepath.Dir(w.next.Name())); err == nil {
  104. fd.Sync()
  105. fd.Close()
  106. }
  107. // Set w.err to return appropriately for any future operations.
  108. w.err = ErrClosed
  109. return nil
  110. }