folder_recvonly_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. // Copyright (C) 2018 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. "bytes"
  9. "context"
  10. "path/filepath"
  11. "testing"
  12. "time"
  13. "github.com/syncthing/syncthing/lib/config"
  14. "github.com/syncthing/syncthing/lib/fs"
  15. "github.com/syncthing/syncthing/lib/protocol"
  16. "github.com/syncthing/syncthing/lib/scanner"
  17. )
  18. func TestRecvOnlyRevertDeletes(t *testing.T) {
  19. // Make sure that we delete extraneous files and directories when we hit
  20. // Revert.
  21. // Get us a model up and running
  22. m, f, wcfgCancel := setupROFolder(t)
  23. defer wcfgCancel()
  24. ffs := f.Filesystem()
  25. defer cleanupModel(m)
  26. // Create some test data
  27. for _, dir := range []string{".stfolder", "ignDir", "unknownDir"} {
  28. must(t, ffs.MkdirAll(dir, 0755))
  29. }
  30. must(t, writeFile(ffs, "ignDir/ignFile", []byte("hello\n"), 0644))
  31. must(t, writeFile(ffs, "unknownDir/unknownFile", []byte("hello\n"), 0644))
  32. must(t, writeFile(ffs, ".stignore", []byte("ignDir\n"), 0644))
  33. knownFiles := setupKnownFiles(t, ffs, []byte("hello\n"))
  34. // Send and index update for the known stuff
  35. m.Index(device1, "ro", knownFiles)
  36. f.updateLocalsFromScanning(knownFiles)
  37. size := globalSize(t, m, "ro")
  38. if size.Files != 1 || size.Directories != 1 {
  39. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  40. }
  41. // Scan, should discover the other stuff in the folder
  42. must(t, m.ScanFolder("ro"))
  43. // We should now have two files and two directories, with global state unchanged.
  44. size = globalSize(t, m, "ro")
  45. if size.Files != 1 || size.Directories != 1 {
  46. t.Fatalf("Global: expected 2 files and 2 directories: %+v", size)
  47. }
  48. size = localSize(t, m, "ro")
  49. if size.Files != 2 || size.Directories != 2 {
  50. t.Fatalf("Local: expected 2 files and 2 directories: %+v", size)
  51. }
  52. size = receiveOnlyChangedSize(t, m, "ro")
  53. if size.Files+size.Directories == 0 {
  54. t.Fatalf("ROChanged: expected something: %+v", size)
  55. }
  56. // Revert should delete the unknown stuff
  57. m.Revert("ro")
  58. // These should still exist
  59. for _, p := range []string{"knownDir/knownFile", "ignDir/ignFile"} {
  60. if _, err := ffs.Stat(p); err != nil {
  61. t.Error("Unexpected error:", err)
  62. }
  63. }
  64. // These should have been removed
  65. for _, p := range []string{"unknownDir", "unknownDir/unknownFile"} {
  66. if _, err := ffs.Stat(p); !fs.IsNotExist(err) {
  67. t.Error("Unexpected existing thing:", p)
  68. }
  69. }
  70. // We should now have one file and directory again.
  71. size = globalSize(t, m, "ro")
  72. if size.Files != 1 || size.Directories != 1 {
  73. t.Fatalf("Global: expected 1 files and 1 directories: %+v", size)
  74. }
  75. size = localSize(t, m, "ro")
  76. if size.Files != 1 || size.Directories != 1 {
  77. t.Fatalf("Local: expected 1 files and 1 directories: %+v", size)
  78. }
  79. }
  80. func TestRecvOnlyRevertNeeds(t *testing.T) {
  81. // Make sure that a new file gets picked up and considered latest, then
  82. // gets considered old when we hit Revert.
  83. // Get us a model up and running
  84. m, f, wcfgCancel := setupROFolder(t)
  85. defer wcfgCancel()
  86. ffs := f.Filesystem()
  87. defer cleanupModel(m)
  88. // Create some test data
  89. must(t, ffs.MkdirAll(".stfolder", 0755))
  90. oldData := []byte("hello\n")
  91. knownFiles := setupKnownFiles(t, ffs, oldData)
  92. // Send and index update for the known stuff
  93. m.Index(device1, "ro", knownFiles)
  94. f.updateLocalsFromScanning(knownFiles)
  95. // Scan the folder.
  96. must(t, m.ScanFolder("ro"))
  97. // Everything should be in sync.
  98. size := globalSize(t, m, "ro")
  99. if size.Files != 1 || size.Directories != 1 {
  100. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  101. }
  102. size = localSize(t, m, "ro")
  103. if size.Files != 1 || size.Directories != 1 {
  104. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  105. }
  106. size = needSize(t, m, "ro")
  107. if size.Files+size.Directories > 0 {
  108. t.Fatalf("Need: expected nothing: %+v", size)
  109. }
  110. size = receiveOnlyChangedSize(t, m, "ro")
  111. if size.Files+size.Directories > 0 {
  112. t.Fatalf("ROChanged: expected nothing: %+v", size)
  113. }
  114. // Update the file.
  115. newData := []byte("totally different data\n")
  116. must(t, writeFile(ffs, "knownDir/knownFile", newData, 0644))
  117. // Rescan.
  118. must(t, m.ScanFolder("ro"))
  119. // We now have a newer file than the rest of the cluster. Global state should reflect this.
  120. size = globalSize(t, m, "ro")
  121. const sizeOfDir = 128
  122. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
  123. t.Fatalf("Global: expected no change due to the new file: %+v", size)
  124. }
  125. size = localSize(t, m, "ro")
  126. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
  127. t.Fatalf("Local: expected the new file to be reflected: %+v", size)
  128. }
  129. size = needSize(t, m, "ro")
  130. if size.Files+size.Directories > 0 {
  131. t.Fatalf("Need: expected nothing: %+v", size)
  132. }
  133. size = receiveOnlyChangedSize(t, m, "ro")
  134. if size.Files+size.Directories == 0 {
  135. t.Fatalf("ROChanged: expected something: %+v", size)
  136. }
  137. // We hit the Revert button. The file that was new should become old.
  138. m.Revert("ro")
  139. size = globalSize(t, m, "ro")
  140. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
  141. t.Fatalf("Global: expected the global size to revert: %+v", size)
  142. }
  143. size = localSize(t, m, "ro")
  144. if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
  145. t.Fatalf("Local: expected the local size to remain: %+v", size)
  146. }
  147. size = needSize(t, m, "ro")
  148. if size.Files != 1 || size.Bytes != int64(len(oldData)) {
  149. t.Fatalf("Local: expected to need the old file data: %+v", size)
  150. }
  151. }
  152. func TestRecvOnlyUndoChanges(t *testing.T) {
  153. // Get us a model up and running
  154. m, f, wcfgCancel := setupROFolder(t)
  155. defer wcfgCancel()
  156. ffs := f.Filesystem()
  157. defer cleanupModel(m)
  158. // Create some test data
  159. must(t, ffs.MkdirAll(".stfolder", 0755))
  160. oldData := []byte("hello\n")
  161. knownFiles := setupKnownFiles(t, ffs, oldData)
  162. // Send an index update for the known stuff
  163. m.Index(device1, "ro", knownFiles)
  164. f.updateLocalsFromScanning(knownFiles)
  165. // Scan the folder.
  166. must(t, m.ScanFolder("ro"))
  167. // Everything should be in sync.
  168. size := globalSize(t, m, "ro")
  169. if size.Files != 1 || size.Directories != 1 {
  170. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  171. }
  172. size = localSize(t, m, "ro")
  173. if size.Files != 1 || size.Directories != 1 {
  174. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  175. }
  176. size = needSize(t, m, "ro")
  177. if size.Files+size.Directories > 0 {
  178. t.Fatalf("Need: expected nothing: %+v", size)
  179. }
  180. size = receiveOnlyChangedSize(t, m, "ro")
  181. if size.Files+size.Directories > 0 {
  182. t.Fatalf("ROChanged: expected nothing: %+v", size)
  183. }
  184. // Create a file and modify another
  185. const file = "foo"
  186. must(t, writeFile(ffs, file, []byte("hello\n"), 0644))
  187. must(t, writeFile(ffs, "knownDir/knownFile", []byte("bye\n"), 0644))
  188. must(t, m.ScanFolder("ro"))
  189. size = receiveOnlyChangedSize(t, m, "ro")
  190. if size.Files != 2 {
  191. t.Fatalf("Receive only: expected 2 files: %+v", size)
  192. }
  193. // Remove the file again and undo the modification
  194. must(t, ffs.Remove(file))
  195. must(t, writeFile(ffs, "knownDir/knownFile", oldData, 0644))
  196. must(t, ffs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime()))
  197. must(t, m.ScanFolder("ro"))
  198. size = receiveOnlyChangedSize(t, m, "ro")
  199. if size.Files+size.Directories+size.Deleted != 0 {
  200. t.Fatalf("Receive only: expected all zero: %+v", size)
  201. }
  202. }
  203. func TestRecvOnlyDeletedRemoteDrop(t *testing.T) {
  204. // Get us a model up and running
  205. m, f, wcfgCancel := setupROFolder(t)
  206. defer wcfgCancel()
  207. ffs := f.Filesystem()
  208. defer cleanupModel(m)
  209. // Create some test data
  210. must(t, ffs.MkdirAll(".stfolder", 0755))
  211. oldData := []byte("hello\n")
  212. knownFiles := setupKnownFiles(t, ffs, oldData)
  213. // Send an index update for the known stuff
  214. m.Index(device1, "ro", knownFiles)
  215. f.updateLocalsFromScanning(knownFiles)
  216. // Scan the folder.
  217. must(t, m.ScanFolder("ro"))
  218. // Everything should be in sync.
  219. size := globalSize(t, m, "ro")
  220. if size.Files != 1 || size.Directories != 1 {
  221. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  222. }
  223. size = localSize(t, m, "ro")
  224. if size.Files != 1 || size.Directories != 1 {
  225. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  226. }
  227. size = needSize(t, m, "ro")
  228. if size.Files+size.Directories > 0 {
  229. t.Fatalf("Need: expected nothing: %+v", size)
  230. }
  231. size = receiveOnlyChangedSize(t, m, "ro")
  232. if size.Files+size.Directories > 0 {
  233. t.Fatalf("ROChanged: expected nothing: %+v", size)
  234. }
  235. // Delete our file
  236. must(t, ffs.Remove(knownFiles[1].Name))
  237. must(t, m.ScanFolder("ro"))
  238. size = receiveOnlyChangedSize(t, m, "ro")
  239. if size.Deleted != 1 {
  240. t.Fatalf("Receive only: expected 1 deleted: %+v", size)
  241. }
  242. // Drop the remote
  243. f.fset.Drop(device1)
  244. must(t, m.ScanFolder("ro"))
  245. size = receiveOnlyChangedSize(t, m, "ro")
  246. if size.Deleted != 0 {
  247. t.Fatalf("Receive only: expected no deleted: %+v", size)
  248. }
  249. }
  250. func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
  251. // Get us a model up and running
  252. m, f, wcfgCancel := setupROFolder(t)
  253. defer wcfgCancel()
  254. ffs := f.Filesystem()
  255. defer cleanupModel(m)
  256. // Create some test data
  257. must(t, ffs.MkdirAll(".stfolder", 0755))
  258. oldData := []byte("hello\n")
  259. knownFiles := setupKnownFiles(t, ffs, oldData)
  260. // Send an index update for the known stuff
  261. m.Index(device1, "ro", knownFiles)
  262. f.updateLocalsFromScanning(knownFiles)
  263. // Scan the folder.
  264. must(t, m.ScanFolder("ro"))
  265. // Everything should be in sync.
  266. size := globalSize(t, m, "ro")
  267. if size.Files != 1 || size.Directories != 1 {
  268. t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
  269. }
  270. size = localSize(t, m, "ro")
  271. if size.Files != 1 || size.Directories != 1 {
  272. t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
  273. }
  274. size = needSize(t, m, "ro")
  275. if size.Files+size.Directories > 0 {
  276. t.Fatalf("Need: expected nothing: %+v", size)
  277. }
  278. size = receiveOnlyChangedSize(t, m, "ro")
  279. if size.Files+size.Directories > 0 {
  280. t.Fatalf("ROChanged: expected nothing: %+v", size)
  281. }
  282. // Create a file and modify another
  283. const file = "foo"
  284. knownFile := filepath.Join("knownDir", "knownFile")
  285. must(t, writeFile(ffs, file, []byte("hello\n"), 0644))
  286. must(t, writeFile(ffs, knownFile, []byte("bye\n"), 0644))
  287. must(t, m.ScanFolder("ro"))
  288. size = receiveOnlyChangedSize(t, m, "ro")
  289. if size.Files != 2 {
  290. t.Fatalf("Receive only: expected 2 files: %+v", size)
  291. }
  292. // Do the same changes on the remote
  293. files := make([]protocol.FileInfo, 0, 2)
  294. snap := f.fset.Snapshot()
  295. snap.WithHave(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
  296. if n := fi.FileName(); n != file && n != knownFile {
  297. return true
  298. }
  299. f := fi.(protocol.FileInfo)
  300. f.LocalFlags = 0
  301. f.Version = protocol.Vector{}.Update(device1.Short())
  302. files = append(files, f)
  303. return true
  304. })
  305. snap.Release()
  306. m.IndexUpdate(device1, "ro", files)
  307. // Ensure the pull to resolve conflicts (content identical) happened
  308. must(t, f.doInSync(func() error {
  309. f.pull()
  310. return nil
  311. }))
  312. size = receiveOnlyChangedSize(t, m, "ro")
  313. if size.Files+size.Directories+size.Deleted != 0 {
  314. t.Fatalf("Receive only: expected all zero: %+v", size)
  315. }
  316. }
  317. func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.FileInfo {
  318. t.Helper()
  319. must(t, ffs.MkdirAll("knownDir", 0755))
  320. must(t, writeFile(ffs, "knownDir/knownFile", data, 0644))
  321. t0 := time.Now().Add(-1 * time.Minute)
  322. must(t, ffs.Chtimes("knownDir/knownFile", t0, t0))
  323. fi, err := ffs.Stat("knownDir/knownFile")
  324. if err != nil {
  325. t.Fatal(err)
  326. }
  327. blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), protocol.BlockSize(int64(len(data))), int64(len(data)), nil, true)
  328. knownFiles := []protocol.FileInfo{
  329. {
  330. Name: "knownDir",
  331. Type: protocol.FileInfoTypeDirectory,
  332. Permissions: 0755,
  333. Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
  334. Sequence: 42,
  335. },
  336. {
  337. Name: "knownDir/knownFile",
  338. Type: protocol.FileInfoTypeFile,
  339. Permissions: 0644,
  340. Size: fi.Size(),
  341. ModifiedS: fi.ModTime().Unix(),
  342. ModifiedNs: int(fi.ModTime().UnixNano() % 1e9),
  343. Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
  344. Sequence: 42,
  345. Blocks: blocks,
  346. },
  347. }
  348. return knownFiles
  349. }
  350. func setupROFolder(t *testing.T) (*testModel, *receiveOnlyFolder, context.CancelFunc) {
  351. t.Helper()
  352. w, cancel := createTmpWrapper(defaultCfg)
  353. cfg := w.RawCopy()
  354. fcfg := testFolderConfigFake()
  355. fcfg.ID = "ro"
  356. fcfg.Label = "ro"
  357. fcfg.Type = config.FolderTypeReceiveOnly
  358. cfg.Folders = []config.FolderConfiguration{fcfg}
  359. replace(t, w, cfg)
  360. m := newModel(t, w, myID, "syncthing", "dev", nil)
  361. m.ServeBackground()
  362. <-m.started
  363. must(t, m.ScanFolder("ro"))
  364. m.fmut.RLock()
  365. defer m.fmut.RUnlock()
  366. f := m.folderRunners["ro"].(*receiveOnlyFolder)
  367. return m, f, cancel
  368. }
  369. func writeFile(fs fs.Filesystem, filename string, data []byte, perm fs.FileMode) error {
  370. fd, err := fs.Create(filename)
  371. if err != nil {
  372. return err
  373. }
  374. _, err = fd.Write(data)
  375. if err != nil {
  376. return err
  377. }
  378. if err := fd.Close(); err != nil {
  379. return err
  380. }
  381. return fs.Chmod(filename, perm)
  382. }