sharedpullerstate.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. // Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
  2. //
  3. // This program is free software: you can redistribute it and/or modify it
  4. // under the terms of the GNU General Public License as published by the Free
  5. // Software Foundation, either version 3 of the License, or (at your option)
  6. // any later version.
  7. //
  8. // This program is distributed in the hope that it will be useful, but WITHOUT
  9. // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10. // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  11. // more details.
  12. //
  13. // You should have received a copy of the GNU General Public License along
  14. // with this program. If not, see <http://www.gnu.org/licenses/>.
  15. package model
  16. import (
  17. "os"
  18. "path/filepath"
  19. "sync"
  20. "github.com/syncthing/syncthing/internal/osutil"
  21. "github.com/syncthing/syncthing/internal/protocol"
  22. )
  23. // A sharedPullerState is kept for each file that is being synced and is kept
  24. // updated along the way.
  25. type sharedPullerState struct {
  26. // Immutable, does not require locking
  27. file protocol.FileInfo
  28. folder string
  29. tempName string
  30. realName string
  31. reused int // Number of blocks reused from temporary file
  32. // Mutable, must be locked for access
  33. err error // The first error we hit
  34. fd *os.File // The fd of the temp file
  35. copyTotal int // Total number of copy actions for the whole job
  36. pullTotal int // Total number of pull actions for the whole job
  37. copyNeeded int // Number of copy actions still pending
  38. pullNeeded int // Number of block pulls still pending
  39. copyOrigin int // Number of blocks copied from the original file
  40. closed bool // Set when the file has been closed
  41. mut sync.Mutex // Protects the above
  42. }
  43. // tempFile returns the fd for the temporary file, reusing an open fd
  44. // or creating the file as necessary.
  45. func (s *sharedPullerState) tempFile() (*os.File, error) {
  46. s.mut.Lock()
  47. defer s.mut.Unlock()
  48. // If we've already hit an error, return early
  49. if s.err != nil {
  50. return nil, s.err
  51. }
  52. // If the temp file is already open, return the file descriptor
  53. if s.fd != nil {
  54. return s.fd, nil
  55. }
  56. // Ensure that the parent directory is writable. This is
  57. // osutil.InWritableDir except we need to do more stuff so we duplicate it
  58. // here.
  59. dir := filepath.Dir(s.tempName)
  60. if info, err := os.Stat(dir); err != nil {
  61. s.earlyCloseLocked("dst stat dir", err)
  62. return nil, err
  63. } else if info.Mode()&0200 == 0 {
  64. err := os.Chmod(dir, 0755)
  65. if err == nil {
  66. defer func() {
  67. err := os.Chmod(dir, info.Mode().Perm())
  68. if err != nil {
  69. panic(err)
  70. }
  71. }()
  72. }
  73. }
  74. // Attempt to create the temp file
  75. flags := os.O_WRONLY
  76. if s.reused == 0 {
  77. flags |= os.O_CREATE | os.O_EXCL
  78. }
  79. fd, err := os.OpenFile(s.tempName, flags, 0644)
  80. if err != nil {
  81. s.earlyCloseLocked("dst create", err)
  82. return nil, err
  83. }
  84. // Same fd will be used by all writers
  85. s.fd = fd
  86. return fd, nil
  87. }
  88. // sourceFile opens the existing source file for reading
  89. func (s *sharedPullerState) sourceFile() (*os.File, error) {
  90. s.mut.Lock()
  91. defer s.mut.Unlock()
  92. // If we've already hit an error, return early
  93. if s.err != nil {
  94. return nil, s.err
  95. }
  96. // Attempt to open the existing file
  97. fd, err := os.Open(s.realName)
  98. if err != nil {
  99. s.earlyCloseLocked("src open", err)
  100. return nil, err
  101. }
  102. return fd, nil
  103. }
  104. // earlyClose prints a warning message composed of the context and
  105. // error, and marks the sharedPullerState as failed. Is a no-op when called on
  106. // an already failed state.
  107. func (s *sharedPullerState) earlyClose(context string, err error) {
  108. s.mut.Lock()
  109. defer s.mut.Unlock()
  110. s.earlyCloseLocked(context, err)
  111. }
  112. func (s *sharedPullerState) earlyCloseLocked(context string, err error) {
  113. if s.err != nil {
  114. return
  115. }
  116. l.Infof("Puller (folder %q, file %q): %s: %v", s.folder, s.file.Name, context, err)
  117. s.err = err
  118. if s.fd != nil {
  119. s.fd.Close()
  120. // Delete temporary file, even if parent dir is read-only
  121. osutil.InWritableDir(func(string) error { os.Remove(s.tempName); return nil }, s.tempName)
  122. }
  123. s.closed = true
  124. }
  125. func (s *sharedPullerState) failed() error {
  126. s.mut.Lock()
  127. defer s.mut.Unlock()
  128. return s.err
  129. }
  130. func (s *sharedPullerState) copyDone() {
  131. s.mut.Lock()
  132. s.copyNeeded--
  133. if debug {
  134. l.Debugln("sharedPullerState", s.folder, s.file.Name, "copyNeeded ->", s.copyNeeded)
  135. }
  136. s.mut.Unlock()
  137. }
  138. func (s *sharedPullerState) copiedFromOrigin() {
  139. s.mut.Lock()
  140. s.copyOrigin++
  141. s.mut.Unlock()
  142. }
  143. func (s *sharedPullerState) pullStarted() {
  144. s.mut.Lock()
  145. s.copyTotal--
  146. s.copyNeeded--
  147. s.pullTotal++
  148. s.pullNeeded++
  149. if debug {
  150. l.Debugln("sharedPullerState", s.folder, s.file.Name, "pullNeeded start ->", s.pullNeeded)
  151. }
  152. s.mut.Unlock()
  153. }
  154. func (s *sharedPullerState) pullDone() {
  155. s.mut.Lock()
  156. s.pullNeeded--
  157. if debug {
  158. l.Debugln("sharedPullerState", s.folder, s.file.Name, "pullNeeded done ->", s.pullNeeded)
  159. }
  160. s.mut.Unlock()
  161. }
  162. // finalClose atomically closes and returns closed status of a file. A true
  163. // first return value means the file was closed and should be finished, with
  164. // the error indicating the success or failure of the close. A false first
  165. // return value indicates the file is not ready to be closed, or is already
  166. // closed and should in either case not be finished off now.
  167. func (s *sharedPullerState) finalClose() (bool, error) {
  168. s.mut.Lock()
  169. defer s.mut.Unlock()
  170. if s.pullNeeded+s.copyNeeded != 0 {
  171. // Not done yet.
  172. return false, nil
  173. }
  174. if s.closed {
  175. // Already handled.
  176. return false, nil
  177. }
  178. s.closed = true
  179. if fd := s.fd; fd != nil {
  180. s.fd = nil
  181. return true, fd.Close()
  182. }
  183. return true, nil
  184. }