requests_test.go 25 KB

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