fswriters.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package ioutils // import "github.com/docker/docker/pkg/ioutils"
  2. import (
  3. "io"
  4. "os"
  5. "path/filepath"
  6. )
  7. // NewAtomicFileWriter returns WriteCloser so that writing to it writes to a
  8. // temporary file and closing it atomically changes the temporary file to
  9. // destination path. Writing and closing concurrently is not allowed.
  10. func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, error) {
  11. f, err := os.CreateTemp(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
  12. if err != nil {
  13. return nil, err
  14. }
  15. abspath, err := filepath.Abs(filename)
  16. if err != nil {
  17. return nil, err
  18. }
  19. return &atomicFileWriter{
  20. f: f,
  21. fn: abspath,
  22. perm: perm,
  23. }, nil
  24. }
  25. // AtomicWriteFile atomically writes data to a file named by filename.
  26. func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
  27. f, err := NewAtomicFileWriter(filename, perm)
  28. if err != nil {
  29. return err
  30. }
  31. n, err := f.Write(data)
  32. if err == nil && n < len(data) {
  33. err = io.ErrShortWrite
  34. f.(*atomicFileWriter).writeErr = err
  35. }
  36. if err1 := f.Close(); err == nil {
  37. err = err1
  38. }
  39. return err
  40. }
  41. type atomicFileWriter struct {
  42. f *os.File
  43. fn string
  44. writeErr error
  45. perm os.FileMode
  46. }
  47. func (w *atomicFileWriter) Write(dt []byte) (int, error) {
  48. n, err := w.f.Write(dt)
  49. if err != nil {
  50. w.writeErr = err
  51. }
  52. return n, err
  53. }
  54. func (w *atomicFileWriter) Close() (retErr error) {
  55. defer func() {
  56. if retErr != nil || w.writeErr != nil {
  57. os.Remove(w.f.Name())
  58. }
  59. }()
  60. if err := w.f.Sync(); err != nil {
  61. w.f.Close()
  62. return err
  63. }
  64. if err := w.f.Close(); err != nil {
  65. return err
  66. }
  67. if err := os.Chmod(w.f.Name(), w.perm); err != nil {
  68. return err
  69. }
  70. if w.writeErr == nil {
  71. return os.Rename(w.f.Name(), w.fn)
  72. }
  73. return nil
  74. }
  75. // AtomicWriteSet is used to atomically write a set
  76. // of files and ensure they are visible at the same time.
  77. // Must be committed to a new directory.
  78. type AtomicWriteSet struct {
  79. root string
  80. }
  81. // NewAtomicWriteSet creates a new atomic write set to
  82. // atomically create a set of files. The given directory
  83. // is used as the base directory for storing files before
  84. // commit. If no temporary directory is given the system
  85. // default is used.
  86. func NewAtomicWriteSet(tmpDir string) (*AtomicWriteSet, error) {
  87. td, err := os.MkdirTemp(tmpDir, "write-set-")
  88. if err != nil {
  89. return nil, err
  90. }
  91. return &AtomicWriteSet{
  92. root: td,
  93. }, nil
  94. }
  95. // WriteFile writes a file to the set, guaranteeing the file
  96. // has been synced.
  97. func (ws *AtomicWriteSet) WriteFile(filename string, data []byte, perm os.FileMode) error {
  98. f, err := ws.FileWriter(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
  99. if err != nil {
  100. return err
  101. }
  102. n, err := f.Write(data)
  103. if err == nil && n < len(data) {
  104. err = io.ErrShortWrite
  105. }
  106. if err1 := f.Close(); err == nil {
  107. err = err1
  108. }
  109. return err
  110. }
  111. type syncFileCloser struct {
  112. *os.File
  113. }
  114. func (w syncFileCloser) Close() error {
  115. err := w.File.Sync()
  116. if err1 := w.File.Close(); err == nil {
  117. err = err1
  118. }
  119. return err
  120. }
  121. // FileWriter opens a file writer inside the set. The file
  122. // should be synced and closed before calling commit.
  123. func (ws *AtomicWriteSet) FileWriter(name string, flag int, perm os.FileMode) (io.WriteCloser, error) {
  124. f, err := os.OpenFile(filepath.Join(ws.root, name), flag, perm)
  125. if err != nil {
  126. return nil, err
  127. }
  128. return syncFileCloser{f}, nil
  129. }
  130. // Cancel cancels the set and removes all temporary data
  131. // created in the set.
  132. func (ws *AtomicWriteSet) Cancel() error {
  133. return os.RemoveAll(ws.root)
  134. }
  135. // Commit moves all created files to the target directory. The
  136. // target directory must not exist and the parent of the target
  137. // directory must exist.
  138. func (ws *AtomicWriteSet) Commit(target string) error {
  139. return os.Rename(ws.root, target)
  140. }
  141. // String returns the location the set is writing to.
  142. func (ws *AtomicWriteSet) String() string {
  143. return ws.root
  144. }