sharedpullerstate.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. // Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
  2. // All rights reserved. Use of this source code is governed by an MIT-style
  3. // license that can be found in the LICENSE file.
  4. package model
  5. import (
  6. "os"
  7. "path/filepath"
  8. "sync"
  9. "github.com/syncthing/syncthing/internal/protocol"
  10. )
  11. // A sharedPullerState is kept for each file that is being synced and is kept
  12. // updated along the way.
  13. type sharedPullerState struct {
  14. // Immutable, does not require locking
  15. file protocol.FileInfo
  16. repo string
  17. tempName string
  18. realName string
  19. // Mutable, must be locked for access
  20. err error // The first error we hit
  21. fd *os.File // The fd of the temp file
  22. copyNeeded int // Number of copy actions we expect to happen
  23. pullNeeded int // Number of block pulls we expect to happen
  24. closed bool // Set when the file has been closed
  25. mut sync.Mutex // Protects the above
  26. }
  27. // tempFile returns the fd for the temporary file, reusing an open fd
  28. // or creating the file as necessary.
  29. func (s *sharedPullerState) tempFile() (*os.File, error) {
  30. s.mut.Lock()
  31. defer s.mut.Unlock()
  32. // If we've already hit an error, return early
  33. if s.err != nil {
  34. return nil, s.err
  35. }
  36. // If the temp file is already open, return the file descriptor
  37. if s.fd != nil {
  38. return s.fd, nil
  39. }
  40. // Ensure that the parent directory is writable. This is
  41. // osutil.InWritableDir except we need to do more stuff so we duplicate it
  42. // here.
  43. dir := filepath.Dir(s.tempName)
  44. if info, err := os.Stat(dir); err != nil {
  45. s.earlyCloseLocked("dst stat dir", err)
  46. return nil, err
  47. } else if info.Mode()&04 == 0 {
  48. err := os.Chmod(dir, 0755)
  49. if err == nil {
  50. defer func() {
  51. err := os.Chmod(dir, info.Mode().Perm())
  52. if err != nil {
  53. panic(err)
  54. }
  55. }()
  56. }
  57. }
  58. // Attempt to create the temp file
  59. fd, err := os.OpenFile(s.tempName, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
  60. if err != nil {
  61. s.earlyCloseLocked("dst create", err)
  62. return nil, err
  63. }
  64. // Same fd will be used by all writers
  65. s.fd = fd
  66. return fd, nil
  67. }
  68. // sourceFile opens the existing source file for reading
  69. func (s *sharedPullerState) sourceFile() (*os.File, error) {
  70. s.mut.Lock()
  71. defer s.mut.Unlock()
  72. // If we've already hit an error, return early
  73. if s.err != nil {
  74. return nil, s.err
  75. }
  76. // Attempt to open the existing file
  77. fd, err := os.Open(s.realName)
  78. if err != nil {
  79. s.earlyCloseLocked("src open", err)
  80. return nil, err
  81. }
  82. return fd, nil
  83. }
  84. // earlyClose prints a warning message composed of the context and
  85. // error, and marks the sharedPullerState as failed. Is a no-op when called on
  86. // an already failed state.
  87. func (s *sharedPullerState) earlyClose(context string, err error) {
  88. s.mut.Lock()
  89. defer s.mut.Unlock()
  90. s.earlyCloseLocked(context, err)
  91. }
  92. func (s *sharedPullerState) earlyCloseLocked(context string, err error) {
  93. if s.err != nil {
  94. return
  95. }
  96. l.Infof("Puller (repo %q, file %q): %s: %v", s.repo, s.file.Name, context, err)
  97. s.err = err
  98. if s.fd != nil {
  99. s.fd.Close()
  100. os.Remove(s.tempName)
  101. }
  102. s.closed = true
  103. }
  104. func (s *sharedPullerState) failed() error {
  105. s.mut.Lock()
  106. defer s.mut.Unlock()
  107. return s.err
  108. }
  109. func (s *sharedPullerState) copyDone() {
  110. s.mut.Lock()
  111. s.copyNeeded--
  112. if debug {
  113. l.Debugln("sharedPullerState", s.repo, s.file.Name, "copyNeeded ->", s.pullNeeded)
  114. }
  115. s.mut.Unlock()
  116. }
  117. func (s *sharedPullerState) pullDone() {
  118. s.mut.Lock()
  119. s.pullNeeded--
  120. if debug {
  121. l.Debugln("sharedPullerState", s.repo, s.file.Name, "pullNeeded ->", s.pullNeeded)
  122. }
  123. s.mut.Unlock()
  124. }
  125. // finalClose atomically closes and returns closed status of a file. A true
  126. // first return value means the file was closed and should be finished, with
  127. // the error indicating the success or failure of the close. A false first
  128. // return value indicates the file is not ready to be closed, or is already
  129. // closed and should in either case not be finished off now.
  130. func (s *sharedPullerState) finalClose() (bool, error) {
  131. s.mut.Lock()
  132. defer s.mut.Unlock()
  133. if s.pullNeeded+s.copyNeeded != 0 {
  134. // Not done yet.
  135. return false, nil
  136. }
  137. if s.closed {
  138. // Already handled.
  139. return false, nil
  140. }
  141. s.closed = true
  142. if fd := s.fd; fd != nil {
  143. s.fd = nil
  144. return true, fd.Close()
  145. }
  146. return true, nil
  147. }