requests_test.go 26 KB

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