sharedpullerstate.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. // Copyright (C) 2014 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 model
  7. import (
  8. "encoding/binary"
  9. "fmt"
  10. "io"
  11. "sync"
  12. "time"
  13. "google.golang.org/protobuf/proto"
  14. "github.com/syncthing/syncthing/internal/protoutil"
  15. "github.com/syncthing/syncthing/lib/fs"
  16. "github.com/syncthing/syncthing/lib/osutil"
  17. "github.com/syncthing/syncthing/lib/protocol"
  18. )
  19. // A sharedPullerState is kept for each file that is being synced and is kept
  20. // updated along the way.
  21. type sharedPullerState struct {
  22. // Immutable, does not require locking
  23. file protocol.FileInfo // The new file (desired end state)
  24. fs fs.Filesystem
  25. folder string
  26. tempName string
  27. realName string
  28. reused int // Number of blocks reused from temporary file
  29. ignorePerms bool
  30. hasCurFile bool // Whether curFile is set
  31. curFile protocol.FileInfo // The file as it exists now in our database
  32. sparse bool
  33. created time.Time
  34. fsync bool
  35. // Mutable, must be locked for access
  36. err error // The first error we hit
  37. writer *lockedWriterAt // Wraps fd to prevent fd closing at the same time as writing
  38. copyTotal int // Total number of copy actions for the whole job
  39. pullTotal int // Total number of pull actions for the whole job
  40. copyOrigin int // Number of blocks copied from the original file
  41. copyNeeded int // Number of copy actions still pending
  42. pullNeeded int // Number of block pulls still pending
  43. updated time.Time // Time when any of the counters above were last updated
  44. closed bool // True if the file has been finalClosed.
  45. available []int // Indexes of the blocks that are available in the temporary file
  46. availableUpdated time.Time // Time when list of available blocks was last updated
  47. mut sync.RWMutex // Protects the above
  48. }
  49. func newSharedPullerState(file protocol.FileInfo, fs fs.Filesystem, folderID, tempName string, blocks []protocol.BlockInfo, reused []int, ignorePerms, hasCurFile bool, curFile protocol.FileInfo, sparse bool, fsync bool) *sharedPullerState {
  50. return &sharedPullerState{
  51. file: file,
  52. fs: fs,
  53. folder: folderID,
  54. tempName: tempName,
  55. realName: file.Name,
  56. copyTotal: len(blocks),
  57. copyNeeded: len(blocks),
  58. reused: len(reused),
  59. updated: time.Now(),
  60. available: reused,
  61. availableUpdated: time.Now(),
  62. ignorePerms: ignorePerms,
  63. hasCurFile: hasCurFile,
  64. curFile: curFile,
  65. sparse: sparse,
  66. fsync: fsync,
  67. created: time.Now(),
  68. }
  69. }
  70. // A momentary state representing the progress of the puller
  71. type PullerProgress struct {
  72. Total int `json:"total"`
  73. Reused int `json:"reused"`
  74. CopiedFromOrigin int `json:"copiedFromOrigin"`
  75. CopiedFromOriginShifted int `json:"copiedFromOriginShifted"`
  76. CopiedFromElsewhere int `json:"copiedFromElsewhere"`
  77. Pulled int `json:"pulled"`
  78. Pulling int `json:"pulling"`
  79. BytesDone int64 `json:"bytesDone"`
  80. BytesTotal int64 `json:"bytesTotal"`
  81. }
  82. // lockedWriterAt adds a lock to protect from closing the fd at the same time as writing.
  83. // WriteAt() is goroutine safe by itself, but not against for example Close().
  84. type lockedWriterAt struct {
  85. mut sync.RWMutex
  86. fd fs.File
  87. }
  88. // WriteAt itself is goroutine safe, thus just needs to acquire a read-lock to
  89. // prevent closing concurrently (see SyncClose).
  90. func (w *lockedWriterAt) WriteAt(p []byte, off int64) (n int, err error) {
  91. w.mut.RLock()
  92. defer w.mut.RUnlock()
  93. return w.fd.WriteAt(p, off)
  94. }
  95. // SyncClose ensures that no more writes are happening before going ahead and
  96. // syncing and closing the fd, thus needs to acquire a write-lock.
  97. func (w *lockedWriterAt) SyncClose(fsync bool) error {
  98. w.mut.Lock()
  99. defer w.mut.Unlock()
  100. if fsync {
  101. if err := w.fd.Sync(); err != nil {
  102. // Sync() is nice if it works but not worth failing the
  103. // operation over if it fails.
  104. l.Debugf("fsync failed: %v", err)
  105. }
  106. }
  107. return w.fd.Close()
  108. }
  109. // tempFile returns the fd for the temporary file, reusing an open fd
  110. // or creating the file as necessary.
  111. func (s *sharedPullerState) tempFile() (*lockedWriterAt, error) {
  112. s.mut.Lock()
  113. defer s.mut.Unlock()
  114. // If we've already hit an error, return early
  115. if s.err != nil {
  116. return nil, s.err
  117. }
  118. // If the temp file is already open, return the file descriptor
  119. if s.writer != nil {
  120. return s.writer, nil
  121. }
  122. if err := s.addWriterLocked(); err != nil {
  123. s.failLocked(err)
  124. return nil, err
  125. }
  126. return s.writer, nil
  127. }
  128. func (s *sharedPullerState) addWriterLocked() error {
  129. return inWritableDir(s.tempFileInWritableDir, s.fs, s.tempName, s.ignorePerms)
  130. }
  131. // tempFileInWritableDir should only be called from tempFile.
  132. func (s *sharedPullerState) tempFileInWritableDir(_ string) error {
  133. // The permissions to use for the temporary file should be those of the
  134. // final file, except we need user read & write at minimum. The
  135. // permissions will be set to the final value later, but in the meantime
  136. // we don't want to have a temporary file with looser permissions than
  137. // the final outcome.
  138. mode := fs.FileMode(s.file.Permissions) | 0o600
  139. if s.ignorePerms {
  140. // When ignorePerms is set we use a very permissive mode and let the
  141. // system umask filter it.
  142. mode = 0o666
  143. }
  144. // Attempt to create the temp file
  145. // RDWR because of issue #2994.
  146. flags := fs.OptReadWrite
  147. if s.reused == 0 {
  148. flags |= fs.OptCreate | fs.OptExclusive
  149. } else if !s.ignorePerms {
  150. // With sufficiently bad luck when exiting or crashing, we may have
  151. // had time to chmod the temp file to read only state but not yet
  152. // moved it to its final name. This leaves us with a read only temp
  153. // file that we're going to try to reuse. To handle that, we need to
  154. // make sure we have write permissions on the file before opening it.
  155. //
  156. // When ignorePerms is set we trust that the permissions are fine
  157. // already and make no modification, as we would otherwise override
  158. // what the umask dictates.
  159. if err := s.fs.Chmod(s.tempName, mode); err != nil {
  160. return fmt.Errorf("setting perms on temp file: %w", err)
  161. }
  162. }
  163. fd, err := s.fs.OpenFile(s.tempName, flags, mode)
  164. if err != nil {
  165. return fmt.Errorf("opening temp file: %w", err)
  166. }
  167. // Hide the temporary file
  168. s.fs.Hide(s.tempName)
  169. // Don't truncate symlink files, as that will mean that the path will
  170. // contain a bunch of nulls.
  171. if s.sparse && !s.file.IsSymlink() {
  172. size := s.file.Size
  173. // Trailer added to encrypted files
  174. if len(s.file.Encrypted) > 0 {
  175. size += encryptionTrailerSize(s.file)
  176. }
  177. // Truncate sets the size of the file. This creates a sparse file or a
  178. // space reservation, depending on the underlying filesystem.
  179. if err := fd.Truncate(size); err != nil {
  180. // The truncate call failed. That can happen in some cases when
  181. // space reservation isn't possible or over some network
  182. // filesystems... This generally doesn't matter.
  183. if s.reused > 0 {
  184. // ... but if we are attempting to reuse a file we have a
  185. // corner case when the old file is larger than the new one
  186. // and we can't just overwrite blocks and let the old data
  187. // linger at the end. In this case we attempt a delete of
  188. // the file and hope for better luck next time, when we
  189. // should come around with s.reused == 0.
  190. fd.Close()
  191. if remErr := s.fs.Remove(s.tempName); remErr != nil {
  192. l.Debugln("failed to remove temporary file:", remErr)
  193. }
  194. return err
  195. }
  196. }
  197. }
  198. // Same fd will be used by all writers
  199. s.writer = &lockedWriterAt{fd: fd}
  200. return nil
  201. }
  202. // fail sets the error on the puller state compose of error, and marks the
  203. // sharedPullerState as failed. Is a no-op when called on an already failed state.
  204. func (s *sharedPullerState) fail(err error) {
  205. s.mut.Lock()
  206. defer s.mut.Unlock()
  207. s.failLocked(err)
  208. }
  209. func (s *sharedPullerState) failLocked(err error) {
  210. if s.err != nil || err == nil {
  211. return
  212. }
  213. s.err = err
  214. }
  215. func (s *sharedPullerState) failed() error {
  216. s.mut.RLock()
  217. err := s.err
  218. s.mut.RUnlock()
  219. return err
  220. }
  221. func (s *sharedPullerState) copyDone(block protocol.BlockInfo) {
  222. s.mut.Lock()
  223. s.copyNeeded--
  224. s.updated = time.Now()
  225. s.available = append(s.available, int(block.Offset/int64(s.file.BlockSize())))
  226. s.availableUpdated = time.Now()
  227. l.Debugln("sharedPullerState", s.folder, s.file.Name, "copyNeeded ->", s.copyNeeded)
  228. s.mut.Unlock()
  229. }
  230. func (s *sharedPullerState) copiedFromOrigin(bytes int) {
  231. s.mut.Lock()
  232. s.copyOrigin++
  233. s.updated = time.Now()
  234. s.mut.Unlock()
  235. metricFolderProcessedBytesTotal.WithLabelValues(s.folder, metricSourceLocalOrigin).Add(float64(bytes))
  236. }
  237. func (s *sharedPullerState) copiedFromElsewhere(bytes int) {
  238. metricFolderProcessedBytesTotal.WithLabelValues(s.folder, metricSourceLocalOther).Add(float64(bytes))
  239. }
  240. func (s *sharedPullerState) skippedSparseBlock(bytes int) {
  241. // pretend we copied it, historical
  242. s.mut.Lock()
  243. s.copyOrigin++
  244. s.updated = time.Now()
  245. s.mut.Unlock()
  246. metricFolderProcessedBytesTotal.WithLabelValues(s.folder, metricSourceSkipped).Add(float64(bytes))
  247. }
  248. func (s *sharedPullerState) pullStarted() {
  249. s.mut.Lock()
  250. s.copyTotal--
  251. s.copyNeeded--
  252. s.pullTotal++
  253. s.pullNeeded++
  254. s.updated = time.Now()
  255. l.Debugln("sharedPullerState", s.folder, s.file.Name, "pullNeeded start ->", s.pullNeeded)
  256. s.mut.Unlock()
  257. }
  258. func (s *sharedPullerState) pullDone(block protocol.BlockInfo) {
  259. s.mut.Lock()
  260. s.pullNeeded--
  261. s.updated = time.Now()
  262. s.available = append(s.available, int(block.Offset/int64(s.file.BlockSize())))
  263. s.availableUpdated = time.Now()
  264. l.Debugln("sharedPullerState", s.folder, s.file.Name, "pullNeeded done ->", s.pullNeeded)
  265. s.mut.Unlock()
  266. metricFolderProcessedBytesTotal.WithLabelValues(s.folder, metricSourceNetwork).Add(float64(block.Size))
  267. }
  268. // finalClose atomically closes and returns closed status of a file. A true
  269. // first return value means the file was closed and should be finished, with
  270. // the error indicating the success or failure of the close. A false first
  271. // return value indicates the file is not ready to be closed, or is already
  272. // closed and should in either case not be finished off now.
  273. func (s *sharedPullerState) finalClose() (bool, error) {
  274. s.mut.Lock()
  275. defer s.mut.Unlock()
  276. if s.closed {
  277. // Already closed
  278. return false, nil
  279. }
  280. if s.pullNeeded+s.copyNeeded != 0 && s.err == nil {
  281. // Not done yet, and not errored
  282. return false, nil
  283. }
  284. if s.writer == nil {
  285. // If we didn't even create a temp file up to this point, now is the
  286. // time to do so. This also truncates the file to the correct size
  287. // if we're using sparse file.
  288. if err := s.addWriterLocked(); err != nil {
  289. return false, err
  290. }
  291. }
  292. if len(s.file.Encrypted) > 0 {
  293. if err := s.finalizeEncrypted(); err != nil && s.err == nil {
  294. // This is our error as we weren't errored before.
  295. s.err = err
  296. }
  297. }
  298. if s.writer != nil {
  299. if err := s.writer.SyncClose(s.fsync); err != nil && s.err == nil {
  300. // This is our error as we weren't errored before.
  301. s.err = err
  302. }
  303. s.writer = nil
  304. }
  305. s.closed = true
  306. // Unhide the temporary file when we close it, as it's likely to
  307. // immediately be renamed to the final name. If this is a failed temp
  308. // file we will also unhide it, but I'm fine with that as we're now
  309. // leaving it around for potentially quite a while.
  310. s.fs.Unhide(s.tempName)
  311. return true, s.err
  312. }
  313. // finalizeEncrypted adds a trailer to the encrypted file containing the
  314. // serialized FileInfo and the length of that FileInfo. When initializing a
  315. // folder from encrypted data we can extract this FileInfo from the end of
  316. // the file and regain the original metadata.
  317. func (s *sharedPullerState) finalizeEncrypted() error {
  318. trailerSize, err := writeEncryptionTrailer(s.file, s.writer)
  319. if err != nil {
  320. return err
  321. }
  322. s.file.Size += trailerSize
  323. s.file.EncryptionTrailerSize = int(trailerSize)
  324. return nil
  325. }
  326. // Returns the size of the written trailer.
  327. func writeEncryptionTrailer(file protocol.FileInfo, writer io.WriterAt) (int64, error) {
  328. // Here the file is in native format, while encryption happens in
  329. // wire format (always slashes).
  330. wireFile := file
  331. wireFile.Name = osutil.NormalizedFilename(wireFile.Name)
  332. trailerSize := encryptionTrailerSize(wireFile)
  333. bs := make([]byte, trailerSize)
  334. n, err := protoutil.MarshalTo(bs, wireFile.ToWire(false))
  335. if err != nil {
  336. return 0, err
  337. }
  338. binary.BigEndian.PutUint32(bs[n:], uint32(n)) //nolint:gosec
  339. bs = bs[:n+4]
  340. if _, err := writer.WriteAt(bs, wireFile.Size); err != nil {
  341. return 0, err
  342. }
  343. return trailerSize, nil
  344. }
  345. func encryptionTrailerSize(file protocol.FileInfo) int64 {
  346. return int64(proto.Size(file.ToWire(false))) + 4 // XXX: Inefficient
  347. }
  348. // Progress returns the momentarily progress for the puller
  349. func (s *sharedPullerState) Progress() *PullerProgress {
  350. s.mut.RLock()
  351. defer s.mut.RUnlock()
  352. total := s.reused + s.copyTotal + s.pullTotal
  353. done := total - s.copyNeeded - s.pullNeeded
  354. file := len(s.file.Blocks)
  355. return &PullerProgress{
  356. Total: total,
  357. Reused: s.reused,
  358. CopiedFromOrigin: s.copyOrigin,
  359. CopiedFromElsewhere: s.copyTotal - s.copyNeeded - s.copyOrigin,
  360. Pulled: s.pullTotal - s.pullNeeded,
  361. Pulling: s.pullNeeded,
  362. BytesTotal: blocksToSize(total, file, s.file.BlockSize(), s.file.Size),
  363. BytesDone: blocksToSize(done, file, s.file.BlockSize(), s.file.Size),
  364. }
  365. }
  366. // Updated returns the time when any of the progress related counters was last updated.
  367. func (s *sharedPullerState) Updated() time.Time {
  368. s.mut.RLock()
  369. t := s.updated
  370. s.mut.RUnlock()
  371. return t
  372. }
  373. // AvailableUpdated returns the time last time list of available blocks was updated
  374. func (s *sharedPullerState) AvailableUpdated() time.Time {
  375. s.mut.RLock()
  376. t := s.availableUpdated
  377. s.mut.RUnlock()
  378. return t
  379. }
  380. // Available returns blocks available in the current temporary file
  381. func (s *sharedPullerState) Available() []int {
  382. s.mut.RLock()
  383. blocks := s.available
  384. s.mut.RUnlock()
  385. return blocks
  386. }
  387. func blocksToSize(blocks, blocksInFile, blockSize int, fileSize int64) int64 {
  388. // The last/only block has somewhere between 1 and blockSize bytes. We do
  389. // not know whether the smaller block is part of the blocks and use an
  390. // estimate assuming a random chance that the small block is contained.
  391. if blocksInFile == 0 {
  392. return 0
  393. }
  394. return int64(blocks)*int64(blockSize) - (int64(blockSize)-fileSize%int64(blockSize))*int64(blocks)/int64(blocksInFile)
  395. }