requests_test.go 26 KB

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