atomic.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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 http://mozilla.org/MPL/2.0/.
  6. package osutil
  7. import (
  8. "errors"
  9. "io/ioutil"
  10. "os"
  11. "path/filepath"
  12. "runtime"
  13. )
  14. var (
  15. ErrClosed = errors.New("write to closed writer")
  16. TempPrefix = ".syncthing.tmp."
  17. )
  18. // An AtomicWriter is an *os.File that writes to a temporary file in the same
  19. // directory as the final path. On successful Close the file is renamed to
  20. // it's final path. Any error on Write or during Close is accumulated and
  21. // returned on Close, so a lazy user can ignore errors until Close.
  22. type AtomicWriter struct {
  23. path string
  24. next *os.File
  25. err error
  26. }
  27. // CreateAtomic is like os.Create with a FileMode, except a temporary file
  28. // name is used instead of the given name.
  29. func CreateAtomic(path string, mode os.FileMode) (*AtomicWriter, error) {
  30. fd, err := ioutil.TempFile(filepath.Dir(path), TempPrefix)
  31. if err != nil {
  32. return nil, err
  33. }
  34. // chmod fails on Android so don't even try
  35. if runtime.GOOS != "android" {
  36. if err := os.Chmod(fd.Name(), mode); err != nil {
  37. fd.Close()
  38. os.Remove(fd.Name())
  39. return nil, err
  40. }
  41. }
  42. w := &AtomicWriter{
  43. path: path,
  44. next: fd,
  45. }
  46. return w, nil
  47. }
  48. // Write is like io.Writer, but is a no-op on an already failed AtomicWriter.
  49. func (w *AtomicWriter) Write(bs []byte) (int, error) {
  50. if w.err != nil {
  51. return 0, w.err
  52. }
  53. n, err := w.next.Write(bs)
  54. if err != nil {
  55. w.err = err
  56. w.next.Close()
  57. }
  58. return n, err
  59. }
  60. // Close closes the temporary file and renames it to the final path. It is
  61. // invalid to call Write() or Close() after Close().
  62. func (w *AtomicWriter) Close() error {
  63. if w.err != nil {
  64. return w.err
  65. }
  66. // Try to not leave temp file around, but ignore error.
  67. defer os.Remove(w.next.Name())
  68. if err := w.next.Sync(); err != nil {
  69. w.err = err
  70. return err
  71. }
  72. if err := w.next.Close(); err != nil {
  73. w.err = err
  74. return err
  75. }
  76. // Remove the destination file, on Windows only. If it fails, and not due
  77. // to the file not existing, we won't be able to complete the rename
  78. // either. Return this error because it may be more informative. On non-
  79. // Windows we want the atomic rename behavior so we don't attempt remove.
  80. if runtime.GOOS == "windows" {
  81. if err := os.Remove(w.path); err != nil && !os.IsNotExist(err) {
  82. return err
  83. }
  84. }
  85. if err := os.Rename(w.next.Name(), w.path); err != nil {
  86. w.err = err
  87. return err
  88. }
  89. SyncDir(filepath.Dir(w.next.Name()))
  90. // Set w.err to return appropriately for any future operations.
  91. w.err = ErrClosed
  92. return nil
  93. }