model_test.go 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228
  1. // Copyright (C) 2014 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 http://mozilla.org/MPL/2.0/.
  6. package model
  7. import (
  8. "bytes"
  9. "encoding/json"
  10. "fmt"
  11. "io/ioutil"
  12. "math/rand"
  13. "net"
  14. "os"
  15. "path/filepath"
  16. "strconv"
  17. "testing"
  18. "time"
  19. "github.com/syncthing/syncthing/lib/config"
  20. "github.com/syncthing/syncthing/lib/db"
  21. "github.com/syncthing/syncthing/lib/protocol"
  22. "github.com/syndtr/goleveldb/leveldb"
  23. "github.com/syndtr/goleveldb/leveldb/storage"
  24. )
  25. var device1, device2 protocol.DeviceID
  26. var defaultConfig *config.Wrapper
  27. var defaultFolderConfig config.FolderConfiguration
  28. func init() {
  29. device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
  30. device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
  31. defaultFolderConfig = config.FolderConfiguration{
  32. ID: "default",
  33. RawPath: "testdata",
  34. Devices: []config.FolderDeviceConfiguration{
  35. {
  36. DeviceID: device1,
  37. },
  38. },
  39. }
  40. _defaultConfig := config.Configuration{
  41. Folders: []config.FolderConfiguration{defaultFolderConfig},
  42. Devices: []config.DeviceConfiguration{
  43. {
  44. DeviceID: device1,
  45. },
  46. },
  47. Options: config.OptionsConfiguration{
  48. // Don't remove temporaries directly on startup
  49. KeepTemporariesH: 1,
  50. },
  51. }
  52. defaultConfig = config.Wrap("/tmp/test", _defaultConfig)
  53. }
  54. var testDataExpected = map[string]protocol.FileInfo{
  55. "foo": {
  56. Name: "foo",
  57. Flags: 0,
  58. Modified: 0,
  59. Blocks: []protocol.BlockInfo{{Offset: 0x0, Size: 0x7, Hash: []uint8{0xae, 0xc0, 0x70, 0x64, 0x5f, 0xe5, 0x3e, 0xe3, 0xb3, 0x76, 0x30, 0x59, 0x37, 0x61, 0x34, 0xf0, 0x58, 0xcc, 0x33, 0x72, 0x47, 0xc9, 0x78, 0xad, 0xd1, 0x78, 0xb6, 0xcc, 0xdf, 0xb0, 0x1, 0x9f}}},
  60. },
  61. "empty": {
  62. Name: "empty",
  63. Flags: 0,
  64. Modified: 0,
  65. Blocks: []protocol.BlockInfo{{Offset: 0x0, Size: 0x0, Hash: []uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}}},
  66. },
  67. "bar": {
  68. Name: "bar",
  69. Flags: 0,
  70. Modified: 0,
  71. Blocks: []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}},
  72. },
  73. }
  74. func init() {
  75. // Fix expected test data to match reality
  76. for n, f := range testDataExpected {
  77. fi, _ := os.Stat("testdata/" + n)
  78. f.Flags = uint32(fi.Mode())
  79. f.Modified = fi.ModTime().Unix()
  80. testDataExpected[n] = f
  81. }
  82. }
  83. func TestRequest(t *testing.T) {
  84. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  85. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  86. // device1 shares default, but device2 doesn't
  87. m.AddFolder(defaultFolderConfig)
  88. m.StartFolderRO("default")
  89. m.ServeBackground()
  90. m.ScanFolder("default")
  91. bs := make([]byte, protocol.BlockSize)
  92. // Existing, shared file
  93. bs = bs[:6]
  94. err := m.Request(device1, "default", "foo", 0, nil, 0, nil, bs)
  95. if err != nil {
  96. t.Error(err)
  97. }
  98. if bytes.Compare(bs, []byte("foobar")) != 0 {
  99. t.Errorf("Incorrect data from request: %q", string(bs))
  100. }
  101. // Existing, nonshared file
  102. err = m.Request(device2, "default", "foo", 0, nil, 0, nil, bs)
  103. if err == nil {
  104. t.Error("Unexpected nil error on insecure file read")
  105. }
  106. // Nonexistent file
  107. err = m.Request(device1, "default", "nonexistent", 0, nil, 0, nil, bs)
  108. if err == nil {
  109. t.Error("Unexpected nil error on insecure file read")
  110. }
  111. // Shared folder, but disallowed file name
  112. err = m.Request(device1, "default", "../walk.go", 0, nil, 0, nil, bs)
  113. if err == nil {
  114. t.Error("Unexpected nil error on insecure file read")
  115. }
  116. // Negative offset
  117. err = m.Request(device1, "default", "foo", -4, nil, 0, nil, bs[:0])
  118. if err == nil {
  119. t.Error("Unexpected nil error on insecure file read")
  120. }
  121. // Larger block than available
  122. bs = bs[:42]
  123. err = m.Request(device1, "default", "foo", 0, nil, 0, nil, bs)
  124. if err == nil {
  125. t.Error("Unexpected nil error on insecure file read")
  126. }
  127. }
  128. func genFiles(n int) []protocol.FileInfo {
  129. files := make([]protocol.FileInfo, n)
  130. t := time.Now().Unix()
  131. for i := 0; i < n; i++ {
  132. files[i] = protocol.FileInfo{
  133. Name: fmt.Sprintf("file%d", i),
  134. Modified: t,
  135. Blocks: []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}},
  136. }
  137. }
  138. return files
  139. }
  140. func BenchmarkIndex_10000(b *testing.B) {
  141. benchmarkIndex(b, 10000)
  142. }
  143. func BenchmarkIndex_100(b *testing.B) {
  144. benchmarkIndex(b, 100)
  145. }
  146. func benchmarkIndex(b *testing.B, nfiles int) {
  147. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  148. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  149. m.AddFolder(defaultFolderConfig)
  150. m.StartFolderRO("default")
  151. m.ServeBackground()
  152. files := genFiles(nfiles)
  153. m.Index(device1, "default", files, 0, nil)
  154. b.ResetTimer()
  155. for i := 0; i < b.N; i++ {
  156. m.Index(device1, "default", files, 0, nil)
  157. }
  158. b.ReportAllocs()
  159. }
  160. func BenchmarkIndexUpdate_10000_10000(b *testing.B) {
  161. benchmarkIndexUpdate(b, 10000, 10000)
  162. }
  163. func BenchmarkIndexUpdate_10000_100(b *testing.B) {
  164. benchmarkIndexUpdate(b, 10000, 100)
  165. }
  166. func BenchmarkIndexUpdate_10000_1(b *testing.B) {
  167. benchmarkIndexUpdate(b, 10000, 1)
  168. }
  169. func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) {
  170. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  171. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  172. m.AddFolder(defaultFolderConfig)
  173. m.StartFolderRO("default")
  174. m.ServeBackground()
  175. files := genFiles(nfiles)
  176. ufiles := genFiles(nufiles)
  177. m.Index(device1, "default", files, 0, nil)
  178. b.ResetTimer()
  179. for i := 0; i < b.N; i++ {
  180. m.IndexUpdate(device1, "default", ufiles, 0, nil)
  181. }
  182. b.ReportAllocs()
  183. }
  184. type FakeConnection struct {
  185. id protocol.DeviceID
  186. requestData []byte
  187. }
  188. func (FakeConnection) Close() error {
  189. return nil
  190. }
  191. func (f FakeConnection) Start() {
  192. }
  193. func (f FakeConnection) ID() protocol.DeviceID {
  194. return f.id
  195. }
  196. func (f FakeConnection) Name() string {
  197. return ""
  198. }
  199. func (f FakeConnection) Option(string) string {
  200. return ""
  201. }
  202. func (FakeConnection) Index(string, []protocol.FileInfo, uint32, []protocol.Option) error {
  203. return nil
  204. }
  205. func (FakeConnection) IndexUpdate(string, []protocol.FileInfo, uint32, []protocol.Option) error {
  206. return nil
  207. }
  208. func (f FakeConnection) Request(folder, name string, offset int64, size int, hash []byte, flags uint32, options []protocol.Option) ([]byte, error) {
  209. return f.requestData, nil
  210. }
  211. func (FakeConnection) ClusterConfig(protocol.ClusterConfigMessage) {}
  212. func (FakeConnection) Ping() bool {
  213. return true
  214. }
  215. func (FakeConnection) Statistics() protocol.Statistics {
  216. return protocol.Statistics{}
  217. }
  218. func BenchmarkRequest(b *testing.B) {
  219. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  220. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  221. m.AddFolder(defaultFolderConfig)
  222. m.ServeBackground()
  223. m.ScanFolder("default")
  224. const n = 1000
  225. files := make([]protocol.FileInfo, n)
  226. t := time.Now().Unix()
  227. for i := 0; i < n; i++ {
  228. files[i] = protocol.FileInfo{
  229. Name: fmt.Sprintf("file%d", i),
  230. Modified: t,
  231. Blocks: []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}},
  232. }
  233. }
  234. fc := FakeConnection{
  235. id: device1,
  236. requestData: []byte("some data to return"),
  237. }
  238. m.AddConnection(Connection{
  239. &net.TCPConn{},
  240. fc,
  241. ConnectionTypeDirectAccept,
  242. })
  243. m.Index(device1, "default", files, 0, nil)
  244. b.ResetTimer()
  245. for i := 0; i < b.N; i++ {
  246. data, err := m.requestGlobal(device1, "default", files[i%n].Name, 0, 32, nil, 0, nil)
  247. if err != nil {
  248. b.Error(err)
  249. }
  250. if data == nil {
  251. b.Error("nil data")
  252. }
  253. }
  254. }
  255. func TestDeviceRename(t *testing.T) {
  256. ccm := protocol.ClusterConfigMessage{
  257. ClientName: "syncthing",
  258. ClientVersion: "v0.9.4",
  259. }
  260. defer os.Remove("tmpconfig.xml")
  261. rawCfg := config.New(device1)
  262. rawCfg.Devices = []config.DeviceConfiguration{
  263. {
  264. DeviceID: device1,
  265. },
  266. }
  267. cfg := config.Wrap("tmpconfig.xml", rawCfg)
  268. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  269. m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  270. fc := FakeConnection{
  271. id: device1,
  272. requestData: []byte("some data to return"),
  273. }
  274. m.AddConnection(Connection{
  275. &net.TCPConn{},
  276. fc,
  277. ConnectionTypeDirectAccept,
  278. })
  279. m.ServeBackground()
  280. if cfg.Devices()[device1].Name != "" {
  281. t.Errorf("Device already has a name")
  282. }
  283. m.ClusterConfig(device1, ccm)
  284. if cfg.Devices()[device1].Name != "" {
  285. t.Errorf("Device already has a name")
  286. }
  287. ccm.Options = []protocol.Option{
  288. {
  289. Key: "name",
  290. Value: "tester",
  291. },
  292. }
  293. m.ClusterConfig(device1, ccm)
  294. if cfg.Devices()[device1].Name != "tester" {
  295. t.Errorf("Device did not get a name")
  296. }
  297. ccm.Options[0].Value = "tester2"
  298. m.ClusterConfig(device1, ccm)
  299. if cfg.Devices()[device1].Name != "tester" {
  300. t.Errorf("Device name got overwritten")
  301. }
  302. cfgw, err := config.Load("tmpconfig.xml", protocol.LocalDeviceID)
  303. if err != nil {
  304. t.Error(err)
  305. return
  306. }
  307. if cfgw.Devices()[device1].Name != "tester" {
  308. t.Errorf("Device name not saved in config")
  309. }
  310. }
  311. func TestClusterConfig(t *testing.T) {
  312. cfg := config.New(device1)
  313. cfg.Devices = []config.DeviceConfiguration{
  314. {
  315. DeviceID: device1,
  316. Introducer: true,
  317. },
  318. {
  319. DeviceID: device2,
  320. },
  321. }
  322. cfg.Folders = []config.FolderConfiguration{
  323. {
  324. ID: "folder1",
  325. Devices: []config.FolderDeviceConfiguration{
  326. {DeviceID: device1},
  327. {DeviceID: device2},
  328. },
  329. },
  330. {
  331. ID: "folder2",
  332. Devices: []config.FolderDeviceConfiguration{
  333. {DeviceID: device1},
  334. {DeviceID: device2},
  335. },
  336. },
  337. }
  338. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  339. m := NewModel(config.Wrap("/tmp/test", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  340. m.AddFolder(cfg.Folders[0])
  341. m.AddFolder(cfg.Folders[1])
  342. m.ServeBackground()
  343. cm := m.clusterConfig(device2)
  344. if l := len(cm.Folders); l != 2 {
  345. t.Fatalf("Incorrect number of folders %d != 2", l)
  346. }
  347. r := cm.Folders[0]
  348. if r.ID != "folder1" {
  349. t.Errorf("Incorrect folder %q != folder1", r.ID)
  350. }
  351. if l := len(r.Devices); l != 2 {
  352. t.Errorf("Incorrect number of devices %d != 2", l)
  353. }
  354. if id := r.Devices[0].ID; bytes.Compare(id, device1[:]) != 0 {
  355. t.Errorf("Incorrect device ID %x != %x", id, device1)
  356. }
  357. if r.Devices[0].Flags&protocol.FlagIntroducer == 0 {
  358. t.Error("Device1 should be flagged as Introducer")
  359. }
  360. if id := r.Devices[1].ID; bytes.Compare(id, device2[:]) != 0 {
  361. t.Errorf("Incorrect device ID %x != %x", id, device2)
  362. }
  363. if r.Devices[1].Flags&protocol.FlagIntroducer != 0 {
  364. t.Error("Device2 should not be flagged as Introducer")
  365. }
  366. r = cm.Folders[1]
  367. if r.ID != "folder2" {
  368. t.Errorf("Incorrect folder %q != folder2", r.ID)
  369. }
  370. if l := len(r.Devices); l != 2 {
  371. t.Errorf("Incorrect number of devices %d != 2", l)
  372. }
  373. if id := r.Devices[0].ID; bytes.Compare(id, device1[:]) != 0 {
  374. t.Errorf("Incorrect device ID %x != %x", id, device1)
  375. }
  376. if r.Devices[0].Flags&protocol.FlagIntroducer == 0 {
  377. t.Error("Device1 should be flagged as Introducer")
  378. }
  379. if id := r.Devices[1].ID; bytes.Compare(id, device2[:]) != 0 {
  380. t.Errorf("Incorrect device ID %x != %x", id, device2)
  381. }
  382. if r.Devices[1].Flags&protocol.FlagIntroducer != 0 {
  383. t.Error("Device2 should not be flagged as Introducer")
  384. }
  385. }
  386. func TestIgnores(t *testing.T) {
  387. arrEqual := func(a, b []string) bool {
  388. if len(a) != len(b) {
  389. return false
  390. }
  391. for i := range a {
  392. if a[i] != b[i] {
  393. return false
  394. }
  395. }
  396. return true
  397. }
  398. // Assure a clean start state
  399. ioutil.WriteFile("testdata/.stfolder", nil, 0644)
  400. ioutil.WriteFile("testdata/.stignore", []byte(".*\nquux\n"), 0644)
  401. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  402. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  403. m.AddFolder(defaultFolderConfig)
  404. m.StartFolderRO("default")
  405. m.ServeBackground()
  406. expected := []string{
  407. ".*",
  408. "quux",
  409. }
  410. ignores, _, err := m.GetIgnores("default")
  411. if err != nil {
  412. t.Error(err)
  413. }
  414. if !arrEqual(ignores, expected) {
  415. t.Errorf("Incorrect ignores: %v != %v", ignores, expected)
  416. }
  417. ignores = append(ignores, "pox")
  418. err = m.SetIgnores("default", ignores)
  419. if err != nil {
  420. t.Error(err)
  421. }
  422. ignores2, _, err := m.GetIgnores("default")
  423. if err != nil {
  424. t.Error(err)
  425. }
  426. if arrEqual(expected, ignores2) {
  427. t.Errorf("Incorrect ignores: %v == %v", ignores2, expected)
  428. }
  429. if !arrEqual(ignores, ignores2) {
  430. t.Errorf("Incorrect ignores: %v != %v", ignores2, ignores)
  431. }
  432. err = m.SetIgnores("default", expected)
  433. if err != nil {
  434. t.Error(err)
  435. }
  436. ignores, _, err = m.GetIgnores("default")
  437. if err != nil {
  438. t.Error(err)
  439. }
  440. if !arrEqual(ignores, expected) {
  441. t.Errorf("Incorrect ignores: %v != %v", ignores, expected)
  442. }
  443. ignores, _, err = m.GetIgnores("doesnotexist")
  444. if err == nil {
  445. t.Error("No error")
  446. }
  447. err = m.SetIgnores("doesnotexist", expected)
  448. if err == nil {
  449. t.Error("No error")
  450. }
  451. m.AddFolder(config.FolderConfiguration{ID: "fresh", RawPath: "XXX"})
  452. ignores, _, err = m.GetIgnores("fresh")
  453. if err != nil {
  454. t.Error(err)
  455. }
  456. if len(ignores) > 0 {
  457. t.Errorf("Expected no ignores, got: %v", ignores)
  458. }
  459. }
  460. func TestRefuseUnknownBits(t *testing.T) {
  461. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  462. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  463. m.AddFolder(defaultFolderConfig)
  464. m.ServeBackground()
  465. m.ScanFolder("default")
  466. m.Index(device1, "default", []protocol.FileInfo{
  467. {
  468. Name: "invalid1",
  469. Flags: (protocol.FlagsAll + 1) &^ protocol.FlagInvalid,
  470. },
  471. {
  472. Name: "invalid2",
  473. Flags: (protocol.FlagsAll + 2) &^ protocol.FlagInvalid,
  474. },
  475. {
  476. Name: "invalid3",
  477. Flags: (1 << 31) &^ protocol.FlagInvalid,
  478. },
  479. {
  480. Name: "valid",
  481. Flags: protocol.FlagsAll &^ (protocol.FlagInvalid | protocol.FlagSymlink),
  482. },
  483. }, 0, nil)
  484. for _, name := range []string{"invalid1", "invalid2", "invalid3"} {
  485. f, ok := m.CurrentGlobalFile("default", name)
  486. if ok || f.Name == name {
  487. t.Error("Invalid file found or name match")
  488. }
  489. }
  490. f, ok := m.CurrentGlobalFile("default", "valid")
  491. if !ok || f.Name != "valid" {
  492. t.Error("Valid file not found or name mismatch", ok, f)
  493. }
  494. }
  495. func TestROScanRecovery(t *testing.T) {
  496. ldb, _ := leveldb.Open(storage.NewMemStorage(), nil)
  497. set := db.NewFileSet("default", ldb)
  498. set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
  499. {Name: "dummyfile"},
  500. })
  501. fcfg := config.FolderConfiguration{
  502. ID: "default",
  503. RawPath: "testdata/rotestfolder",
  504. RescanIntervalS: 1,
  505. }
  506. cfg := config.Wrap("/tmp/test", config.Configuration{
  507. Folders: []config.FolderConfiguration{fcfg},
  508. Devices: []config.DeviceConfiguration{
  509. {
  510. DeviceID: device1,
  511. },
  512. },
  513. })
  514. os.RemoveAll(fcfg.RawPath)
  515. m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
  516. m.AddFolder(fcfg)
  517. m.StartFolderRO("default")
  518. m.ServeBackground()
  519. waitFor := func(status string) error {
  520. timeout := time.Now().Add(2 * time.Second)
  521. for {
  522. if time.Now().After(timeout) {
  523. return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
  524. }
  525. _, _, err := m.State("default")
  526. if err == nil && status == "" {
  527. return nil
  528. }
  529. if err != nil && err.Error() == status {
  530. return nil
  531. }
  532. time.Sleep(10 * time.Millisecond)
  533. }
  534. }
  535. if err := waitFor("folder path missing"); err != nil {
  536. t.Error(err)
  537. return
  538. }
  539. os.Mkdir(fcfg.RawPath, 0700)
  540. if err := waitFor("folder marker missing"); err != nil {
  541. t.Error(err)
  542. return
  543. }
  544. fd, err := os.Create(filepath.Join(fcfg.RawPath, ".stfolder"))
  545. if err != nil {
  546. t.Error(err)
  547. return
  548. }
  549. fd.Close()
  550. if err := waitFor(""); err != nil {
  551. t.Error(err)
  552. return
  553. }
  554. os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
  555. if err := waitFor("folder marker missing"); err != nil {
  556. t.Error(err)
  557. return
  558. }
  559. os.Remove(fcfg.RawPath)
  560. if err := waitFor("folder path missing"); err != nil {
  561. t.Error(err)
  562. return
  563. }
  564. }
  565. func TestRWScanRecovery(t *testing.T) {
  566. ldb, _ := leveldb.Open(storage.NewMemStorage(), nil)
  567. set := db.NewFileSet("default", ldb)
  568. set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
  569. {Name: "dummyfile"},
  570. })
  571. fcfg := config.FolderConfiguration{
  572. ID: "default",
  573. RawPath: "testdata/rwtestfolder",
  574. RescanIntervalS: 1,
  575. }
  576. cfg := config.Wrap("/tmp/test", config.Configuration{
  577. Folders: []config.FolderConfiguration{fcfg},
  578. Devices: []config.DeviceConfiguration{
  579. {
  580. DeviceID: device1,
  581. },
  582. },
  583. })
  584. os.RemoveAll(fcfg.RawPath)
  585. m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
  586. m.AddFolder(fcfg)
  587. m.StartFolderRW("default")
  588. m.ServeBackground()
  589. waitFor := func(status string) error {
  590. timeout := time.Now().Add(2 * time.Second)
  591. for {
  592. if time.Now().After(timeout) {
  593. return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
  594. }
  595. _, _, err := m.State("default")
  596. if err == nil && status == "" {
  597. return nil
  598. }
  599. if err != nil && err.Error() == status {
  600. return nil
  601. }
  602. time.Sleep(10 * time.Millisecond)
  603. }
  604. }
  605. if err := waitFor("folder path missing"); err != nil {
  606. t.Error(err)
  607. return
  608. }
  609. os.Mkdir(fcfg.RawPath, 0700)
  610. if err := waitFor("folder marker missing"); err != nil {
  611. t.Error(err)
  612. return
  613. }
  614. fd, err := os.Create(filepath.Join(fcfg.RawPath, ".stfolder"))
  615. if err != nil {
  616. t.Error(err)
  617. return
  618. }
  619. fd.Close()
  620. if err := waitFor(""); err != nil {
  621. t.Error(err)
  622. return
  623. }
  624. os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
  625. if err := waitFor("folder marker missing"); err != nil {
  626. t.Error(err)
  627. return
  628. }
  629. os.Remove(fcfg.RawPath)
  630. if err := waitFor("folder path missing"); err != nil {
  631. t.Error(err)
  632. return
  633. }
  634. }
  635. func TestGlobalDirectoryTree(t *testing.T) {
  636. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  637. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  638. m.AddFolder(defaultFolderConfig)
  639. m.ServeBackground()
  640. b := func(isfile bool, path ...string) protocol.FileInfo {
  641. flags := uint32(protocol.FlagDirectory)
  642. blocks := []protocol.BlockInfo{}
  643. if isfile {
  644. flags = 0
  645. blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
  646. }
  647. return protocol.FileInfo{
  648. Name: filepath.Join(path...),
  649. Flags: flags,
  650. Modified: 0x666,
  651. Blocks: blocks,
  652. }
  653. }
  654. filedata := []interface{}{time.Unix(0x666, 0), 0xa}
  655. testdata := []protocol.FileInfo{
  656. b(false, "another"),
  657. b(false, "another", "directory"),
  658. b(true, "another", "directory", "afile"),
  659. b(false, "another", "directory", "with"),
  660. b(false, "another", "directory", "with", "a"),
  661. b(true, "another", "directory", "with", "a", "file"),
  662. b(true, "another", "directory", "with", "file"),
  663. b(true, "another", "file"),
  664. b(false, "other"),
  665. b(false, "other", "rand"),
  666. b(false, "other", "random"),
  667. b(false, "other", "random", "dir"),
  668. b(false, "other", "random", "dirx"),
  669. b(false, "other", "randomx"),
  670. b(false, "some"),
  671. b(false, "some", "directory"),
  672. b(false, "some", "directory", "with"),
  673. b(false, "some", "directory", "with", "a"),
  674. b(true, "some", "directory", "with", "a", "file"),
  675. b(true, "rootfile"),
  676. }
  677. expectedResult := map[string]interface{}{
  678. "another": map[string]interface{}{
  679. "directory": map[string]interface{}{
  680. "afile": filedata,
  681. "with": map[string]interface{}{
  682. "a": map[string]interface{}{
  683. "file": filedata,
  684. },
  685. "file": filedata,
  686. },
  687. },
  688. "file": filedata,
  689. },
  690. "other": map[string]interface{}{
  691. "rand": map[string]interface{}{},
  692. "random": map[string]interface{}{
  693. "dir": map[string]interface{}{},
  694. "dirx": map[string]interface{}{},
  695. },
  696. "randomx": map[string]interface{}{},
  697. },
  698. "some": map[string]interface{}{
  699. "directory": map[string]interface{}{
  700. "with": map[string]interface{}{
  701. "a": map[string]interface{}{
  702. "file": filedata,
  703. },
  704. },
  705. },
  706. },
  707. "rootfile": filedata,
  708. }
  709. mm := func(data interface{}) string {
  710. bytes, err := json.Marshal(data)
  711. if err != nil {
  712. panic(err)
  713. }
  714. return string(bytes)
  715. }
  716. m.Index(device1, "default", testdata, 0, nil)
  717. result := m.GlobalDirectoryTree("default", "", -1, false)
  718. if mm(result) != mm(expectedResult) {
  719. t.Errorf("Does not match:\n%#v\n%#v", result, expectedResult)
  720. }
  721. result = m.GlobalDirectoryTree("default", "another", -1, false)
  722. if mm(result) != mm(expectedResult["another"]) {
  723. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult["another"]))
  724. }
  725. result = m.GlobalDirectoryTree("default", "", 0, false)
  726. currentResult := map[string]interface{}{
  727. "another": map[string]interface{}{},
  728. "other": map[string]interface{}{},
  729. "some": map[string]interface{}{},
  730. "rootfile": filedata,
  731. }
  732. if mm(result) != mm(currentResult) {
  733. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  734. }
  735. result = m.GlobalDirectoryTree("default", "", 1, false)
  736. currentResult = map[string]interface{}{
  737. "another": map[string]interface{}{
  738. "directory": map[string]interface{}{},
  739. "file": filedata,
  740. },
  741. "other": map[string]interface{}{
  742. "rand": map[string]interface{}{},
  743. "random": map[string]interface{}{},
  744. "randomx": map[string]interface{}{},
  745. },
  746. "some": map[string]interface{}{
  747. "directory": map[string]interface{}{},
  748. },
  749. "rootfile": filedata,
  750. }
  751. if mm(result) != mm(currentResult) {
  752. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  753. }
  754. result = m.GlobalDirectoryTree("default", "", -1, true)
  755. currentResult = map[string]interface{}{
  756. "another": map[string]interface{}{
  757. "directory": map[string]interface{}{
  758. "with": map[string]interface{}{
  759. "a": map[string]interface{}{},
  760. },
  761. },
  762. },
  763. "other": map[string]interface{}{
  764. "rand": map[string]interface{}{},
  765. "random": map[string]interface{}{
  766. "dir": map[string]interface{}{},
  767. "dirx": map[string]interface{}{},
  768. },
  769. "randomx": map[string]interface{}{},
  770. },
  771. "some": map[string]interface{}{
  772. "directory": map[string]interface{}{
  773. "with": map[string]interface{}{
  774. "a": map[string]interface{}{},
  775. },
  776. },
  777. },
  778. }
  779. if mm(result) != mm(currentResult) {
  780. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  781. }
  782. result = m.GlobalDirectoryTree("default", "", 1, true)
  783. currentResult = map[string]interface{}{
  784. "another": map[string]interface{}{
  785. "directory": map[string]interface{}{},
  786. },
  787. "other": map[string]interface{}{
  788. "rand": map[string]interface{}{},
  789. "random": map[string]interface{}{},
  790. "randomx": map[string]interface{}{},
  791. },
  792. "some": map[string]interface{}{
  793. "directory": map[string]interface{}{},
  794. },
  795. }
  796. if mm(result) != mm(currentResult) {
  797. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  798. }
  799. result = m.GlobalDirectoryTree("default", "another", 0, false)
  800. currentResult = map[string]interface{}{
  801. "directory": map[string]interface{}{},
  802. "file": filedata,
  803. }
  804. if mm(result) != mm(currentResult) {
  805. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  806. }
  807. result = m.GlobalDirectoryTree("default", "some/directory", 0, false)
  808. currentResult = map[string]interface{}{
  809. "with": map[string]interface{}{},
  810. }
  811. if mm(result) != mm(currentResult) {
  812. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  813. }
  814. result = m.GlobalDirectoryTree("default", "some/directory", 1, false)
  815. currentResult = map[string]interface{}{
  816. "with": map[string]interface{}{
  817. "a": map[string]interface{}{},
  818. },
  819. }
  820. if mm(result) != mm(currentResult) {
  821. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  822. }
  823. result = m.GlobalDirectoryTree("default", "some/directory", 2, false)
  824. currentResult = map[string]interface{}{
  825. "with": map[string]interface{}{
  826. "a": map[string]interface{}{
  827. "file": filedata,
  828. },
  829. },
  830. }
  831. if mm(result) != mm(currentResult) {
  832. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  833. }
  834. result = m.GlobalDirectoryTree("default", "another", -1, true)
  835. currentResult = map[string]interface{}{
  836. "directory": map[string]interface{}{
  837. "with": map[string]interface{}{
  838. "a": map[string]interface{}{},
  839. },
  840. },
  841. }
  842. if mm(result) != mm(currentResult) {
  843. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  844. }
  845. // No prefix matching!
  846. result = m.GlobalDirectoryTree("default", "som", -1, false)
  847. currentResult = map[string]interface{}{}
  848. if mm(result) != mm(currentResult) {
  849. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  850. }
  851. }
  852. func TestGlobalDirectorySelfFixing(t *testing.T) {
  853. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  854. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  855. m.AddFolder(defaultFolderConfig)
  856. m.ServeBackground()
  857. b := func(isfile bool, path ...string) protocol.FileInfo {
  858. flags := uint32(protocol.FlagDirectory)
  859. blocks := []protocol.BlockInfo{}
  860. if isfile {
  861. flags = 0
  862. blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
  863. }
  864. return protocol.FileInfo{
  865. Name: filepath.Join(path...),
  866. Flags: flags,
  867. Modified: 0x666,
  868. Blocks: blocks,
  869. }
  870. }
  871. filedata := []interface{}{time.Unix(0x666, 0).Format(time.RFC3339), 0xa}
  872. testdata := []protocol.FileInfo{
  873. b(true, "another", "directory", "afile"),
  874. b(true, "another", "directory", "with", "a", "file"),
  875. b(true, "another", "directory", "with", "file"),
  876. b(false, "other", "random", "dirx"),
  877. b(false, "other", "randomx"),
  878. b(false, "some", "directory", "with", "x"),
  879. b(true, "some", "directory", "with", "a", "file"),
  880. b(false, "this", "is", "a", "deep", "invalid", "directory"),
  881. b(true, "xthis", "is", "a", "deep", "invalid", "file"),
  882. }
  883. expectedResult := map[string]interface{}{
  884. "another": map[string]interface{}{
  885. "directory": map[string]interface{}{
  886. "afile": filedata,
  887. "with": map[string]interface{}{
  888. "a": map[string]interface{}{
  889. "file": filedata,
  890. },
  891. "file": filedata,
  892. },
  893. },
  894. },
  895. "other": map[string]interface{}{
  896. "random": map[string]interface{}{
  897. "dirx": map[string]interface{}{},
  898. },
  899. "randomx": map[string]interface{}{},
  900. },
  901. "some": map[string]interface{}{
  902. "directory": map[string]interface{}{
  903. "with": map[string]interface{}{
  904. "a": map[string]interface{}{
  905. "file": filedata,
  906. },
  907. "x": map[string]interface{}{},
  908. },
  909. },
  910. },
  911. "this": map[string]interface{}{
  912. "is": map[string]interface{}{
  913. "a": map[string]interface{}{
  914. "deep": map[string]interface{}{
  915. "invalid": map[string]interface{}{
  916. "directory": map[string]interface{}{},
  917. },
  918. },
  919. },
  920. },
  921. },
  922. "xthis": map[string]interface{}{
  923. "is": map[string]interface{}{
  924. "a": map[string]interface{}{
  925. "deep": map[string]interface{}{
  926. "invalid": map[string]interface{}{
  927. "file": filedata,
  928. },
  929. },
  930. },
  931. },
  932. },
  933. }
  934. mm := func(data interface{}) string {
  935. bytes, err := json.Marshal(data)
  936. if err != nil {
  937. panic(err)
  938. }
  939. return string(bytes)
  940. }
  941. m.Index(device1, "default", testdata, 0, nil)
  942. result := m.GlobalDirectoryTree("default", "", -1, false)
  943. if mm(result) != mm(expectedResult) {
  944. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult))
  945. }
  946. result = m.GlobalDirectoryTree("default", "xthis/is/a/deep", -1, false)
  947. currentResult := map[string]interface{}{
  948. "invalid": map[string]interface{}{
  949. "file": filedata,
  950. },
  951. }
  952. if mm(result) != mm(currentResult) {
  953. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  954. }
  955. result = m.GlobalDirectoryTree("default", "xthis/is/a/deep", -1, true)
  956. currentResult = map[string]interface{}{
  957. "invalid": map[string]interface{}{},
  958. }
  959. if mm(result) != mm(currentResult) {
  960. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  961. }
  962. // !!! This is actually BAD, because we don't have enough level allowance
  963. // to accept this file, hence the tree is left unbuilt !!!
  964. result = m.GlobalDirectoryTree("default", "xthis", 1, false)
  965. currentResult = map[string]interface{}{}
  966. if mm(result) != mm(currentResult) {
  967. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  968. }
  969. }
  970. func genDeepFiles(n, d int) []protocol.FileInfo {
  971. rand.Seed(int64(n))
  972. files := make([]protocol.FileInfo, n)
  973. t := time.Now().Unix()
  974. for i := 0; i < n; i++ {
  975. path := ""
  976. for i := 0; i <= d; i++ {
  977. path = filepath.Join(path, strconv.Itoa(rand.Int()))
  978. }
  979. sofar := ""
  980. for _, path := range filepath.SplitList(path) {
  981. sofar = filepath.Join(sofar, path)
  982. files[i] = protocol.FileInfo{
  983. Name: sofar,
  984. }
  985. i++
  986. }
  987. files[i].Modified = t
  988. files[i].Blocks = []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}}
  989. }
  990. return files
  991. }
  992. func BenchmarkTree_10000_50(b *testing.B) {
  993. benchmarkTree(b, 10000, 50)
  994. }
  995. func BenchmarkTree_100_50(b *testing.B) {
  996. benchmarkTree(b, 100, 50)
  997. }
  998. func BenchmarkTree_100_10(b *testing.B) {
  999. benchmarkTree(b, 100, 10)
  1000. }
  1001. func benchmarkTree(b *testing.B, n1, n2 int) {
  1002. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  1003. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  1004. m.AddFolder(defaultFolderConfig)
  1005. m.ServeBackground()
  1006. m.ScanFolder("default")
  1007. files := genDeepFiles(n1, n2)
  1008. m.Index(device1, "default", files, 0, nil)
  1009. b.ResetTimer()
  1010. for i := 0; i < b.N; i++ {
  1011. m.GlobalDirectoryTree("default", "", -1, false)
  1012. }
  1013. b.ReportAllocs()
  1014. }
  1015. func TestIgnoreDelete(t *testing.T) {
  1016. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  1017. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  1018. // This folder should ignore external deletes
  1019. cfg := defaultFolderConfig
  1020. cfg.IgnoreDelete = true
  1021. m.AddFolder(cfg)
  1022. m.ServeBackground()
  1023. m.StartFolderRW("default")
  1024. m.ScanFolder("default")
  1025. // Get a currently existing file
  1026. f, ok := m.CurrentGlobalFile("default", "foo")
  1027. if !ok {
  1028. t.Fatal("foo should exist")
  1029. }
  1030. // Mark it for deletion
  1031. f.Flags = protocol.FlagDeleted
  1032. f.Version = f.Version.Update(142) // arbitrary short remote ID
  1033. f.Blocks = nil
  1034. // Send the index
  1035. m.Index(device1, "default", []protocol.FileInfo{f}, 0, nil)
  1036. // Make sure we ignored it
  1037. f, ok = m.CurrentGlobalFile("default", "foo")
  1038. if !ok {
  1039. t.Fatal("foo should exist")
  1040. }
  1041. if f.IsDeleted() {
  1042. t.Fatal("foo should not be marked for deletion")
  1043. }
  1044. }