requests_test.go 25 KB

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