folder_recvonly_test.go 13 KB

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