atomic.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  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. if err := w.next.Sync(); err != nil {
  74. w.err = err
  75. return err
  76. }
  77. if err := w.next.Close(); err != nil {
  78. w.err = err
  79. return err
  80. }
  81. // Remove the destination file, on Windows only. If it fails, and not due
  82. // to the file not existing, we won't be able to complete the rename
  83. // either. Return this error because it may be more informative. On non-
  84. // Windows we want the atomic rename behavior so we don't attempt remove.
  85. if runtime.GOOS == "windows" {
  86. if err := w.fs.Remove(w.path); err != nil && !fs.IsNotExist(err) {
  87. return err
  88. }
  89. }
  90. if err := w.fs.Rename(w.next.Name(), w.path); err != nil {
  91. w.err = err
  92. return err
  93. }
  94. // fsync the directory too
  95. if fd, err := w.fs.Open(filepath.Dir(w.next.Name())); err == nil {
  96. fd.Sync()
  97. fd.Close()
  98. }
  99. // Set w.err to return appropriately for any future operations.
  100. w.err = ErrClosed
  101. return nil
  102. }