requests_test.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. // Copyright (C) 2016 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. "errors"
  10. "io/ioutil"
  11. "os"
  12. "path/filepath"
  13. "runtime"
  14. "strings"
  15. "testing"
  16. "time"
  17. "github.com/syncthing/syncthing/lib/config"
  18. "github.com/syncthing/syncthing/lib/events"
  19. "github.com/syncthing/syncthing/lib/fs"
  20. "github.com/syncthing/syncthing/lib/ignore"
  21. "github.com/syncthing/syncthing/lib/protocol"
  22. )
  23. func TestRequestSimple(t *testing.T) {
  24. // Verify that the model performs a request and creates a file based on
  25. // an incoming index update.
  26. m, fc, fcfg := setupModelWithConnection()
  27. tfs := fcfg.Filesystem()
  28. defer cleanupModelAndRemoveDir(m, tfs.URI())
  29. // We listen for incoming index updates and trigger when we see one for
  30. // the expected test file.
  31. done := make(chan struct{})
  32. fc.mut.Lock()
  33. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  34. select {
  35. case <-done:
  36. t.Fatalf("More than one index update sent")
  37. default:
  38. }
  39. for _, f := range fs {
  40. if f.Name == "testfile" {
  41. close(done)
  42. return
  43. }
  44. }
  45. }
  46. fc.mut.Unlock()
  47. // Send an update for the test file, wait for it to sync and be reported back.
  48. contents := []byte("test file contents\n")
  49. fc.addFile("testfile", 0644, protocol.FileInfoTypeFile, contents)
  50. fc.sendIndexUpdate()
  51. <-done
  52. // Verify the contents
  53. if err := equalContents(filepath.Join(tfs.URI(), "testfile"), contents); err != nil {
  54. t.Error("File did not sync correctly:", err)
  55. }
  56. }
  57. func TestSymlinkTraversalRead(t *testing.T) {
  58. // Verify that a symlink can not be traversed for reading.
  59. if runtime.GOOS == "windows" {
  60. t.Skip("no symlink support on CI")
  61. return
  62. }
  63. m, fc, fcfg := setupModelWithConnection()
  64. defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
  65. // We listen for incoming index updates and trigger when we see one for
  66. // the expected test file.
  67. done := make(chan struct{})
  68. fc.mut.Lock()
  69. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  70. select {
  71. case <-done:
  72. t.Fatalf("More than one index update sent")
  73. default:
  74. }
  75. for _, f := range fs {
  76. if f.Name == "symlink" {
  77. close(done)
  78. return
  79. }
  80. }
  81. }
  82. fc.mut.Unlock()
  83. // Send an update for the symlink, wait for it to sync and be reported back.
  84. contents := []byte("..")
  85. fc.addFile("symlink", 0644, protocol.FileInfoTypeSymlink, contents)
  86. fc.sendIndexUpdate()
  87. <-done
  88. // Request a file by traversing the symlink
  89. res, err := m.Request(device1, "default", "symlink/requests_test.go", 10, 0, nil, 0, false)
  90. if err == nil || res != nil {
  91. t.Error("Managed to traverse symlink")
  92. }
  93. }
  94. func TestSymlinkTraversalWrite(t *testing.T) {
  95. // Verify that a symlink can not be traversed for writing.
  96. if runtime.GOOS == "windows" {
  97. t.Skip("no symlink support on CI")
  98. return
  99. }
  100. m, fc, fcfg := setupModelWithConnection()
  101. defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
  102. // We listen for incoming index updates and trigger when we see one for
  103. // the expected names.
  104. done := make(chan struct{}, 1)
  105. badReq := make(chan string, 1)
  106. badIdx := make(chan string, 1)
  107. fc.mut.Lock()
  108. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  109. for _, f := range fs {
  110. if f.Name == "symlink" {
  111. done <- struct{}{}
  112. return
  113. }
  114. if strings.HasPrefix(f.Name, "symlink") {
  115. badIdx <- f.Name
  116. return
  117. }
  118. }
  119. }
  120. fc.requestFn = func(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
  121. if name != "symlink" && strings.HasPrefix(name, "symlink") {
  122. badReq <- name
  123. }
  124. return fc.fileData[name], nil
  125. }
  126. fc.mut.Unlock()
  127. // Send an update for the symlink, wait for it to sync and be reported back.
  128. contents := []byte("..")
  129. fc.addFile("symlink", 0644, protocol.FileInfoTypeSymlink, contents)
  130. fc.sendIndexUpdate()
  131. <-done
  132. // Send an update for things behind the symlink, wait for requests for
  133. // blocks for any of them to come back, or index entries. Hopefully none
  134. // of that should happen.
  135. contents = []byte("testdata testdata\n")
  136. fc.addFile("symlink/testfile", 0644, protocol.FileInfoTypeFile, contents)
  137. fc.addFile("symlink/testdir", 0644, protocol.FileInfoTypeDirectory, contents)
  138. fc.addFile("symlink/testsyml", 0644, protocol.FileInfoTypeSymlink, contents)
  139. fc.sendIndexUpdate()
  140. select {
  141. case name := <-badReq:
  142. t.Fatal("Should not have requested the data for", name)
  143. case name := <-badIdx:
  144. t.Fatal("Should not have sent the index entry for", name)
  145. case <-time.After(3 * time.Second):
  146. // Unfortunately not much else to trigger on here. The puller sleep
  147. // interval is 1s so if we didn't get any requests within two
  148. // iterations we should be fine.
  149. }
  150. }
  151. func TestRequestCreateTmpSymlink(t *testing.T) {
  152. // Test that an update for a temporary file is invalidated
  153. m, fc, fcfg := setupModelWithConnection()
  154. defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
  155. // We listen for incoming index updates and trigger when we see one for
  156. // the expected test file.
  157. goodIdx := make(chan struct{})
  158. name := fs.TempName("testlink")
  159. fc.mut.Lock()
  160. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  161. for _, f := range fs {
  162. if f.Name == name {
  163. if f.IsInvalid() {
  164. goodIdx <- struct{}{}
  165. } else {
  166. t.Fatal("Received index with non-invalid temporary file")
  167. }
  168. return
  169. }
  170. }
  171. }
  172. fc.mut.Unlock()
  173. // Send an update for the test file, wait for it to sync and be reported back.
  174. fc.addFile(name, 0644, protocol.FileInfoTypeSymlink, []byte(".."))
  175. fc.sendIndexUpdate()
  176. select {
  177. case <-goodIdx:
  178. case <-time.After(3 * time.Second):
  179. t.Fatal("Timed out without index entry being sent")
  180. }
  181. }
  182. func TestRequestVersioningSymlinkAttack(t *testing.T) {
  183. if runtime.GOOS == "windows" {
  184. t.Skip("no symlink support on Windows")
  185. }
  186. // Sets up a folder with trashcan versioning and tries to use a
  187. // deleted symlink to escape
  188. w, fcfg := tmpDefaultWrapper()
  189. defer func() {
  190. os.RemoveAll(fcfg.Filesystem().URI())
  191. os.Remove(w.ConfigPath())
  192. }()
  193. fcfg.Versioning = config.VersioningConfiguration{Type: "trashcan"}
  194. w.SetFolder(fcfg)
  195. m, fc := setupModelWithConnectionFromWrapper(w)
  196. defer cleanupModel(m)
  197. // Create a temporary directory that we will use as target to see if
  198. // we can escape to it
  199. tmpdir, err := ioutil.TempDir("", "syncthing-test")
  200. if err != nil {
  201. t.Fatal(err)
  202. }
  203. defer os.RemoveAll(tmpdir)
  204. // We listen for incoming index updates and trigger when we see one for
  205. // the expected test file.
  206. idx := make(chan int)
  207. fc.mut.Lock()
  208. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  209. idx <- len(fs)
  210. }
  211. fc.mut.Unlock()
  212. // Send an update for the test file, wait for it to sync and be reported back.
  213. fc.addFile("foo", 0644, protocol.FileInfoTypeSymlink, []byte(tmpdir))
  214. fc.sendIndexUpdate()
  215. for updates := 0; updates < 1; updates += <-idx {
  216. }
  217. // Delete the symlink, hoping for it to get versioned
  218. fc.deleteFile("foo")
  219. fc.sendIndexUpdate()
  220. for updates := 0; updates < 1; updates += <-idx {
  221. }
  222. // Recreate foo and a file in it with some data
  223. fc.updateFile("foo", 0755, protocol.FileInfoTypeDirectory, nil)
  224. fc.addFile("foo/test", 0644, protocol.FileInfoTypeFile, []byte("testtesttest"))
  225. fc.sendIndexUpdate()
  226. for updates := 0; updates < 1; updates += <-idx {
  227. }
  228. // Remove the test file and see if it escaped
  229. fc.deleteFile("foo/test")
  230. fc.sendIndexUpdate()
  231. for updates := 0; updates < 1; updates += <-idx {
  232. }
  233. path := filepath.Join(tmpdir, "test")
  234. if _, err := os.Lstat(path); !os.IsNotExist(err) {
  235. t.Fatal("File escaped to", path)
  236. }
  237. }
  238. func TestPullInvalidIgnoredSO(t *testing.T) {
  239. pullInvalidIgnored(t, config.FolderTypeSendOnly)
  240. }
  241. func TestPullInvalidIgnoredSR(t *testing.T) {
  242. pullInvalidIgnored(t, config.FolderTypeSendReceive)
  243. }
  244. // This test checks that (un-)ignored/invalid/deleted files are treated as expected.
  245. func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
  246. w := createTmpWrapper(defaultCfgWrapper.RawCopy())
  247. fcfg := testFolderConfigTmp()
  248. fss := fcfg.Filesystem()
  249. fcfg.Type = ft
  250. w.SetFolder(fcfg)
  251. m := setupModel(w)
  252. defer cleanupModelAndRemoveDir(m, fss.URI())
  253. m.RemoveFolder(fcfg)
  254. m.AddFolder(fcfg)
  255. // Reach in and update the ignore matcher to one that always does
  256. // reloads when asked to, instead of checking file mtimes. This is
  257. // because we might be changing the files on disk often enough that the
  258. // mtimes will be unreliable to determine change status.
  259. m.fmut.Lock()
  260. m.folderIgnores["default"] = ignore.New(fss, ignore.WithChangeDetector(newAlwaysChanged()))
  261. m.fmut.Unlock()
  262. m.StartFolder(fcfg.ID)
  263. fc := addFakeConn(m, device1)
  264. fc.folder = "default"
  265. if err := m.SetIgnores("default", []string{"*ignored*"}); err != nil {
  266. panic(err)
  267. }
  268. contents := []byte("test file contents\n")
  269. otherContents := []byte("other test file contents\n")
  270. invIgn := "invalid:ignored"
  271. invDel := "invalid:deleted"
  272. ign := "ignoredNonExisting"
  273. ignExisting := "ignoredExisting"
  274. fc.addFile(invIgn, 0644, protocol.FileInfoTypeFile, contents)
  275. fc.addFile(invDel, 0644, protocol.FileInfoTypeFile, contents)
  276. fc.deleteFile(invDel)
  277. fc.addFile(ign, 0644, protocol.FileInfoTypeFile, contents)
  278. fc.addFile(ignExisting, 0644, protocol.FileInfoTypeFile, contents)
  279. if err := ioutil.WriteFile(filepath.Join(fss.URI(), ignExisting), otherContents, 0644); err != nil {
  280. panic(err)
  281. }
  282. done := make(chan struct{})
  283. fc.mut.Lock()
  284. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  285. expected := map[string]struct{}{invIgn: {}, ign: {}, ignExisting: {}}
  286. for _, f := range fs {
  287. if _, ok := expected[f.Name]; !ok {
  288. t.Errorf("Unexpected file %v was added to index", f.Name)
  289. }
  290. if !f.IsInvalid() {
  291. t.Errorf("File %v wasn't marked as invalid", f.Name)
  292. }
  293. delete(expected, f.Name)
  294. }
  295. for name := range expected {
  296. t.Errorf("File %v wasn't added to index", name)
  297. }
  298. close(done)
  299. }
  300. fc.mut.Unlock()
  301. sub := events.Default.Subscribe(events.FolderErrors)
  302. defer events.Default.Unsubscribe(sub)
  303. fc.sendIndexUpdate()
  304. select {
  305. case ev := <-sub.C():
  306. t.Fatalf("Errors while scanning/pulling: %v", ev)
  307. case <-time.After(5 * time.Second):
  308. t.Fatalf("timed out before index was received")
  309. case <-done:
  310. }
  311. done = make(chan struct{})
  312. fc.mut.Lock()
  313. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  314. expected := map[string]struct{}{ign: {}, ignExisting: {}}
  315. for _, f := range fs {
  316. if _, ok := expected[f.Name]; !ok {
  317. t.Fatalf("Unexpected file %v was updated in index", f.Name)
  318. }
  319. if f.IsInvalid() {
  320. t.Errorf("File %v is still marked as invalid", f.Name)
  321. }
  322. // The unignored files should only have a local version,
  323. // to mark them as in conflict with any other existing versions.
  324. ev := protocol.Vector{}.Update(myID.Short())
  325. if v := f.Version; !v.Equal(ev) {
  326. t.Errorf("File %v has version %v, expected %v", f.Name, v, ev)
  327. }
  328. if f.Name == ign {
  329. if !f.Deleted {
  330. t.Errorf("File %v was not marked as deleted", f.Name)
  331. }
  332. } else if f.Deleted {
  333. t.Errorf("File %v is marked as deleted", f.Name)
  334. }
  335. delete(expected, f.Name)
  336. }
  337. for name := range expected {
  338. t.Errorf("File %v wasn't updated in index", name)
  339. }
  340. close(done)
  341. }
  342. // Make sure pulling doesn't interfere, as index updates are racy and
  343. // thus we cannot distinguish between scan and pull results.
  344. fc.requestFn = func(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
  345. return nil, nil
  346. }
  347. fc.mut.Unlock()
  348. if err := m.SetIgnores("default", []string{"*:ignored*"}); err != nil {
  349. panic(err)
  350. }
  351. select {
  352. case <-time.After(5 * time.Second):
  353. t.Fatalf("timed out before index was received")
  354. case <-done:
  355. }
  356. }
  357. func TestIssue4841(t *testing.T) {
  358. m, fc, fcfg := setupModelWithConnection()
  359. defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
  360. received := make(chan protocol.FileInfo)
  361. fc.mut.Lock()
  362. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  363. if len(fs) != 1 {
  364. t.Fatalf("Sent index with %d files, should be 1", len(fs))
  365. }
  366. if fs[0].Name != "foo" {
  367. t.Fatalf(`Sent index with file %v, should be "foo"`, fs[0].Name)
  368. }
  369. received <- fs[0]
  370. }
  371. fc.mut.Unlock()
  372. // Setup file from remote that was ignored locally
  373. folder := m.folderRunners[defaultFolderConfig.ID].(*sendReceiveFolder)
  374. folder.updateLocals([]protocol.FileInfo{{
  375. Name: "foo",
  376. Type: protocol.FileInfoTypeFile,
  377. LocalFlags: protocol.FlagLocalIgnored,
  378. Version: protocol.Vector{}.Update(device1.Short()),
  379. }})
  380. <-received
  381. // Scan without ignore patterns with "foo" not existing locally
  382. if err := m.ScanFolder("default"); err != nil {
  383. t.Fatal("Failed scanning:", err)
  384. }
  385. f := <-received
  386. if expected := (protocol.Vector{}.Update(myID.Short())); !f.Version.Equal(expected) {
  387. t.Errorf("Got Version == %v, expected %v", f.Version, expected)
  388. }
  389. }
  390. func TestRescanIfHaveInvalidContent(t *testing.T) {
  391. m, fc, fcfg := setupModelWithConnection()
  392. tmpDir := fcfg.Filesystem().URI()
  393. defer cleanupModelAndRemoveDir(m, tmpDir)
  394. payload := []byte("hello")
  395. must(t, ioutil.WriteFile(filepath.Join(tmpDir, "foo"), payload, 0777))
  396. received := make(chan protocol.FileInfo)
  397. fc.mut.Lock()
  398. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  399. if len(fs) != 1 {
  400. t.Fatalf("Sent index with %d files, should be 1", len(fs))
  401. }
  402. if fs[0].Name != "foo" {
  403. t.Fatalf(`Sent index with file %v, should be "foo"`, fs[0].Name)
  404. }
  405. received <- fs[0]
  406. }
  407. fc.mut.Unlock()
  408. // Scan without ignore patterns with "foo" not existing locally
  409. if err := m.ScanFolder("default"); err != nil {
  410. t.Fatal("Failed scanning:", err)
  411. }
  412. f := <-received
  413. if f.Blocks[0].WeakHash != 103547413 {
  414. t.Fatalf("unexpected weak hash: %d != 103547413", f.Blocks[0].WeakHash)
  415. }
  416. res, err := m.Request(device1, "default", "foo", int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
  417. if err != nil {
  418. t.Fatal(err)
  419. }
  420. buf := res.Data()
  421. if !bytes.Equal(buf, payload) {
  422. t.Errorf("%s != %s", buf, payload)
  423. }
  424. payload = []byte("bye")
  425. buf = make([]byte, len(payload))
  426. must(t, ioutil.WriteFile(filepath.Join(tmpDir, "foo"), payload, 0777))
  427. _, err = m.Request(device1, "default", "foo", int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
  428. if err == nil {
  429. t.Fatalf("expected failure")
  430. }
  431. select {
  432. case f := <-received:
  433. if f.Blocks[0].WeakHash != 41943361 {
  434. t.Fatalf("unexpected weak hash: %d != 41943361", f.Blocks[0].WeakHash)
  435. }
  436. case <-time.After(time.Second):
  437. t.Fatalf("timed out")
  438. }
  439. }
  440. func TestParentDeletion(t *testing.T) {
  441. m, fc, fcfg := setupModelWithConnection()
  442. testFs := fcfg.Filesystem()
  443. defer cleanupModelAndRemoveDir(m, testFs.URI())
  444. parent := "foo"
  445. child := filepath.Join(parent, "bar")
  446. received := make(chan []protocol.FileInfo)
  447. fc.addFile(parent, 0777, protocol.FileInfoTypeDirectory, nil)
  448. fc.addFile(child, 0777, protocol.FileInfoTypeDirectory, nil)
  449. fc.mut.Lock()
  450. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  451. received <- fs
  452. }
  453. fc.mut.Unlock()
  454. fc.sendIndexUpdate()
  455. // Get back index from initial setup
  456. select {
  457. case fs := <-received:
  458. if len(fs) != 2 {
  459. t.Fatalf("Sent index with %d files, should be 2", len(fs))
  460. }
  461. case <-time.After(time.Second):
  462. t.Fatalf("timed out")
  463. }
  464. // Delete parent dir
  465. must(t, testFs.RemoveAll(parent))
  466. // Scan only the child dir (not the parent)
  467. if err := m.ScanFolderSubdirs("default", []string{child}); err != nil {
  468. t.Fatal("Failed scanning:", err)
  469. }
  470. select {
  471. case fs := <-received:
  472. if len(fs) != 1 {
  473. t.Fatalf("Sent index with %d files, should be 1", len(fs))
  474. }
  475. if fs[0].Name != child {
  476. t.Fatalf(`Sent index with file "%v", should be "%v"`, fs[0].Name, child)
  477. }
  478. case <-time.After(time.Second):
  479. t.Fatalf("timed out")
  480. }
  481. // Recreate the child dir on the remote
  482. fc.updateFile(child, 0777, protocol.FileInfoTypeDirectory, nil)
  483. fc.sendIndexUpdate()
  484. // Wait for the child dir to be recreated and sent to the remote
  485. select {
  486. case fs := <-received:
  487. l.Debugln("sent:", fs)
  488. found := false
  489. for _, f := range fs {
  490. if f.Name == child {
  491. if f.Deleted {
  492. t.Fatalf(`File "%v" is still deleted`, child)
  493. }
  494. found = true
  495. }
  496. }
  497. if !found {
  498. t.Fatalf(`File "%v" is missing in index`, child)
  499. }
  500. case <-time.After(5 * time.Second):
  501. t.Fatalf("timed out")
  502. }
  503. }
  504. // TestRequestSymlinkWindows checks that symlinks aren't marked as deleted on windows
  505. // Issue: https://github.com/syncthing/syncthing/issues/5125
  506. func TestRequestSymlinkWindows(t *testing.T) {
  507. if runtime.GOOS != "windows" {
  508. t.Skip("windows specific test")
  509. }
  510. m, fc, fcfg := setupModelWithConnection()
  511. defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
  512. done := make(chan struct{})
  513. fc.mut.Lock()
  514. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  515. select {
  516. case <-done:
  517. t.Fatalf("More than one index update sent")
  518. default:
  519. }
  520. // expected first index
  521. if len(fs) != 1 {
  522. t.Fatalf("Expected just one file in index, got %v", fs)
  523. }
  524. f := fs[0]
  525. if f.Name != "link" {
  526. t.Fatalf(`Got file info with path "%v", expected "link"`, f.Name)
  527. }
  528. if !f.IsInvalid() {
  529. t.Errorf(`File info was not marked as invalid`)
  530. }
  531. close(done)
  532. }
  533. fc.mut.Unlock()
  534. fc.addFile("link", 0644, protocol.FileInfoTypeSymlink, nil)
  535. fc.sendIndexUpdate()
  536. select {
  537. case <-done:
  538. case <-time.After(time.Second):
  539. t.Fatalf("timed out before pull was finished")
  540. }
  541. sub := events.Default.Subscribe(events.StateChanged | events.LocalIndexUpdated)
  542. defer events.Default.Unsubscribe(sub)
  543. m.ScanFolder("default")
  544. for {
  545. select {
  546. case ev := <-sub.C():
  547. switch data := ev.Data.(map[string]interface{}); {
  548. case ev.Type == events.LocalIndexUpdated:
  549. t.Fatalf("Local index was updated unexpectedly: %v", data)
  550. case ev.Type == events.StateChanged:
  551. if data["from"] == "scanning" {
  552. return
  553. }
  554. }
  555. case <-time.After(5 * time.Second):
  556. t.Fatalf("Timed out before scan finished")
  557. }
  558. }
  559. }
  560. func equalContents(path string, contents []byte) error {
  561. if bs, err := ioutil.ReadFile(path); err != nil {
  562. return err
  563. } else if !bytes.Equal(bs, contents) {
  564. return errors.New("incorrect data")
  565. }
  566. return nil
  567. }
  568. func TestRequestRemoteRenameChanged(t *testing.T) {
  569. m, fc, fcfg := setupModelWithConnection()
  570. tfs := fcfg.Filesystem()
  571. tmpDir := tfs.URI()
  572. defer cleanupModelAndRemoveDir(m, tfs.URI())
  573. done := make(chan struct{})
  574. fc.mut.Lock()
  575. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  576. select {
  577. case <-done:
  578. t.Fatalf("More than one index update sent")
  579. default:
  580. }
  581. if len(fs) != 2 {
  582. t.Fatalf("Received index with %v indexes instead of 2", len(fs))
  583. }
  584. close(done)
  585. }
  586. fc.mut.Unlock()
  587. // setup
  588. a := "a"
  589. b := "b"
  590. data := map[string][]byte{
  591. a: []byte("aData"),
  592. b: []byte("bData"),
  593. }
  594. for _, n := range [2]string{a, b} {
  595. fc.addFile(n, 0644, protocol.FileInfoTypeFile, data[n])
  596. }
  597. fc.sendIndexUpdate()
  598. select {
  599. case <-done:
  600. case <-time.After(10 * time.Second):
  601. t.Fatal("timed out")
  602. }
  603. for _, n := range [2]string{a, b} {
  604. must(t, equalContents(filepath.Join(tmpDir, n), data[n]))
  605. }
  606. var gotA, gotB, gotConfl bool
  607. done = make(chan struct{})
  608. fc.mut.Lock()
  609. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  610. select {
  611. case <-done:
  612. t.Fatalf("Received more index updates than expected")
  613. default:
  614. }
  615. for _, f := range fs {
  616. switch {
  617. case f.Name == a:
  618. if gotA {
  619. t.Error("Got more than one index update for", f.Name)
  620. }
  621. gotA = true
  622. case f.Name == b:
  623. if gotB {
  624. t.Error("Got more than one index update for", f.Name)
  625. }
  626. gotB = true
  627. case strings.HasPrefix(f.Name, "b.sync-conflict-"):
  628. if gotConfl {
  629. t.Error("Got more than one index update for conflicts of", f.Name)
  630. }
  631. gotConfl = true
  632. default:
  633. t.Error("Got unexpected file in index update", f.Name)
  634. }
  635. }
  636. if gotA && gotB && gotConfl {
  637. close(done)
  638. }
  639. }
  640. fc.mut.Unlock()
  641. fd, err := tfs.OpenFile(b, fs.OptReadWrite, 0644)
  642. if err != nil {
  643. t.Fatal(err)
  644. }
  645. otherData := []byte("otherData")
  646. if _, err = fd.Write(otherData); err != nil {
  647. t.Fatal(err)
  648. }
  649. fd.Close()
  650. // rename
  651. fc.deleteFile(a)
  652. fc.updateFile(b, 0644, protocol.FileInfoTypeFile, data[a])
  653. // Make sure the remote file for b is newer and thus stays global -> local conflict
  654. fc.mut.Lock()
  655. for i := range fc.files {
  656. if fc.files[i].Name == b {
  657. fc.files[i].ModifiedS += 100
  658. break
  659. }
  660. }
  661. fc.mut.Unlock()
  662. fc.sendIndexUpdate()
  663. select {
  664. case <-done:
  665. case <-time.After(10 * time.Second):
  666. t.Errorf("timed out without receiving all expected index updates")
  667. }
  668. // Check outcome
  669. tfs.Walk(".", func(path string, info fs.FileInfo, err error) error {
  670. switch {
  671. case path == a:
  672. t.Errorf(`File "a" was not removed`)
  673. case path == b:
  674. if err := equalContents(filepath.Join(tmpDir, b), data[a]); err != nil {
  675. t.Error(`File "b" has unexpected content (renamed from a on remote)`)
  676. }
  677. case strings.HasPrefix(path, b+".sync-conflict-"):
  678. if err := equalContents(filepath.Join(tmpDir, path), otherData); err != nil {
  679. t.Error(`Sync conflict of "b" has unexptected content`)
  680. }
  681. case path == "." || strings.HasPrefix(path, ".stfolder"):
  682. default:
  683. t.Error("Found unexpected file", path)
  684. }
  685. return nil
  686. })
  687. }
  688. func TestRequestRemoteRenameConflict(t *testing.T) {
  689. m, fc, fcfg := setupModelWithConnection()
  690. tfs := fcfg.Filesystem()
  691. tmpDir := tfs.URI()
  692. defer cleanupModelAndRemoveDir(m, tmpDir)
  693. recv := make(chan int)
  694. fc.mut.Lock()
  695. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  696. recv <- len(fs)
  697. }
  698. fc.mut.Unlock()
  699. // setup
  700. a := "a"
  701. b := "b"
  702. data := map[string][]byte{
  703. a: []byte("aData"),
  704. b: []byte("bData"),
  705. }
  706. for _, n := range [2]string{a, b} {
  707. fc.addFile(n, 0644, protocol.FileInfoTypeFile, data[n])
  708. }
  709. fc.sendIndexUpdate()
  710. select {
  711. case i := <-recv:
  712. if i != 2 {
  713. t.Fatalf("received %v items in index, expected 1", i)
  714. }
  715. case <-time.After(10 * time.Second):
  716. t.Fatal("timed out")
  717. }
  718. for _, n := range [2]string{a, b} {
  719. must(t, equalContents(filepath.Join(tmpDir, n), data[n]))
  720. }
  721. fd, err := tfs.OpenFile(b, fs.OptReadWrite, 0644)
  722. if err != nil {
  723. t.Fatal(err)
  724. }
  725. otherData := []byte("otherData")
  726. if _, err = fd.Write(otherData); err != nil {
  727. t.Fatal(err)
  728. }
  729. fd.Close()
  730. m.ScanFolders()
  731. select {
  732. case i := <-recv:
  733. if i != 1 {
  734. t.Fatalf("received %v items in index, expected 1", i)
  735. }
  736. case <-time.After(10 * time.Second):
  737. t.Fatal("timed out")
  738. }
  739. // make sure the following rename is more recent (not concurrent)
  740. time.Sleep(2 * time.Second)
  741. // rename
  742. fc.deleteFile(a)
  743. fc.updateFile(b, 0644, protocol.FileInfoTypeFile, data[a])
  744. fc.sendIndexUpdate()
  745. select {
  746. case <-recv:
  747. case <-time.After(10 * time.Second):
  748. t.Fatal("timed out")
  749. }
  750. // Check outcome
  751. foundB := false
  752. foundBConfl := false
  753. tfs.Walk(".", func(path string, info fs.FileInfo, err error) error {
  754. switch {
  755. case path == a:
  756. t.Errorf(`File "a" was not removed`)
  757. case path == b:
  758. foundB = true
  759. case strings.HasPrefix(path, b) && strings.Contains(path, ".sync-conflict-"):
  760. foundBConfl = true
  761. }
  762. return nil
  763. })
  764. if !foundB {
  765. t.Errorf(`File "b" was removed`)
  766. }
  767. if !foundBConfl {
  768. t.Errorf(`No conflict file for "b" was created`)
  769. }
  770. }
  771. func TestRequestDeleteChanged(t *testing.T) {
  772. m, fc, fcfg := setupModelWithConnection()
  773. tfs := fcfg.Filesystem()
  774. defer cleanupModelAndRemoveDir(m, tfs.URI())
  775. done := make(chan struct{})
  776. fc.mut.Lock()
  777. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  778. select {
  779. case <-done:
  780. t.Fatalf("More than one index update sent")
  781. default:
  782. }
  783. close(done)
  784. }
  785. fc.mut.Unlock()
  786. // setup
  787. a := "a"
  788. data := []byte("aData")
  789. fc.addFile(a, 0644, protocol.FileInfoTypeFile, data)
  790. fc.sendIndexUpdate()
  791. select {
  792. case <-done:
  793. done = make(chan struct{})
  794. case <-time.After(10 * time.Second):
  795. t.Fatal("timed out")
  796. }
  797. fc.mut.Lock()
  798. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  799. select {
  800. case <-done:
  801. t.Fatalf("More than one index update sent")
  802. default:
  803. }
  804. close(done)
  805. }
  806. fc.mut.Unlock()
  807. fd, err := tfs.OpenFile(a, fs.OptReadWrite, 0644)
  808. if err != nil {
  809. t.Fatal(err)
  810. }
  811. otherData := []byte("otherData")
  812. if _, err = fd.Write(otherData); err != nil {
  813. t.Fatal(err)
  814. }
  815. fd.Close()
  816. // rename
  817. fc.deleteFile(a)
  818. fc.sendIndexUpdate()
  819. select {
  820. case <-done:
  821. case <-time.After(10 * time.Second):
  822. t.Fatal("timed out")
  823. }
  824. // Check outcome
  825. if _, err := tfs.Lstat(a); err != nil {
  826. if fs.IsNotExist(err) {
  827. t.Error(`Modified file "a" was removed`)
  828. } else {
  829. t.Error(`Error stating file "a":`, err)
  830. }
  831. }
  832. }