requests_test.go 29 KB

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