folder_recvonly_test.go 15 KB

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