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 https://mozilla.org/MPL/2.0/. 6 7package model 8 9import ( 10 "bytes" 11 "context" 12 "encoding/json" 13 "fmt" 14 "io" 15 "io/ioutil" 16 "math/rand" 17 "os" 18 "path/filepath" 19 "runtime" 20 "runtime/pprof" 21 "sort" 22 "strconv" 23 "strings" 24 "sync" 25 "sync/atomic" 26 "testing" 27 "time" 28 29 "github.com/pkg/errors" 30 "github.com/syncthing/syncthing/lib/config" 31 "github.com/syncthing/syncthing/lib/db" 32 "github.com/syncthing/syncthing/lib/db/backend" 33 "github.com/syncthing/syncthing/lib/events" 34 "github.com/syncthing/syncthing/lib/fs" 35 "github.com/syncthing/syncthing/lib/ignore" 36 "github.com/syncthing/syncthing/lib/osutil" 37 "github.com/syncthing/syncthing/lib/protocol" 38 protocolmocks "github.com/syncthing/syncthing/lib/protocol/mocks" 39 srand "github.com/syncthing/syncthing/lib/rand" 40 "github.com/syncthing/syncthing/lib/testutils" 41 "github.com/syncthing/syncthing/lib/util" 42 "github.com/syncthing/syncthing/lib/versioner" 43) 44 45var testDataExpected = map[string]protocol.FileInfo{ 46 "foo": { 47 Name: "foo", 48 Type: protocol.FileInfoTypeFile, 49 ModifiedS: 0, 50 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}}}, 51 }, 52 "empty": { 53 Name: "empty", 54 Type: protocol.FileInfoTypeFile, 55 ModifiedS: 0, 56 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}}}, 57 }, 58 "bar": { 59 Name: "bar", 60 Type: protocol.FileInfoTypeFile, 61 ModifiedS: 0, 62 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}}}, 63 }, 64} 65 66func init() { 67 // Fix expected test data to match reality 68 for n, f := range testDataExpected { 69 fi, _ := os.Stat("testdata/" + n) 70 f.Permissions = uint32(fi.Mode()) 71 f.ModifiedS = fi.ModTime().Unix() 72 f.Size = fi.Size() 73 testDataExpected[n] = f 74 } 75} 76 77func TestMain(m *testing.M) { 78 tmpName, err := prepareTmpFile(defaultFs) 79 if err != nil { 80 panic(err) 81 } 82 83 exitCode := m.Run() 84 85 defaultCfgWrapperCancel() 86 os.Remove(defaultCfgWrapper.ConfigPath()) 87 defaultFs.Remove(tmpName) 88 defaultFs.RemoveAll(config.DefaultMarkerName) 89 90 os.Exit(exitCode) 91} 92 93func prepareTmpFile(to fs.Filesystem) (string, error) { 94 tmpName := fs.TempName("file") 95 in, err := defaultFs.Open("tmpfile") 96 if err != nil { 97 return "", err 98 } 99 defer in.Close() 100 out, err := to.Create(tmpName) 101 if err != nil { 102 return "", err 103 } 104 defer out.Close() 105 if _, err = io.Copy(out, in); err != nil { 106 return "", err 107 } 108 future := time.Now().Add(time.Hour) 109 if err := os.Chtimes(filepath.Join("testdata", tmpName), future, future); err != nil { 110 return "", err 111 } 112 return tmpName, nil 113} 114 115func newState(t testing.TB, cfg config.Configuration) (*testModel, context.CancelFunc) { 116 wcfg, cancel := createTmpWrapper(cfg) 117 118 m := setupModel(t, wcfg) 119 120 for _, dev := range cfg.Devices { 121 m.AddConnection(newFakeConnection(dev.DeviceID, m), protocol.Hello{}) 122 } 123 124 return m, cancel 125} 126 127func createClusterConfig(remote protocol.DeviceID, ids ...string) protocol.ClusterConfig { 128 cc := protocol.ClusterConfig{ 129 Folders: make([]protocol.Folder, len(ids)), 130 } 131 for i, id := range ids { 132 cc.Folders[i] = protocol.Folder{ 133 ID: id, 134 Label: id, 135 } 136 } 137 return addFolderDevicesToClusterConfig(cc, remote) 138} 139 140func addFolderDevicesToClusterConfig(cc protocol.ClusterConfig, remote protocol.DeviceID) protocol.ClusterConfig { 141 for i := range cc.Folders { 142 cc.Folders[i].Devices = []protocol.Device{ 143 {ID: myID}, 144 {ID: remote}, 145 } 146 } 147 return cc 148} 149 150func TestRequest(t *testing.T) { 151 m := setupModel(t, defaultCfgWrapper) 152 defer cleanupModel(m) 153 154 // Existing, shared file 155 res, err := m.Request(device1, "default", "foo", 0, 6, 0, nil, 0, false) 156 if err != nil { 157 t.Fatal(err) 158 } 159 bs := res.Data() 160 if !bytes.Equal(bs, []byte("foobar")) { 161 t.Errorf("Incorrect data from request: %q", string(bs)) 162 } 163 164 // Existing, nonshared file 165 _, err = m.Request(device2, "default", "foo", 0, 6, 0, nil, 0, false) 166 if err == nil { 167 t.Error("Unexpected nil error on insecure file read") 168 } 169 170 // Nonexistent file 171 _, err = m.Request(device1, "default", "nonexistent", 0, 6, 0, nil, 0, false) 172 if err == nil { 173 t.Error("Unexpected nil error on insecure file read") 174 } 175 176 // Shared folder, but disallowed file name 177 _, err = m.Request(device1, "default", "../walk.go", 0, 6, 0, nil, 0, false) 178 if err == nil { 179 t.Error("Unexpected nil error on insecure file read") 180 } 181 182 // Negative offset 183 _, err = m.Request(device1, "default", "foo", 0, -4, 0, nil, 0, false) 184 if err == nil { 185 t.Error("Unexpected nil error on insecure file read") 186 } 187 188 // Larger block than available 189 _, err = m.Request(device1, "default", "foo", 0, 42, 0, []byte("hash necessary but not checked"), 0, false) 190 if err == nil { 191 t.Error("Unexpected nil error on read past end of file") 192 } 193 _, err = m.Request(device1, "default", "foo", 0, 42, 0, nil, 0, false) 194 if err != nil { 195 t.Error("Unexpected error when large read should be permitted") 196 } 197} 198 199func genFiles(n int) []protocol.FileInfo { 200 files := make([]protocol.FileInfo, n) 201 t := time.Now().Unix() 202 for i := 0; i < n; i++ { 203 files[i] = protocol.FileInfo{ 204 Name: fmt.Sprintf("file%d", i), 205 ModifiedS: t, 206 Sequence: int64(i + 1), 207 Blocks: []protocol.BlockInfo{{Offset: 0, Size: 100, Hash: []byte("some hash bytes")}}, 208 Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}, 209 } 210 } 211 212 return files 213} 214 215func BenchmarkIndex_10000(b *testing.B) { 216 benchmarkIndex(b, 10000) 217} 218 219func BenchmarkIndex_100(b *testing.B) { 220 benchmarkIndex(b, 100) 221} 222 223func benchmarkIndex(b *testing.B, nfiles int) { 224 m, _, fcfg, wcfgCancel := setupModelWithConnection(b) 225 defer wcfgCancel() 226 defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) 227 228 files := genFiles(nfiles) 229 must(b, m.Index(device1, fcfg.ID, files)) 230 231 b.ResetTimer() 232 for i := 0; i < b.N; i++ { 233 must(b, m.Index(device1, fcfg.ID, files)) 234 } 235 b.ReportAllocs() 236} 237 238func BenchmarkIndexUpdate_10000_10000(b *testing.B) { 239 benchmarkIndexUpdate(b, 10000, 10000) 240} 241 242func BenchmarkIndexUpdate_10000_100(b *testing.B) { 243 benchmarkIndexUpdate(b, 10000, 100) 244} 245 246func BenchmarkIndexUpdate_10000_1(b *testing.B) { 247 benchmarkIndexUpdate(b, 10000, 1) 248} 249 250func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) { 251 m, _, fcfg, wcfgCancel := setupModelWithConnection(b) 252 defer wcfgCancel() 253 defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) 254 255 files := genFiles(nfiles) 256 ufiles := genFiles(nufiles) 257 258 must(b, m.Index(device1, fcfg.ID, files)) 259 260 b.ResetTimer() 261 for i := 0; i < b.N; i++ { 262 must(b, m.IndexUpdate(device1, fcfg.ID, ufiles)) 263 } 264 b.ReportAllocs() 265} 266 267func BenchmarkRequestOut(b *testing.B) { 268 m := setupModel(b, defaultCfgWrapper) 269 defer cleanupModel(m) 270 271 const n = 1000 272 files := genFiles(n) 273 274 fc := newFakeConnection(device1, m) 275 for _, f := range files { 276 fc.addFile(f.Name, 0644, protocol.FileInfoTypeFile, []byte("some data to return")) 277 } 278 m.AddConnection(fc, protocol.Hello{}) 279 must(b, m.Index(device1, "default", files)) 280 281 b.ResetTimer() 282 for i := 0; i < b.N; i++ { 283 data, err := m.requestGlobal(context.Background(), device1, "default", files[i%n].Name, 0, 0, 32, nil, 0, false) 284 if err != nil { 285 b.Error(err) 286 } 287 if data == nil { 288 b.Error("nil data") 289 } 290 } 291} 292 293func BenchmarkRequestInSingleFile(b *testing.B) { 294 m := setupModel(b, defaultCfgWrapper) 295 defer cleanupModel(m) 296 297 buf := make([]byte, 128<<10) 298 rand.Read(buf) 299 mustRemove(b, defaultFs.RemoveAll("request")) 300 defer func() { mustRemove(b, defaultFs.RemoveAll("request")) }() 301 must(b, defaultFs.MkdirAll("request/for/a/file/in/a/couple/of/dirs", 0755)) 302 writeFile(defaultFs, "request/for/a/file/in/a/couple/of/dirs/128k", buf, 0644) 303 304 b.ResetTimer() 305 306 for i := 0; i < b.N; i++ { 307 if _, err := m.Request(device1, "default", "request/for/a/file/in/a/couple/of/dirs/128k", 0, 128<<10, 0, nil, 0, false); err != nil { 308 b.Error(err) 309 } 310 } 311 312 b.SetBytes(128 << 10) 313} 314 315func TestDeviceRename(t *testing.T) { 316 hello := protocol.Hello{ 317 ClientName: "syncthing", 318 ClientVersion: "v0.9.4", 319 } 320 321 rawCfg := config.New(device1) 322 rawCfg.Devices = []config.DeviceConfiguration{ 323 { 324 DeviceID: device1, 325 }, 326 } 327 cfg, cfgCancel := createTmpWrapper(rawCfg) 328 defer cfgCancel() 329 330 m := newModel(t, cfg, myID, "syncthing", "dev", nil) 331 332 if cfg.Devices()[device1].Name != "" { 333 t.Errorf("Device already has a name") 334 } 335 336 conn := newFakeConnection(device1, m) 337 338 m.AddConnection(conn, hello) 339 340 m.ServeBackground() 341 defer cleanupModel(m) 342 343 if cfg.Devices()[device1].Name != "" { 344 t.Errorf("Device already has a name") 345 } 346 347 m.Closed(conn.ID(), protocol.ErrTimeout) 348 hello.DeviceName = "tester" 349 m.AddConnection(conn, hello) 350 351 if cfg.Devices()[device1].Name != "tester" { 352 t.Errorf("Device did not get a name") 353 } 354 355 m.Closed(conn.ID(), protocol.ErrTimeout) 356 hello.DeviceName = "tester2" 357 m.AddConnection(conn, hello) 358 359 if cfg.Devices()[device1].Name != "tester" { 360 t.Errorf("Device name got overwritten") 361 } 362 363 must(t, cfg.Save()) 364 cfgw, _, err := config.Load(cfg.ConfigPath(), myID, events.NoopLogger) 365 if err != nil { 366 t.Error(err) 367 return 368 } 369 if cfgw.Devices()[device1].Name != "tester" { 370 t.Errorf("Device name not saved in config") 371 } 372 373 m.Closed(conn.ID(), protocol.ErrTimeout) 374 375 waiter, err := cfg.Modify(func(cfg *config.Configuration) { 376 cfg.Options.OverwriteRemoteDevNames = true 377 }) 378 must(t, err) 379 waiter.Wait() 380 381 hello.DeviceName = "tester2" 382 m.AddConnection(conn, hello) 383 384 if cfg.Devices()[device1].Name != "tester2" { 385 t.Errorf("Device name not overwritten") 386 } 387} 388 389func TestClusterConfig(t *testing.T) { 390 cfg := config.New(device1) 391 cfg.Devices = []config.DeviceConfiguration{ 392 { 393 DeviceID: device1, 394 Introducer: true, 395 }, 396 { 397 DeviceID: device2, 398 }, 399 } 400 cfg.Folders = []config.FolderConfiguration{ 401 { 402 ID: "folder1", 403 Path: "testdata1", 404 Devices: []config.FolderDeviceConfiguration{ 405 {DeviceID: device1}, 406 {DeviceID: device2}, 407 }, 408 }, 409 { 410 ID: "folder2", 411 Path: "testdata2", 412 Paused: true, // should still be included 413 Devices: []config.FolderDeviceConfiguration{ 414 {DeviceID: device1}, 415 {DeviceID: device2}, 416 }, 417 }, 418 { 419 ID: "folder3", 420 Path: "testdata3", 421 Devices: []config.FolderDeviceConfiguration{ 422 {DeviceID: device1}, 423 // should not be included, does not include device2 424 }, 425 }, 426 } 427 428 wrapper, cancel := createTmpWrapper(cfg) 429 defer cancel() 430 m := newModel(t, wrapper, myID, "syncthing", "dev", nil) 431 m.ServeBackground() 432 defer cleanupModel(m) 433 434 cm, _ := m.generateClusterConfig(device2) 435 436 if l := len(cm.Folders); l != 2 { 437 t.Fatalf("Incorrect number of folders %d != 2", l) 438 } 439 440 r := cm.Folders[0] 441 if r.ID != "folder1" { 442 t.Errorf("Incorrect folder %q != folder1", r.ID) 443 } 444 if l := len(r.Devices); l != 2 { 445 t.Errorf("Incorrect number of devices %d != 2", l) 446 } 447 if id := r.Devices[0].ID; id != device1 { 448 t.Errorf("Incorrect device ID %s != %s", id, device1) 449 } 450 if !r.Devices[0].Introducer { 451 t.Error("Device1 should be flagged as Introducer") 452 } 453 if id := r.Devices[1].ID; id != device2 { 454 t.Errorf("Incorrect device ID %s != %s", id, device2) 455 } 456 if r.Devices[1].Introducer { 457 t.Error("Device2 should not be flagged as Introducer") 458 } 459 460 r = cm.Folders[1] 461 if r.ID != "folder2" { 462 t.Errorf("Incorrect folder %q != folder2", r.ID) 463 } 464 if l := len(r.Devices); l != 2 { 465 t.Errorf("Incorrect number of devices %d != 2", l) 466 } 467 if id := r.Devices[0].ID; id != device1 { 468 t.Errorf("Incorrect device ID %s != %s", id, device1) 469 } 470 if !r.Devices[0].Introducer { 471 t.Error("Device1 should be flagged as Introducer") 472 } 473 if id := r.Devices[1].ID; id != device2 { 474 t.Errorf("Incorrect device ID %s != %s", id, device2) 475 } 476 if r.Devices[1].Introducer { 477 t.Error("Device2 should not be flagged as Introducer") 478 } 479} 480 481func TestIntroducer(t *testing.T) { 482 var introducedByAnyone protocol.DeviceID 483 484 // LocalDeviceID is a magic value meaning don't check introducer 485 contains := func(cfg config.FolderConfiguration, id, introducedBy protocol.DeviceID) bool { 486 for _, dev := range cfg.Devices { 487 if dev.DeviceID.Equals(id) { 488 if introducedBy.Equals(introducedByAnyone) { 489 return true 490 } 491 return dev.IntroducedBy.Equals(introducedBy) 492 } 493 } 494 return false 495 } 496 497 m, cancel := newState(t, config.Configuration{ 498 Devices: []config.DeviceConfiguration{ 499 { 500 DeviceID: device1, 501 Introducer: true, 502 }, 503 }, 504 Folders: []config.FolderConfiguration{ 505 { 506 ID: "folder1", 507 Path: "testdata", 508 Devices: []config.FolderDeviceConfiguration{ 509 {DeviceID: device1}, 510 }, 511 }, 512 { 513 ID: "folder2", 514 Path: "testdata", 515 Devices: []config.FolderDeviceConfiguration{ 516 {DeviceID: device1}, 517 }, 518 }, 519 }, 520 }) 521 cc := basicClusterConfig(myID, device1, "folder1", "folder2") 522 cc.Folders[0].Devices = append(cc.Folders[0].Devices, protocol.Device{ 523 ID: device2, 524 Introducer: true, 525 SkipIntroductionRemovals: true, 526 }) 527 cc.Folders[1].Devices = append(cc.Folders[1].Devices, protocol.Device{ 528 ID: device2, 529 Introducer: true, 530 SkipIntroductionRemovals: true, 531 EncryptionPasswordToken: []byte("faketoken"), 532 }) 533 m.ClusterConfig(device1, cc) 534 535 if newDev, ok := m.cfg.Device(device2); !ok || !newDev.Introducer || !newDev.SkipIntroductionRemovals { 536 t.Error("device 2 missing or wrong flags") 537 } 538 539 if !contains(m.cfg.Folders()["folder1"], device2, device1) { 540 t.Error("expected folder 1 to have device2 introduced by device 1") 541 } 542 543 for _, devCfg := range m.cfg.Folders()["folder2"].Devices { 544 if devCfg.DeviceID == device2 { 545 t.Error("Device was added even though it's untrusted") 546 } 547 } 548 549 cleanupModel(m) 550 cancel() 551 m, cancel = newState(t, config.Configuration{ 552 Devices: []config.DeviceConfiguration{ 553 { 554 DeviceID: device1, 555 Introducer: true, 556 }, 557 { 558 DeviceID: device2, 559 IntroducedBy: device1, 560 }, 561 }, 562 Folders: []config.FolderConfiguration{ 563 { 564 ID: "folder1", 565 Path: "testdata", 566 Devices: []config.FolderDeviceConfiguration{ 567 {DeviceID: device1}, 568 {DeviceID: device2, IntroducedBy: device1}, 569 }, 570 }, 571 { 572 ID: "folder2", 573 Path: "testdata", 574 Devices: []config.FolderDeviceConfiguration{ 575 {DeviceID: device1}, 576 }, 577 }, 578 }, 579 }) 580 cc = basicClusterConfig(myID, device1, "folder2") 581 cc.Folders[0].Devices = append(cc.Folders[0].Devices, protocol.Device{ 582 ID: device2, 583 Introducer: true, 584 SkipIntroductionRemovals: true, 585 }) 586 m.ClusterConfig(device1, cc) 587 588 // Should not get introducer, as it's already unset, and it's an existing device. 589 if newDev, ok := m.cfg.Device(device2); !ok || newDev.Introducer || newDev.SkipIntroductionRemovals { 590 t.Error("device 2 missing or changed flags") 591 } 592 593 if contains(m.cfg.Folders()["folder1"], device2, introducedByAnyone) { 594 t.Error("expected device 2 to be removed from folder 1") 595 } 596 597 if !contains(m.cfg.Folders()["folder2"], device2, device1) { 598 t.Error("expected device 2 to be added to folder 2") 599 } 600 601 cleanupModel(m) 602 cancel() 603 m, cancel = newState(t, config.Configuration{ 604 Devices: []config.DeviceConfiguration{ 605 { 606 DeviceID: device1, 607 Introducer: true, 608 }, 609 { 610 DeviceID: device2, 611 IntroducedBy: device1, 612 }, 613 }, 614 Folders: []config.FolderConfiguration{ 615 { 616 ID: "folder1", 617 Path: "testdata", 618 Devices: []config.FolderDeviceConfiguration{ 619 {DeviceID: device1}, 620 {DeviceID: device2, IntroducedBy: device1}, 621 }, 622 }, 623 { 624 ID: "folder2", 625 Path: "testdata", 626 Devices: []config.FolderDeviceConfiguration{ 627 {DeviceID: device1}, 628 {DeviceID: device2, IntroducedBy: device1}, 629 }, 630 }, 631 }, 632 }) 633 m.ClusterConfig(device1, protocol.ClusterConfig{}) 634 635 if _, ok := m.cfg.Device(device2); ok { 636 t.Error("device 2 should have been removed") 637 } 638 639 if contains(m.cfg.Folders()["folder1"], device2, introducedByAnyone) { 640 t.Error("expected device 2 to be removed from folder 1") 641 } 642 643 if contains(m.cfg.Folders()["folder2"], device2, introducedByAnyone) { 644 t.Error("expected device 2 to be removed from folder 2") 645 } 646 647 // Two cases when removals should not happen 648 // 1. Introducer flag no longer set on device 649 650 cleanupModel(m) 651 cancel() 652 m, cancel = newState(t, config.Configuration{ 653 Devices: []config.DeviceConfiguration{ 654 { 655 DeviceID: device1, 656 Introducer: false, 657 }, 658 { 659 DeviceID: device2, 660 IntroducedBy: device1, 661 }, 662 }, 663 Folders: []config.FolderConfiguration{ 664 { 665 ID: "folder1", 666 Path: "testdata", 667 Devices: []config.FolderDeviceConfiguration{ 668 {DeviceID: device1}, 669 {DeviceID: device2, IntroducedBy: device1}, 670 }, 671 }, 672 { 673 ID: "folder2", 674 Path: "testdata", 675 Devices: []config.FolderDeviceConfiguration{ 676 {DeviceID: device1}, 677 {DeviceID: device2, IntroducedBy: device1}, 678 }, 679 }, 680 }, 681 }) 682 m.ClusterConfig(device1, protocol.ClusterConfig{}) 683 684 if _, ok := m.cfg.Device(device2); !ok { 685 t.Error("device 2 should not have been removed") 686 } 687 688 if !contains(m.cfg.Folders()["folder1"], device2, device1) { 689 t.Error("expected device 2 not to be removed from folder 1") 690 } 691 692 if !contains(m.cfg.Folders()["folder2"], device2, device1) { 693 t.Error("expected device 2 not to be removed from folder 2") 694 } 695 696 // 2. SkipIntroductionRemovals is set 697 698 cleanupModel(m) 699 cancel() 700 m, cancel = newState(t, config.Configuration{ 701 Devices: []config.DeviceConfiguration{ 702 { 703 DeviceID: device1, 704 Introducer: true, 705 SkipIntroductionRemovals: true, 706 }, 707 { 708 DeviceID: device2, 709 IntroducedBy: device1, 710 }, 711 }, 712 Folders: []config.FolderConfiguration{ 713 { 714 ID: "folder1", 715 Path: "testdata", 716 Devices: []config.FolderDeviceConfiguration{ 717 {DeviceID: device1}, 718 {DeviceID: device2, IntroducedBy: device1}, 719 }, 720 }, 721 { 722 ID: "folder2", 723 Path: "testdata", 724 Devices: []config.FolderDeviceConfiguration{ 725 {DeviceID: device1}, 726 }, 727 }, 728 }, 729 }) 730 cc = basicClusterConfig(myID, device1, "folder2") 731 cc.Folders[0].Devices = append(cc.Folders[0].Devices, protocol.Device{ 732 ID: device2, 733 Introducer: true, 734 SkipIntroductionRemovals: true, 735 }) 736 m.ClusterConfig(device1, cc) 737 738 if _, ok := m.cfg.Device(device2); !ok { 739 t.Error("device 2 should not have been removed") 740 } 741 742 if !contains(m.cfg.Folders()["folder1"], device2, device1) { 743 t.Error("expected device 2 not to be removed from folder 1") 744 } 745 746 if !contains(m.cfg.Folders()["folder2"], device2, device1) { 747 t.Error("expected device 2 not to be added to folder 2") 748 } 749 750 // Test device not being removed as it's shared without an introducer. 751 752 cleanupModel(m) 753 cancel() 754 m, cancel = newState(t, config.Configuration{ 755 Devices: []config.DeviceConfiguration{ 756 { 757 DeviceID: device1, 758 Introducer: true, 759 }, 760 { 761 DeviceID: device2, 762 IntroducedBy: device1, 763 }, 764 }, 765 Folders: []config.FolderConfiguration{ 766 { 767 ID: "folder1", 768 Path: "testdata", 769 Devices: []config.FolderDeviceConfiguration{ 770 {DeviceID: device1}, 771 {DeviceID: device2, IntroducedBy: device1}, 772 }, 773 }, 774 { 775 ID: "folder2", 776 Path: "testdata", 777 Devices: []config.FolderDeviceConfiguration{ 778 {DeviceID: device1}, 779 {DeviceID: device2}, 780 }, 781 }, 782 }, 783 }) 784 m.ClusterConfig(device1, protocol.ClusterConfig{}) 785 786 if _, ok := m.cfg.Device(device2); !ok { 787 t.Error("device 2 should not have been removed") 788 } 789 790 if contains(m.cfg.Folders()["folder1"], device2, introducedByAnyone) { 791 t.Error("expected device 2 to be removed from folder 1") 792 } 793 794 if !contains(m.cfg.Folders()["folder2"], device2, introducedByAnyone) { 795 t.Error("expected device 2 not to be removed from folder 2") 796 } 797 798 // Test device not being removed as it's shared by a different introducer. 799 800 cleanupModel(m) 801 cancel() 802 m, cancel = newState(t, config.Configuration{ 803 Devices: []config.DeviceConfiguration{ 804 { 805 DeviceID: device1, 806 Introducer: true, 807 }, 808 { 809 DeviceID: device2, 810 IntroducedBy: device1, 811 }, 812 }, 813 Folders: []config.FolderConfiguration{ 814 { 815 ID: "folder1", 816 Path: "testdata", 817 Devices: []config.FolderDeviceConfiguration{ 818 {DeviceID: device1}, 819 {DeviceID: device2, IntroducedBy: device1}, 820 }, 821 }, 822 { 823 ID: "folder2", 824 Path: "testdata", 825 Devices: []config.FolderDeviceConfiguration{ 826 {DeviceID: device1}, 827 {DeviceID: device2, IntroducedBy: myID}, 828 }, 829 }, 830 }, 831 }) 832 defer cleanupModel(m) 833 defer cancel() 834 m.ClusterConfig(device1, protocol.ClusterConfig{}) 835 836 if _, ok := m.cfg.Device(device2); !ok { 837 t.Error("device 2 should not have been removed") 838 } 839 840 if contains(m.cfg.Folders()["folder1"], device2, introducedByAnyone) { 841 t.Error("expected device 2 to be removed from folder 1") 842 } 843 844 if !contains(m.cfg.Folders()["folder2"], device2, introducedByAnyone) { 845 t.Error("expected device 2 not to be removed from folder 2") 846 } 847} 848 849func TestIssue4897(t *testing.T) { 850 m, cancel := newState(t, config.Configuration{ 851 Devices: []config.DeviceConfiguration{ 852 { 853 DeviceID: device1, 854 Introducer: true, 855 }, 856 }, 857 Folders: []config.FolderConfiguration{ 858 { 859 ID: "folder1", 860 Path: "testdata", 861 Devices: []config.FolderDeviceConfiguration{ 862 {DeviceID: device1}, 863 }, 864 Paused: true, 865 }, 866 }, 867 }) 868 defer cleanupModel(m) 869 cancel() 870 871 cm, _ := m.generateClusterConfig(device1) 872 if l := len(cm.Folders); l != 1 { 873 t.Errorf("Cluster config contains %v folders, expected 1", l) 874 } 875} 876 877// TestIssue5063 is about a panic in connection with modifying config in quick 878// succession, related with auto accepted folders. It's unclear what exactly, a 879// relevant bit seems to be here: 880// PR-comments: https://github.com/syncthing/syncthing/pull/5069/files#r203146546 881// Issue: https://github.com/syncthing/syncthing/pull/5509 882func TestIssue5063(t *testing.T) { 883 m, cancel := newState(t, defaultAutoAcceptCfg) 884 defer cleanupModel(m) 885 defer cancel() 886 887 m.pmut.Lock() 888 for _, c := range m.conn { 889 conn := c.(*fakeConnection) 890 conn.CloseCalls(func(_ error) {}) 891 defer m.Closed(c.ID(), errStopped) // to unblock deferred m.Stop() 892 } 893 m.pmut.Unlock() 894 895 wg := sync.WaitGroup{} 896 897 addAndVerify := func(id string) { 898 m.ClusterConfig(device1, createClusterConfig(device1, id)) 899 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { 900 t.Error("expected shared", id) 901 } 902 wg.Done() 903 } 904 905 reps := 10 906 ids := make([]string, reps) 907 for i := 0; i < reps; i++ { 908 wg.Add(1) 909 ids[i] = srand.String(8) 910 go addAndVerify(ids[i]) 911 } 912 defer func() { 913 for _, id := range ids { 914 os.RemoveAll(id) 915 } 916 }() 917 918 finished := make(chan struct{}) 919 go func() { 920 wg.Wait() 921 close(finished) 922 }() 923 select { 924 case <-finished: 925 case <-time.After(10 * time.Second): 926 pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) 927 t.Fatal("Timed out before all devices were added") 928 } 929} 930 931func TestAutoAcceptRejected(t *testing.T) { 932 // Nothing happens if AutoAcceptFolders not set 933 tcfg := defaultAutoAcceptCfg.Copy() 934 for i := range tcfg.Devices { 935 tcfg.Devices[i].AutoAcceptFolders = false 936 } 937 m, cancel := newState(t, tcfg) 938 defer cleanupModel(m) 939 defer cancel() 940 id := srand.String(8) 941 defer os.RemoveAll(id) 942 m.ClusterConfig(device1, createClusterConfig(device1, id)) 943 944 if cfg, ok := m.cfg.Folder(id); ok && cfg.SharedWith(device1) { 945 t.Error("unexpected shared", id) 946 } 947} 948 949func TestAutoAcceptNewFolder(t *testing.T) { 950 // New folder 951 m, cancel := newState(t, defaultAutoAcceptCfg) 952 defer cleanupModel(m) 953 defer cancel() 954 id := srand.String(8) 955 defer os.RemoveAll(id) 956 m.ClusterConfig(device1, createClusterConfig(device1, id)) 957 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { 958 t.Error("expected shared", id) 959 } 960} 961 962func TestAutoAcceptNewFolderFromTwoDevices(t *testing.T) { 963 m, cancel := newState(t, defaultAutoAcceptCfg) 964 defer cleanupModel(m) 965 defer cancel() 966 id := srand.String(8) 967 defer os.RemoveAll(id) 968 m.ClusterConfig(device1, createClusterConfig(device1, id)) 969 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { 970 t.Error("expected shared", id) 971 } 972 if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) { 973 t.Error("unexpected expected shared", id) 974 } 975 m.ClusterConfig(device2, createClusterConfig(device2, id)) 976 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device2) { 977 t.Error("expected shared", id) 978 } 979} 980 981func TestAutoAcceptNewFolderFromOnlyOneDevice(t *testing.T) { 982 modifiedCfg := defaultAutoAcceptCfg.Copy() 983 modifiedCfg.Devices[2].AutoAcceptFolders = false 984 m, cancel := newState(t, modifiedCfg) 985 id := srand.String(8) 986 defer os.RemoveAll(id) 987 defer cleanupModel(m) 988 defer cancel() 989 m.ClusterConfig(device1, createClusterConfig(device1, id)) 990 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { 991 t.Error("expected shared", id) 992 } 993 if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) { 994 t.Error("unexpected expected shared", id) 995 } 996 m.ClusterConfig(device2, createClusterConfig(device2, id)) 997 if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) { 998 t.Error("unexpected shared", id) 999 } 1000} 1001 1002func TestAutoAcceptNewFolderPremutationsNoPanic(t *testing.T) { 1003 if testing.Short() { 1004 t.Skip("short tests only") 1005 } 1006 1007 testOs := &fatalOs{t} 1008 1009 id := srand.String(8) 1010 label := srand.String(8) 1011 premutations := []protocol.Folder{ 1012 {ID: id, Label: id}, 1013 {ID: id, Label: label}, 1014 {ID: label, Label: id}, 1015 {ID: label, Label: label}, 1016 } 1017 localFolders := append(premutations, protocol.Folder{}) 1018 for _, localFolder := range localFolders { 1019 for _, localFolderPaused := range []bool{false, true} { 1020 for _, dev1folder := range premutations { 1021 for _, dev2folder := range premutations { 1022 cfg := defaultAutoAcceptCfg.Copy() 1023 if localFolder.Label != "" { 1024 fcfg := newFolderConfiguration(defaultCfgWrapper, localFolder.ID, localFolder.Label, fs.FilesystemTypeBasic, localFolder.ID) 1025 fcfg.Paused = localFolderPaused 1026 cfg.Folders = append(cfg.Folders, fcfg) 1027 } 1028 m, cancel := newState(t, cfg) 1029 m.ClusterConfig(device1, protocol.ClusterConfig{ 1030 Folders: []protocol.Folder{dev1folder}, 1031 }) 1032 m.ClusterConfig(device2, protocol.ClusterConfig{ 1033 Folders: []protocol.Folder{dev2folder}, 1034 }) 1035 cleanupModel(m) 1036 cancel() 1037 testOs.RemoveAll(id) 1038 testOs.RemoveAll(label) 1039 } 1040 } 1041 } 1042 } 1043} 1044 1045func TestAutoAcceptMultipleFolders(t *testing.T) { 1046 // Multiple new folders 1047 id1 := srand.String(8) 1048 defer os.RemoveAll(id1) 1049 id2 := srand.String(8) 1050 defer os.RemoveAll(id2) 1051 m, cancel := newState(t, defaultAutoAcceptCfg) 1052 defer cleanupModel(m) 1053 defer cancel() 1054 m.ClusterConfig(device1, createClusterConfig(device1, id1, id2)) 1055 if fcfg, ok := m.cfg.Folder(id1); !ok || !fcfg.SharedWith(device1) { 1056 t.Error("expected shared", id1) 1057 } 1058 if fcfg, ok := m.cfg.Folder(id2); !ok || !fcfg.SharedWith(device1) { 1059 t.Error("expected shared", id2) 1060 } 1061} 1062 1063func TestAutoAcceptExistingFolder(t *testing.T) { 1064 // Existing folder 1065 id := srand.String(8) 1066 idOther := srand.String(8) // To check that path does not get changed. 1067 defer os.RemoveAll(id) 1068 defer os.RemoveAll(idOther) 1069 1070 tcfg := defaultAutoAcceptCfg.Copy() 1071 tcfg.Folders = []config.FolderConfiguration{ 1072 { 1073 ID: id, 1074 Path: idOther, // To check that path does not get changed. 1075 }, 1076 } 1077 m, cancel := newState(t, tcfg) 1078 defer cleanupModel(m) 1079 defer cancel() 1080 if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device1) { 1081 t.Error("missing folder, or shared", id) 1082 } 1083 m.ClusterConfig(device1, createClusterConfig(device1, id)) 1084 1085 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) || fcfg.Path != idOther { 1086 t.Error("missing folder, or unshared, or path changed", id) 1087 } 1088} 1089 1090func TestAutoAcceptNewAndExistingFolder(t *testing.T) { 1091 // New and existing folder 1092 id1 := srand.String(8) 1093 defer os.RemoveAll(id1) 1094 id2 := srand.String(8) 1095 defer os.RemoveAll(id2) 1096 1097 tcfg := defaultAutoAcceptCfg.Copy() 1098 tcfg.Folders = []config.FolderConfiguration{ 1099 { 1100 ID: id1, 1101 Path: id1, // from previous test case, to verify that path doesn't get changed. 1102 }, 1103 } 1104 m, cancel := newState(t, tcfg) 1105 defer cleanupModel(m) 1106 defer cancel() 1107 if fcfg, ok := m.cfg.Folder(id1); !ok || fcfg.SharedWith(device1) { 1108 t.Error("missing folder, or shared", id1) 1109 } 1110 m.ClusterConfig(device1, createClusterConfig(device1, id1, id2)) 1111 1112 for i, id := range []string{id1, id2} { 1113 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { 1114 t.Error("missing folder, or unshared", i, id) 1115 } 1116 } 1117} 1118 1119func TestAutoAcceptAlreadyShared(t *testing.T) { 1120 // Already shared 1121 id := srand.String(8) 1122 defer os.RemoveAll(id) 1123 tcfg := defaultAutoAcceptCfg.Copy() 1124 tcfg.Folders = []config.FolderConfiguration{ 1125 { 1126 ID: id, 1127 Path: id, 1128 Devices: []config.FolderDeviceConfiguration{ 1129 { 1130 DeviceID: device1, 1131 }, 1132 }, 1133 }, 1134 } 1135 m, cancel := newState(t, tcfg) 1136 defer cleanupModel(m) 1137 defer cancel() 1138 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { 1139 t.Error("missing folder, or not shared", id) 1140 } 1141 m.ClusterConfig(device1, createClusterConfig(device1, id)) 1142 1143 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { 1144 t.Error("missing folder, or not shared", id) 1145 } 1146} 1147 1148func TestAutoAcceptNameConflict(t *testing.T) { 1149 testOs := &fatalOs{t} 1150 1151 id := srand.String(8) 1152 label := srand.String(8) 1153 testOs.MkdirAll(id, 0777) 1154 testOs.MkdirAll(label, 0777) 1155 defer os.RemoveAll(id) 1156 defer os.RemoveAll(label) 1157 m, cancel := newState(t, defaultAutoAcceptCfg) 1158 defer cleanupModel(m) 1159 defer cancel() 1160 m.ClusterConfig(device1, protocol.ClusterConfig{ 1161 Folders: []protocol.Folder{ 1162 { 1163 ID: id, 1164 Label: label, 1165 }, 1166 }, 1167 }) 1168 if fcfg, ok := m.cfg.Folder(id); ok && fcfg.SharedWith(device1) { 1169 t.Error("unexpected folder", id) 1170 } 1171} 1172 1173func TestAutoAcceptPrefersLabel(t *testing.T) { 1174 // Prefers label, falls back to ID. 1175 m, cancel := newState(t, defaultAutoAcceptCfg) 1176 id := srand.String(8) 1177 label := srand.String(8) 1178 defer os.RemoveAll(id) 1179 defer os.RemoveAll(label) 1180 defer cleanupModel(m) 1181 defer cancel() 1182 m.ClusterConfig(device1, addFolderDevicesToClusterConfig(protocol.ClusterConfig{ 1183 Folders: []protocol.Folder{ 1184 { 1185 ID: id, 1186 Label: label, 1187 }, 1188 }, 1189 }, device1)) 1190 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) || !strings.HasSuffix(fcfg.Path, label) { 1191 t.Error("expected shared, or wrong path", id, label, fcfg.Path) 1192 } 1193} 1194 1195func TestAutoAcceptFallsBackToID(t *testing.T) { 1196 testOs := &fatalOs{t} 1197 1198 // Prefers label, falls back to ID. 1199 m, cancel := newState(t, defaultAutoAcceptCfg) 1200 id := srand.String(8) 1201 label := srand.String(8) 1202 t.Log(id, label) 1203 testOs.MkdirAll(label, 0777) 1204 defer os.RemoveAll(label) 1205 defer os.RemoveAll(id) 1206 defer cleanupModel(m) 1207 defer cancel() 1208 m.ClusterConfig(device1, addFolderDevicesToClusterConfig(protocol.ClusterConfig{ 1209 Folders: []protocol.Folder{ 1210 { 1211 ID: id, 1212 Label: label, 1213 }, 1214 }, 1215 }, device1)) 1216 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) || !strings.HasSuffix(fcfg.Path, id) { 1217 t.Error("expected shared, or wrong path", id, label, fcfg.Path) 1218 } 1219} 1220 1221func TestAutoAcceptPausedWhenFolderConfigChanged(t *testing.T) { 1222 // Existing folder 1223 id := srand.String(8) 1224 idOther := srand.String(8) // To check that path does not get changed. 1225 defer os.RemoveAll(id) 1226 defer os.RemoveAll(idOther) 1227 1228 tcfg := defaultAutoAcceptCfg.Copy() 1229 fcfg := newFolderConfiguration(defaultCfgWrapper, id, "", fs.FilesystemTypeBasic, idOther) 1230 fcfg.Paused = true 1231 // The order of devices here is wrong (cfg.clean() sorts them), which will cause the folder to restart. 1232 // Because of the restart, folder gets removed from m.deviceFolder, which means that generateClusterConfig will not panic. 1233 // This wasn't an issue before, yet keeping the test case to prove that it still isn't. 1234 fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{ 1235 DeviceID: device1, 1236 }) 1237 tcfg.Folders = []config.FolderConfiguration{fcfg} 1238 m, cancel := newState(t, tcfg) 1239 defer cleanupModel(m) 1240 defer cancel() 1241 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { 1242 t.Error("missing folder, or not shared", id) 1243 } 1244 if _, ok := m.folderRunners[id]; ok { 1245 t.Fatal("folder running?") 1246 } 1247 1248 m.ClusterConfig(device1, createClusterConfig(device1, id)) 1249 m.generateClusterConfig(device1) 1250 1251 if fcfg, ok := m.cfg.Folder(id); !ok { 1252 t.Error("missing folder") 1253 } else if fcfg.Path != idOther { 1254 t.Error("folder path changed") 1255 } else { 1256 for _, dev := range fcfg.DeviceIDs() { 1257 if dev == device1 { 1258 return 1259 } 1260 } 1261 t.Error("device missing") 1262 } 1263 1264 if _, ok := m.folderRunners[id]; ok { 1265 t.Error("folder started") 1266 } 1267} 1268 1269func TestAutoAcceptPausedWhenFolderConfigNotChanged(t *testing.T) { 1270 // Existing folder 1271 id := srand.String(8) 1272 idOther := srand.String(8) // To check that path does not get changed. 1273 defer os.RemoveAll(id) 1274 defer os.RemoveAll(idOther) 1275 1276 tcfg := defaultAutoAcceptCfg.Copy() 1277 fcfg := newFolderConfiguration(defaultCfgWrapper, id, "", fs.FilesystemTypeBasic, idOther) 1278 fcfg.Paused = true 1279 // The new folder is exactly the same as the one constructed by handleAutoAccept, which means 1280 // the folder will not be restarted (even if it's paused), yet handleAutoAccept used to add the folder 1281 // to m.deviceFolders which had caused panics when calling generateClusterConfig, as the folder 1282 // did not have a file set. 1283 fcfg.Devices = append([]config.FolderDeviceConfiguration{ 1284 { 1285 DeviceID: device1, 1286 }, 1287 }, fcfg.Devices...) // Need to ensure this device order to avoid folder restart. 1288 tcfg.Folders = []config.FolderConfiguration{fcfg} 1289 m, cancel := newState(t, tcfg) 1290 defer cleanupModel(m) 1291 defer cancel() 1292 if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { 1293 t.Error("missing folder, or not shared", id) 1294 } 1295 if _, ok := m.folderRunners[id]; ok { 1296 t.Fatal("folder running?") 1297 } 1298 1299 m.ClusterConfig(device1, createClusterConfig(device1, id)) 1300 m.generateClusterConfig(device1) 1301 1302 if fcfg, ok := m.cfg.Folder(id); !ok { 1303 t.Error("missing folder") 1304 } else if fcfg.Path != idOther { 1305 t.Error("folder path changed") 1306 } else { 1307 for _, dev := range fcfg.DeviceIDs() { 1308 if dev == device1 { 1309 return 1310 } 1311 } 1312 t.Error("device missing") 1313 } 1314 1315 if _, ok := m.folderRunners[id]; ok { 1316 t.Error("folder started") 1317 } 1318} 1319 1320func TestAutoAcceptEnc(t *testing.T) { 1321 tcfg := defaultAutoAcceptCfg.Copy() 1322 m, cancel := newState(t, tcfg) 1323 defer cleanupModel(m) 1324 defer cancel() 1325 1326 id := srand.String(8) 1327 defer os.RemoveAll(id) 1328 1329 token := []byte("token") 1330 basicCC := func() protocol.ClusterConfig { 1331 return protocol.ClusterConfig{ 1332 Folders: []protocol.Folder{{ 1333 ID: id, 1334 Label: id, 1335 }}} 1336 } 1337 1338 // Earlier tests might cause the connection to get closed, thus ClusterConfig 1339 // would panic. 1340 clusterConfig := func(deviceID protocol.DeviceID, cm protocol.ClusterConfig) { 1341 m.AddConnection(newFakeConnection(deviceID, m), protocol.Hello{}) 1342 m.ClusterConfig(deviceID, cm) 1343 } 1344 1345 clusterConfig(device1, basicCC()) 1346 if _, ok := m.cfg.Folder(id); ok { 1347 t.Fatal("unexpected added") 1348 } 1349 cc := basicCC() 1350 cc.Folders[0].Devices = []protocol.Device{{ID: device1}} 1351 clusterConfig(device1, cc) 1352 if _, ok := m.cfg.Folder(id); ok { 1353 t.Fatal("unexpected added") 1354 } 1355 cc = basicCC() 1356 cc.Folders[0].Devices = []protocol.Device{{ID: myID}} 1357 clusterConfig(device1, cc) 1358 if _, ok := m.cfg.Folder(id); ok { 1359 t.Fatal("unexpected added") 1360 } 1361 1362 // New folder, encrypted -> add as enc 1363 1364 cc = createClusterConfig(device1, id) 1365 cc.Folders[0].Devices[1].EncryptionPasswordToken = token 1366 clusterConfig(device1, cc) 1367 if cfg, ok := m.cfg.Folder(id); !ok { 1368 t.Fatal("unexpected unadded") 1369 } else { 1370 if !cfg.SharedWith(device1) { 1371 t.Fatal("unexpected unshared") 1372 } 1373 if cfg.Type != config.FolderTypeReceiveEncrypted { 1374 t.Fatal("Folder not added as receiveEncrypted") 1375 } 1376 } 1377 1378 // New device, unencrypted on encrypted folder -> reject 1379 1380 clusterConfig(device2, createClusterConfig(device2, id)) 1381 if cfg, _ := m.cfg.Folder(id); cfg.SharedWith(device2) { 1382 t.Fatal("unexpected shared") 1383 } 1384 1385 // New device, encrypted on encrypted folder -> share 1386 1387 cc = createClusterConfig(device2, id) 1388 cc.Folders[0].Devices[1].EncryptionPasswordToken = token 1389 clusterConfig(device2, cc) 1390 if cfg, _ := m.cfg.Folder(id); !cfg.SharedWith(device2) { 1391 t.Fatal("unexpected unshared") 1392 } 1393 1394 // New folder, no encrypted -> add "normal" 1395 1396 id = srand.String(8) 1397 defer os.RemoveAll(id) 1398 1399 clusterConfig(device1, createClusterConfig(device1, id)) 1400 if cfg, ok := m.cfg.Folder(id); !ok { 1401 t.Fatal("unexpected unadded") 1402 } else { 1403 if !cfg.SharedWith(device1) { 1404 t.Fatal("unexpected unshared") 1405 } 1406 if cfg.Type != config.FolderTypeSendReceive { 1407 t.Fatal("Folder not added as send-receive") 1408 } 1409 } 1410 1411 // New device, encrypted on unencrypted folder -> reject 1412 1413 cc = createClusterConfig(device2, id) 1414 cc.Folders[0].Devices[1].EncryptionPasswordToken = token 1415 clusterConfig(device2, cc) 1416 if cfg, _ := m.cfg.Folder(id); cfg.SharedWith(device2) { 1417 t.Fatal("unexpected shared") 1418 } 1419 1420 // New device, unencrypted on unencrypted folder -> share 1421 1422 clusterConfig(device2, createClusterConfig(device2, id)) 1423 if cfg, _ := m.cfg.Folder(id); !cfg.SharedWith(device2) { 1424 t.Fatal("unexpected unshared") 1425 } 1426} 1427 1428func changeIgnores(t *testing.T, m *testModel, expected []string) { 1429 arrEqual := func(a, b []string) bool { 1430 if len(a) != len(b) { 1431 return false 1432 } 1433 1434 for i := range a { 1435 if a[i] != b[i] { 1436 return false 1437 } 1438 } 1439 return true 1440 } 1441 1442 ignores, _, err := m.LoadIgnores("default") 1443 if err != nil { 1444 t.Error(err) 1445 } 1446 1447 if !arrEqual(ignores, expected) { 1448 t.Errorf("Incorrect ignores: %v != %v", ignores, expected) 1449 } 1450 1451 ignores = append(ignores, "pox") 1452 1453 err = m.SetIgnores("default", ignores) 1454 if err != nil { 1455 t.Error(err) 1456 } 1457 1458 ignores2, _, err := m.LoadIgnores("default") 1459 if err != nil { 1460 t.Error(err) 1461 } 1462 1463 if !arrEqual(ignores, ignores2) { 1464 t.Errorf("Incorrect ignores: %v != %v", ignores2, ignores) 1465 } 1466 1467 if runtime.GOOS == "darwin" { 1468 // see above 1469 time.Sleep(time.Second) 1470 } else { 1471 time.Sleep(time.Millisecond) 1472 } 1473 err = m.SetIgnores("default", expected) 1474 if err != nil { 1475 t.Error(err) 1476 } 1477 1478 ignores, _, err = m.LoadIgnores("default") 1479 if err != nil { 1480 t.Error(err) 1481 } 1482 1483 if !arrEqual(ignores, expected) { 1484 t.Errorf("Incorrect ignores: %v != %v", ignores, expected) 1485 } 1486} 1487 1488func TestIgnores(t *testing.T) { 1489 // Assure a clean start state 1490 mustRemove(t, defaultFs.RemoveAll(config.DefaultMarkerName)) 1491 mustRemove(t, defaultFs.MkdirAll(config.DefaultMarkerName, 0644)) 1492 writeFile(defaultFs, ".stignore", []byte(".*\nquux\n"), 0644) 1493 1494 m := setupModel(t, defaultCfgWrapper) 1495 defer cleanupModel(m) 1496 1497 folderIgnoresAlwaysReload(t, m, defaultFolderConfig) 1498 1499 // Make sure the initial scan has finished (ScanFolders is blocking) 1500 m.ScanFolders() 1501 1502 expected := []string{ 1503 ".*", 1504 "quux", 1505 } 1506 1507 changeIgnores(t, m, expected) 1508 1509 _, _, err := m.LoadIgnores("doesnotexist") 1510 if err == nil { 1511 t.Error("No error") 1512 } 1513 1514 err = m.SetIgnores("doesnotexist", expected) 1515 if err == nil { 1516 t.Error("No error") 1517 } 1518 1519 // Invalid path, marker should be missing, hence returns an error. 1520 fcfg := config.FolderConfiguration{ID: "fresh", Path: "XXX"} 1521 ignores := ignore.New(fcfg.Filesystem(), ignore.WithCache(m.cfg.Options().CacheIgnoredFiles)) 1522 m.fmut.Lock() 1523 m.folderCfgs[fcfg.ID] = fcfg 1524 m.folderIgnores[fcfg.ID] = ignores 1525 m.fmut.Unlock() 1526 1527 _, _, err = m.LoadIgnores("fresh") 1528 if err == nil { 1529 t.Error("No error") 1530 } 1531 1532 // Repeat tests with paused folder 1533 pausedDefaultFolderConfig := defaultFolderConfig 1534 pausedDefaultFolderConfig.Paused = true 1535 1536 m.restartFolder(defaultFolderConfig, pausedDefaultFolderConfig, false) 1537 // Here folder initialization is not an issue as a paused folder isn't 1538 // added to the model and thus there is no initial scan happening. 1539 1540 changeIgnores(t, m, expected) 1541 1542 // Make sure no .stignore file is considered valid 1543 defer func() { 1544 must(t, defaultFs.Rename(".stignore.bak", ".stignore")) 1545 }() 1546 must(t, defaultFs.Rename(".stignore", ".stignore.bak")) 1547 changeIgnores(t, m, []string{}) 1548} 1549 1550func TestEmptyIgnores(t *testing.T) { 1551 // Assure a clean start state 1552 mustRemove(t, defaultFs.RemoveAll(config.DefaultMarkerName)) 1553 must(t, defaultFs.MkdirAll(config.DefaultMarkerName, 0644)) 1554 1555 m := setupModel(t, defaultCfgWrapper) 1556 defer cleanupModel(m) 1557 1558 if err := m.SetIgnores("default", []string{}); err != nil { 1559 t.Error(err) 1560 } 1561 if _, err := os.Stat("testdata/.stignore"); err == nil { 1562 t.Error(".stignore was created despite being empty") 1563 } 1564 1565 if err := m.SetIgnores("default", []string{".*", "quux"}); err != nil { 1566 t.Error(err) 1567 } 1568 if _, err := os.Stat("testdata/.stignore"); os.IsNotExist(err) { 1569 t.Error(".stignore does not exist") 1570 } 1571 1572 if err := m.SetIgnores("default", []string{}); err != nil { 1573 t.Error(err) 1574 } 1575 if _, err := os.Stat("testdata/.stignore"); err == nil { 1576 t.Error(".stignore should have been deleted because it is empty") 1577 } 1578} 1579 1580func waitForState(t *testing.T, sub events.Subscription, folder, expected string) { 1581 t.Helper() 1582 timeout := time.After(5 * time.Second) 1583 var error string 1584 for { 1585 select { 1586 case ev := <-sub.C(): 1587 data := ev.Data.(map[string]interface{}) 1588 if data["folder"].(string) == folder { 1589 if data["error"] == nil { 1590 error = "" 1591 } else { 1592 error = data["error"].(string) 1593 } 1594 if error == expected { 1595 return 1596 } 1597 } 1598 case <-timeout: 1599 t.Fatalf("Timed out waiting for status: %s, current status: %v", expected, error) 1600 } 1601 } 1602} 1603 1604func TestROScanRecovery(t *testing.T) { 1605 testOs := &fatalOs{t} 1606 1607 fcfg := config.FolderConfiguration{ 1608 ID: "default", 1609 Path: "rotestfolder", 1610 Type: config.FolderTypeSendOnly, 1611 RescanIntervalS: 1, 1612 MarkerName: config.DefaultMarkerName, 1613 } 1614 cfg, cancel := createTmpWrapper(config.Configuration{ 1615 Folders: []config.FolderConfiguration{fcfg}, 1616 Devices: []config.DeviceConfiguration{ 1617 { 1618 DeviceID: device1, 1619 }, 1620 }, 1621 }) 1622 defer cancel() 1623 m := newModel(t, cfg, myID, "syncthing", "dev", nil) 1624 1625 set := newFileSet(t, "default", defaultFs, m.db) 1626 set.Update(protocol.LocalDeviceID, []protocol.FileInfo{ 1627 {Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}}, 1628 }) 1629 1630 testOs.RemoveAll(fcfg.Path) 1631 1632 sub := m.evLogger.Subscribe(events.StateChanged) 1633 defer sub.Unsubscribe() 1634 m.ServeBackground() 1635 defer cleanupModel(m) 1636 1637 waitForState(t, sub, "default", "folder path missing") 1638 1639 testOs.Mkdir(fcfg.Path, 0700) 1640 1641 waitForState(t, sub, "default", config.ErrMarkerMissing.Error()) 1642 1643 fd := testOs.Create(filepath.Join(fcfg.Path, config.DefaultMarkerName)) 1644 fd.Close() 1645 1646 waitForState(t, sub, "default", "") 1647 1648 testOs.Remove(filepath.Join(fcfg.Path, config.DefaultMarkerName)) 1649 1650 waitForState(t, sub, "default", config.ErrMarkerMissing.Error()) 1651 1652 testOs.Remove(fcfg.Path) 1653 1654 waitForState(t, sub, "default", "folder path missing") 1655} 1656 1657func TestRWScanRecovery(t *testing.T) { 1658 testOs := &fatalOs{t} 1659 1660 fcfg := config.FolderConfiguration{ 1661 ID: "default", 1662 Path: "rwtestfolder", 1663 Type: config.FolderTypeSendReceive, 1664 RescanIntervalS: 1, 1665 MarkerName: config.DefaultMarkerName, 1666 } 1667 cfg, cancel := createTmpWrapper(config.Configuration{ 1668 Folders: []config.FolderConfiguration{fcfg}, 1669 Devices: []config.DeviceConfiguration{ 1670 { 1671 DeviceID: device1, 1672 }, 1673 }, 1674 }) 1675 defer cancel() 1676 m := newModel(t, cfg, myID, "syncthing", "dev", nil) 1677 1678 testOs.RemoveAll(fcfg.Path) 1679 1680 set := newFileSet(t, "default", defaultFs, m.db) 1681 set.Update(protocol.LocalDeviceID, []protocol.FileInfo{ 1682 {Name: "dummyfile", Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1}}}}, 1683 }) 1684 1685 sub := m.evLogger.Subscribe(events.StateChanged) 1686 defer sub.Unsubscribe() 1687 m.ServeBackground() 1688 defer cleanupModel(m) 1689 1690 waitForState(t, sub, "default", "folder path missing") 1691 1692 testOs.Mkdir(fcfg.Path, 0700) 1693 1694 waitForState(t, sub, "default", config.ErrMarkerMissing.Error()) 1695 1696 fd := testOs.Create(filepath.Join(fcfg.Path, config.DefaultMarkerName)) 1697 fd.Close() 1698 1699 waitForState(t, sub, "default", "") 1700 1701 testOs.Remove(filepath.Join(fcfg.Path, config.DefaultMarkerName)) 1702 1703 waitForState(t, sub, "default", config.ErrMarkerMissing.Error()) 1704 1705 testOs.Remove(fcfg.Path) 1706 1707 waitForState(t, sub, "default", "folder path missing") 1708} 1709 1710func TestGlobalDirectoryTree(t *testing.T) { 1711 m, _, fcfg, wCancel := setupModelWithConnection(t) 1712 defer wCancel() 1713 defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) 1714 1715 b := func(isfile bool, path ...string) protocol.FileInfo { 1716 typ := protocol.FileInfoTypeDirectory 1717 blocks := []protocol.BlockInfo{} 1718 if isfile { 1719 typ = protocol.FileInfoTypeFile 1720 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}}} 1721 } 1722 return protocol.FileInfo{ 1723 Name: filepath.Join(path...), 1724 Type: typ, 1725 ModifiedS: 0x666, 1726 Blocks: blocks, 1727 Size: 0xa, 1728 } 1729 } 1730 f := func(name string) *TreeEntry { 1731 return &TreeEntry{ 1732 Name: name, 1733 ModTime: time.Unix(0x666, 0), 1734 Size: 0xa, 1735 Type: protocol.FileInfoTypeFile, 1736 } 1737 } 1738 d := func(name string, entries ...*TreeEntry) *TreeEntry { 1739 return &TreeEntry{ 1740 Name: name, 1741 ModTime: time.Unix(0x666, 0), 1742 Size: 128, 1743 Type: protocol.FileInfoTypeDirectory, 1744 Children: entries, 1745 } 1746 } 1747 1748 testdata := []protocol.FileInfo{ 1749 b(false, "another"), 1750 b(false, "another", "directory"), 1751 b(true, "another", "directory", "afile"), 1752 b(false, "another", "directory", "with"), 1753 b(false, "another", "directory", "with", "a"), 1754 b(true, "another", "directory", "with", "a", "file"), 1755 b(true, "another", "directory", "with", "file"), 1756 b(true, "another", "file"), 1757 1758 b(false, "other"), 1759 b(false, "other", "rand"), 1760 b(false, "other", "random"), 1761 b(false, "other", "random", "dir"), 1762 b(false, "other", "random", "dirx"), 1763 b(false, "other", "randomx"), 1764 1765 b(false, "some"), 1766 b(false, "some", "directory"), 1767 b(false, "some", "directory", "with"), 1768 b(false, "some", "directory", "with", "a"), 1769 b(true, "some", "directory", "with", "a", "file"), 1770 1771 b(true, "zzrootfile"), 1772 } 1773 expectedResult := []*TreeEntry{ 1774 d("another", 1775 d("directory", 1776 f("afile"), 1777 d("with", 1778 d("a", 1779 f("file"), 1780 ), 1781 f("file"), 1782 ), 1783 ), 1784 f("file"), 1785 ), 1786 d("other", 1787 d("rand"), 1788 d("random", 1789 d("dir"), 1790 d("dirx"), 1791 ), 1792 d("randomx"), 1793 ), 1794 d("some", 1795 d("directory", 1796 d("with", 1797 d("a", 1798 f("file"), 1799 ), 1800 ), 1801 ), 1802 ), 1803 f("zzrootfile"), 1804 } 1805 1806 mm := func(data interface{}) string { 1807 bytes, err := json.MarshalIndent(data, "", " ") 1808 if err != nil { 1809 panic(err) 1810 } 1811 return string(bytes) 1812 } 1813 1814 must(t, m.Index(device1, "default", testdata)) 1815 1816 result, _ := m.GlobalDirectoryTree("default", "", -1, false) 1817 1818 if mm(result) != mm(expectedResult) { 1819 t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(expectedResult)) 1820 } 1821 1822 result, _ = m.GlobalDirectoryTree("default", "another", -1, false) 1823 1824 if mm(result) != mm(findByName(expectedResult, "another").Children) { 1825 t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(findByName(expectedResult, "another").Children)) 1826 } 1827 1828 result, _ = m.GlobalDirectoryTree("default", "", 0, false) 1829 currentResult := []*TreeEntry{ 1830 d("another"), 1831 d("other"), 1832 d("some"), 1833 f("zzrootfile"), 1834 } 1835 1836 if mm(result) != mm(currentResult) { 1837 t.Errorf("Does not match:\n%s\n============\n%s", mm(result), mm(currentResult)) 1838 } 1839 1840 result, _ = m.GlobalDirectoryTree("default", "", 1, false) 1841 currentResult = []*TreeEntry{ 1842 d("another", 1843 d("directory"), 1844 f("file"), 1845 ), 1846 d("other", 1847 d("rand"), 1848 d("random"), 1849 d("randomx"), 1850 ), 1851 d("some", 1852 d("directory"), 1853 ), 1854 f("zzrootfile"), 1855 } 1856 1857 if mm(result) != mm(currentResult) { 1858 t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) 1859 } 1860 1861 result, _ = m.GlobalDirectoryTree("default", "", -1, true) 1862 currentResult = []*TreeEntry{ 1863 d("another", 1864 d("directory", 1865 d("with", 1866 d("a"), 1867 ), 1868 ), 1869 ), 1870 d("other", 1871 d("rand"), 1872 d("random", 1873 d("dir"), 1874 d("dirx"), 1875 ), 1876 d("randomx"), 1877 ), 1878 d("some", 1879 d("directory", 1880 d("with", 1881 d("a"), 1882 ), 1883 ), 1884 ), 1885 } 1886 1887 if mm(result) != mm(currentResult) { 1888 t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) 1889 } 1890 1891 result, _ = m.GlobalDirectoryTree("default", "", 1, true) 1892 currentResult = []*TreeEntry{ 1893 d("another", 1894 d("directory"), 1895 ), 1896 d("other", 1897 d("rand"), 1898 d("random"), 1899 d("randomx"), 1900 ), 1901 d("some", 1902 d("directory"), 1903 ), 1904 } 1905 1906 if mm(result) != mm(currentResult) { 1907 t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) 1908 } 1909 1910 result, _ = m.GlobalDirectoryTree("default", "another", 0, false) 1911 currentResult = []*TreeEntry{ 1912 d("directory"), 1913 f("file"), 1914 } 1915 1916 if mm(result) != mm(currentResult) { 1917 t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) 1918 } 1919 1920 result, _ = m.GlobalDirectoryTree("default", "some/directory", 0, false) 1921 currentResult = []*TreeEntry{ 1922 d("with"), 1923 } 1924 1925 if mm(result) != mm(currentResult) { 1926 t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) 1927 } 1928 1929 result, _ = m.GlobalDirectoryTree("default", "some/directory", 1, false) 1930 currentResult = []*TreeEntry{ 1931 d("with", 1932 d("a"), 1933 ), 1934 } 1935 1936 if mm(result) != mm(currentResult) { 1937 t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) 1938 } 1939 1940 result, _ = m.GlobalDirectoryTree("default", "some/directory", 2, false) 1941 currentResult = []*TreeEntry{ 1942 d("with", 1943 d("a", 1944 f("file"), 1945 ), 1946 ), 1947 } 1948 1949 if mm(result) != mm(currentResult) { 1950 t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) 1951 } 1952 1953 result, _ = m.GlobalDirectoryTree("default", "another", -1, true) 1954 currentResult = []*TreeEntry{ 1955 d("directory", 1956 d("with", 1957 d("a"), 1958 ), 1959 ), 1960 } 1961 1962 if mm(result) != mm(currentResult) { 1963 t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) 1964 } 1965 1966 // No prefix matching! 1967 result, _ = m.GlobalDirectoryTree("default", "som", -1, false) 1968 currentResult = []*TreeEntry{} 1969 1970 if mm(result) != mm(currentResult) { 1971 t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) 1972 } 1973} 1974 1975func genDeepFiles(n, d int) []protocol.FileInfo { 1976 rand.Seed(int64(n)) 1977 files := make([]protocol.FileInfo, n) 1978 t := time.Now().Unix() 1979 for i := 0; i < n; i++ { 1980 path := "" 1981 for i := 0; i <= d; i++ { 1982 path = filepath.Join(path, strconv.Itoa(rand.Int())) 1983 } 1984 1985 sofar := "" 1986 for _, path := range filepath.SplitList(path) { 1987 sofar = filepath.Join(sofar, path) 1988 files[i] = protocol.FileInfo{ 1989 Name: sofar, 1990 } 1991 i++ 1992 } 1993 1994 files[i].ModifiedS = t 1995 files[i].Blocks = []protocol.BlockInfo{{Offset: 0, Size: 100, Hash: []byte("some hash bytes")}} 1996 } 1997 1998 return files 1999} 2000 2001func BenchmarkTree_10000_50(b *testing.B) { 2002 benchmarkTree(b, 10000, 50) 2003} 2004 2005func BenchmarkTree_100_50(b *testing.B) { 2006 benchmarkTree(b, 100, 50) 2007} 2008 2009func BenchmarkTree_100_10(b *testing.B) { 2010 benchmarkTree(b, 100, 10) 2011} 2012 2013func benchmarkTree(b *testing.B, n1, n2 int) { 2014 m, _, fcfg, wcfgCancel := setupModelWithConnection(b) 2015 defer wcfgCancel() 2016 defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) 2017 2018 m.ScanFolder(fcfg.ID) 2019 files := genDeepFiles(n1, n2) 2020 2021 must(b, m.Index(device1, fcfg.ID, files)) 2022 2023 b.ResetTimer() 2024 for i := 0; i < b.N; i++ { 2025 m.GlobalDirectoryTree(fcfg.ID, "", -1, false) 2026 } 2027 b.ReportAllocs() 2028} 2029 2030func TestIssue3028(t *testing.T) { 2031 // Create two files that we'll delete, one with a name that is a prefix of the other. 2032 2033 must(t, writeFile(defaultFs, "testrm", []byte("Hello"), 0644)) 2034 must(t, writeFile(defaultFs, "testrm2", []byte("Hello"), 0644)) 2035 defer func() { 2036 mustRemove(t, defaultFs.Remove("testrm")) 2037 mustRemove(t, defaultFs.Remove("testrm2")) 2038 }() 2039 2040 // Create a model and default folder 2041 2042 m := setupModel(t, defaultCfgWrapper) 2043 defer cleanupModel(m) 2044 2045 // Get a count of how many files are there now 2046 2047 locorigfiles := localSize(t, m, "default").Files 2048 globorigfiles := globalSize(t, m, "default").Files 2049 2050 // Delete and rescan specifically these two 2051 2052 must(t, defaultFs.Remove("testrm")) 2053 must(t, defaultFs.Remove("testrm2")) 2054 m.ScanFolderSubdirs("default", []string{"testrm", "testrm2"}) 2055 2056 // Verify that the number of files decreased by two and the number of 2057 // deleted files increases by two 2058 2059 loc := localSize(t, m, "default") 2060 glob := globalSize(t, m, "default") 2061 if loc.Files != locorigfiles-2 { 2062 t.Errorf("Incorrect local accounting; got %d current files, expected %d", loc.Files, locorigfiles-2) 2063 } 2064 if glob.Files != globorigfiles-2 { 2065 t.Errorf("Incorrect global accounting; got %d current files, expected %d", glob.Files, globorigfiles-2) 2066 } 2067 if loc.Deleted != 2 { 2068 t.Errorf("Incorrect local accounting; got %d deleted files, expected 2", loc.Deleted) 2069 } 2070 if glob.Deleted != 2 { 2071 t.Errorf("Incorrect global accounting; got %d deleted files, expected 2", glob.Deleted) 2072 } 2073} 2074 2075func TestIssue4357(t *testing.T) { 2076 cfg := defaultCfgWrapper.RawCopy() 2077 // Create a separate wrapper not to pollute other tests. 2078 wrapper, cancel := createTmpWrapper(config.Configuration{}) 2079 defer cancel() 2080 m := newModel(t, wrapper, myID, "syncthing", "dev", nil) 2081 m.ServeBackground() 2082 defer cleanupModel(m) 2083 2084 // Force the model to wire itself and add the folders 2085 replace(t, wrapper, cfg) 2086 2087 if _, ok := m.folderCfgs["default"]; !ok { 2088 t.Error("Folder should be running") 2089 } 2090 2091 newCfg := wrapper.RawCopy() 2092 newCfg.Folders[0].Paused = true 2093 2094 replace(t, wrapper, newCfg) 2095 2096 if _, ok := m.folderCfgs["default"]; ok { 2097 t.Error("Folder should not be running") 2098 } 2099 2100 if _, ok := m.cfg.Folder("default"); !ok { 2101 t.Error("should still have folder in config") 2102 } 2103 2104 replace(t, wrapper, config.Configuration{}) 2105 2106 if _, ok := m.cfg.Folder("default"); ok { 2107 t.Error("should not have folder in config") 2108 } 2109 2110 // Add the folder back, should be running 2111 replace(t, wrapper, cfg) 2112 2113 if _, ok := m.folderCfgs["default"]; !ok { 2114 t.Error("Folder should be running") 2115 } 2116 if _, ok := m.cfg.Folder("default"); !ok { 2117 t.Error("should still have folder in config") 2118 } 2119 2120 // Should not panic when removing a running folder. 2121 replace(t, wrapper, config.Configuration{}) 2122 2123 if _, ok := m.folderCfgs["default"]; ok { 2124 t.Error("Folder should not be running") 2125 } 2126 if _, ok := m.cfg.Folder("default"); ok { 2127 t.Error("should not have folder in config") 2128 } 2129} 2130 2131func TestIssue2782(t *testing.T) { 2132 // CheckHealth should accept a symlinked folder, when using tilde-expanded path. 2133 2134 if runtime.GOOS == "windows" { 2135 t.Skip("not reliable on Windows") 2136 return 2137 } 2138 home := os.Getenv("HOME") 2139 if home == "" { 2140 t.Skip("no home") 2141 } 2142 2143 // Create the test env. Needs to be based on $HOME as tilde expansion is 2144 // part of the issue. Skip the test if any of this fails, as we are a 2145 // bit outside of our stated domain here... 2146 2147 testName := ".syncthing-test." + srand.String(16) 2148 testDir := filepath.Join(home, testName) 2149 if err := os.RemoveAll(testDir); err != nil { 2150 t.Skip(err) 2151 } 2152 if err := os.MkdirAll(testDir+"/syncdir", 0755); err != nil { 2153 t.Skip(err) 2154 } 2155 if err := ioutil.WriteFile(testDir+"/syncdir/file", []byte("hello, world\n"), 0644); err != nil { 2156 t.Skip(err) 2157 } 2158 if err := os.Symlink("syncdir", testDir+"/synclink"); err != nil { 2159 t.Skip(err) 2160 } 2161 defer os.RemoveAll(testDir) 2162 2163 m := setupModel(t, defaultCfgWrapper) 2164 defer cleanupModel(m) 2165 2166 if err := m.ScanFolder("default"); err != nil { 2167 t.Error("scan error:", err) 2168 } 2169 2170 m.fmut.Lock() 2171 runner := m.folderRunners["default"] 2172 m.fmut.Unlock() 2173 if _, _, err := runner.getState(); err != nil { 2174 t.Error("folder error:", err) 2175 } 2176} 2177 2178func TestIndexesForUnknownDevicesDropped(t *testing.T) { 2179 m := newModel(t, defaultCfgWrapper, myID, "syncthing", "dev", nil) 2180 2181 files := newFileSet(t, "default", defaultFs, m.db) 2182 files.Drop(device1) 2183 files.Update(device1, genFiles(1)) 2184 files.Drop(device2) 2185 files.Update(device2, genFiles(1)) 2186 2187 if len(files.ListDevices()) != 2 { 2188 t.Error("expected two devices") 2189 } 2190 2191 m.newFolder(defaultFolderConfig, false) 2192 defer cleanupModel(m) 2193 2194 // Remote sequence is cached, hence need to recreated. 2195 files = newFileSet(t, "default", defaultFs, m.db) 2196 2197 if l := len(files.ListDevices()); l != 1 { 2198 t.Errorf("Expected one device got %v", l) 2199 } 2200} 2201 2202func TestSharedWithClearedOnDisconnect(t *testing.T) { 2203 wcfg, cancel := createTmpWrapper(defaultCfg) 2204 defer cancel() 2205 addDevice2(t, wcfg, wcfg.FolderList()[0]) 2206 2207 defer os.Remove(wcfg.ConfigPath()) 2208 2209 m := setupModel(t, wcfg) 2210 defer cleanupModel(m) 2211 2212 conn1 := newFakeConnection(device1, m) 2213 m.AddConnection(conn1, protocol.Hello{}) 2214 conn2 := newFakeConnection(device2, m) 2215 m.AddConnection(conn2, protocol.Hello{}) 2216 2217 m.ClusterConfig(device1, protocol.ClusterConfig{ 2218 Folders: []protocol.Folder{ 2219 { 2220 ID: "default", 2221 Devices: []protocol.Device{ 2222 {ID: myID}, 2223 {ID: device1}, 2224 {ID: device2}, 2225 }, 2226 }, 2227 }, 2228 }) 2229 m.ClusterConfig(device2, protocol.ClusterConfig{ 2230 Folders: []protocol.Folder{ 2231 { 2232 ID: "default", 2233 Devices: []protocol.Device{ 2234 {ID: myID}, 2235 {ID: device1}, 2236 {ID: device2}, 2237 }, 2238 }, 2239 }, 2240 }) 2241 2242 if fcfg, ok := m.cfg.Folder("default"); !ok || !fcfg.SharedWith(device1) { 2243 t.Error("not shared with device1") 2244 } 2245 if fcfg, ok := m.cfg.Folder("default"); !ok || !fcfg.SharedWith(device2) { 2246 t.Error("not shared with device2") 2247 } 2248 2249 select { 2250 case <-conn2.Closed(): 2251 t.Error("conn already closed") 2252 default: 2253 } 2254 2255 if _, err := wcfg.RemoveDevice(device2); err != nil { 2256 t.Error(err) 2257 } 2258 2259 time.Sleep(100 * time.Millisecond) // Committer notification happens in a separate routine 2260 2261 fcfg, ok := m.cfg.Folder("default") 2262 if !ok { 2263 t.Fatal("default folder missing") 2264 } 2265 if !fcfg.SharedWith(device1) { 2266 t.Error("not shared with device1") 2267 } 2268 if fcfg.SharedWith(device2) { 2269 t.Error("shared with device2") 2270 } 2271 for _, dev := range fcfg.Devices { 2272 if dev.DeviceID == device2 { 2273 t.Error("still there") 2274 } 2275 } 2276 2277 select { 2278 case <-conn2.Closed(): 2279 default: 2280 t.Error("connection not closed") 2281 } 2282 2283 if _, ok := wcfg.Devices()[device2]; ok { 2284 t.Error("device still in config") 2285 } 2286 2287 if _, ok := m.conn[device2]; ok { 2288 t.Error("conn not missing") 2289 } 2290 2291 if _, ok := m.helloMessages[device2]; ok { 2292 t.Error("hello not missing") 2293 } 2294 2295 if _, ok := m.deviceDownloads[device2]; ok { 2296 t.Error("downloads not missing") 2297 } 2298} 2299 2300func TestIssue3496(t *testing.T) { 2301 t.Skip("This test deletes files that the other test depend on. Needs fixing.") 2302 2303 // It seems like lots of deleted files can cause negative completion 2304 // percentages. Lets make sure that doesn't happen. Also do some general 2305 // checks on the completion calculation stuff. 2306 2307 m := setupModel(t, defaultCfgWrapper) 2308 defer cleanupModel(m) 2309 2310 m.ScanFolder("default") 2311 2312 addFakeConn(m, device1, "default") 2313 addFakeConn(m, device2, "default") 2314 2315 // Reach into the model and grab the current file list... 2316 2317 m.fmut.RLock() 2318 fs := m.folderFiles["default"] 2319 m.fmut.RUnlock() 2320 var localFiles []protocol.FileInfo 2321 snap := fsetSnapshot(t, fs) 2322 snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileIntf) bool { 2323 localFiles = append(localFiles, i.(protocol.FileInfo)) 2324 return true 2325 }) 2326 snap.Release() 2327 2328 // Mark all files as deleted and fake it as update from device1 2329 2330 for i := range localFiles { 2331 localFiles[i].Deleted = true 2332 localFiles[i].Version = localFiles[i].Version.Update(device1.Short()) 2333 localFiles[i].Blocks = nil 2334 } 2335 2336 // Also add a small file that we're supposed to need, or the global size 2337 // stuff will bail out early due to the entire folder being zero size. 2338 2339 localFiles = append(localFiles, protocol.FileInfo{ 2340 Name: "fake", 2341 Size: 1234, 2342 Type: protocol.FileInfoTypeFile, 2343 Version: protocol.Vector{Counters: []protocol.Counter{{ID: device1.Short(), Value: 42}}}, 2344 }) 2345 2346 must(t, m.IndexUpdate(device1, "default", localFiles)) 2347 2348 // Check that the completion percentage for us makes sense 2349 2350 comp := m.testCompletion(protocol.LocalDeviceID, "default") 2351 if comp.NeedBytes > comp.GlobalBytes { 2352 t.Errorf("Need more bytes than exist, not possible: %d > %d", comp.NeedBytes, comp.GlobalBytes) 2353 } 2354 if comp.CompletionPct < 0 { 2355 t.Errorf("Less than zero percent complete, not possible: %.02f%%", comp.CompletionPct) 2356 } 2357 if comp.NeedBytes == 0 { 2358 t.Error("Need no bytes even though some files are deleted") 2359 } 2360 if comp.CompletionPct == 100 { 2361 t.Errorf("Fully complete, not possible: %.02f%%", comp.CompletionPct) 2362 } 2363 t.Log(comp) 2364 2365 // Check that NeedSize does the correct thing 2366 need := needSizeLocal(t, m, "default") 2367 if need.Files != 1 || need.Bytes != 1234 { 2368 // The one we added synthetically above 2369 t.Errorf("Incorrect need size; %d, %d != 1, 1234", need.Files, need.Bytes) 2370 } 2371 if int(need.Deleted) != len(localFiles)-1 { 2372 // The rest 2373 t.Errorf("Incorrect need deletes; %d != %d", need.Deleted, len(localFiles)-1) 2374 } 2375} 2376 2377func TestIssue3804(t *testing.T) { 2378 m := setupModel(t, defaultCfgWrapper) 2379 defer cleanupModel(m) 2380 2381 // Subdirs ending in slash should be accepted 2382 2383 if err := m.ScanFolderSubdirs("default", []string{"baz/", "foo"}); err != nil { 2384 t.Error("Unexpected error:", err) 2385 } 2386} 2387 2388func TestIssue3829(t *testing.T) { 2389 m := setupModel(t, defaultCfgWrapper) 2390 defer cleanupModel(m) 2391 2392 // Empty subdirs should be accepted 2393 2394 if err := m.ScanFolderSubdirs("default", []string{""}); err != nil { 2395 t.Error("Unexpected error:", err) 2396 } 2397} 2398 2399func TestNoRequestsFromPausedDevices(t *testing.T) { 2400 t.Skip("broken, fails randomly, #3843") 2401 2402 wcfg, cancel := createTmpWrapper(defaultCfg) 2403 defer cancel() 2404 addDevice2(t, wcfg, wcfg.FolderList()[0]) 2405 2406 m := setupModel(t, wcfg) 2407 defer cleanupModel(m) 2408 2409 file := testDataExpected["foo"] 2410 files := m.folderFiles["default"] 2411 files.Update(device1, []protocol.FileInfo{file}) 2412 files.Update(device2, []protocol.FileInfo{file}) 2413 2414 avail := m.testAvailability("default", file, file.Blocks[0]) 2415 if len(avail) != 0 { 2416 t.Errorf("should not be available, no connections") 2417 } 2418 2419 addFakeConn(m, device1, "default") 2420 addFakeConn(m, device2, "default") 2421 2422 // !!! This is not what I'd expect to happen, as we don't even know if the peer has the original index !!! 2423 2424 avail = m.testAvailability("default", file, file.Blocks[0]) 2425 if len(avail) != 2 { 2426 t.Errorf("should have two available") 2427 } 2428 2429 cc := protocol.ClusterConfig{ 2430 Folders: []protocol.Folder{ 2431 { 2432 ID: "default", 2433 Devices: []protocol.Device{ 2434 {ID: device1}, 2435 {ID: device2}, 2436 }, 2437 }, 2438 }, 2439 } 2440 2441 m.ClusterConfig(device1, cc) 2442 m.ClusterConfig(device2, cc) 2443 2444 avail = m.testAvailability("default", file, file.Blocks[0]) 2445 if len(avail) != 2 { 2446 t.Errorf("should have two available") 2447 } 2448 2449 m.Closed(device1, errDeviceUnknown) 2450 m.Closed(device2, errDeviceUnknown) 2451 2452 avail = m.testAvailability("default", file, file.Blocks[0]) 2453 if len(avail) != 0 { 2454 t.Errorf("should have no available") 2455 } 2456 2457 // Test that remote paused folders are not used. 2458 2459 addFakeConn(m, device1, "default") 2460 addFakeConn(m, device2, "default") 2461 2462 m.ClusterConfig(device1, cc) 2463 ccp := cc 2464 ccp.Folders[0].Paused = true 2465 m.ClusterConfig(device1, ccp) 2466 2467 avail = m.testAvailability("default", file, file.Blocks[0]) 2468 if len(avail) != 1 { 2469 t.Errorf("should have one available") 2470 } 2471} 2472 2473// TestIssue2571 tests replacing a directory with content with a symlink 2474func TestIssue2571(t *testing.T) { 2475 if runtime.GOOS == "windows" { 2476 t.Skip("Scanning symlinks isn't supported on windows") 2477 } 2478 2479 w, fcfg, wCancel := tmpDefaultWrapper() 2480 defer wCancel() 2481 testFs := fcfg.Filesystem() 2482 defer os.RemoveAll(testFs.URI()) 2483 2484 for _, dir := range []string{"toLink", "linkTarget"} { 2485 must(t, testFs.MkdirAll(dir, 0775)) 2486 fd, err := testFs.Create(filepath.Join(dir, "a")) 2487 must(t, err) 2488 fd.Close() 2489 } 2490 2491 m := setupModel(t, w) 2492 defer cleanupModel(m) 2493 2494 must(t, testFs.RemoveAll("toLink")) 2495 2496 must(t, fs.DebugSymlinkForTestsOnly(testFs, testFs, "linkTarget", "toLink")) 2497 2498 m.ScanFolder("default") 2499 2500 if dir, ok := m.testCurrentFolderFile("default", "toLink"); !ok { 2501 t.Fatalf("Dir missing in db") 2502 } else if !dir.IsSymlink() { 2503 t.Errorf("Dir wasn't changed to symlink") 2504 } 2505 if file, ok := m.testCurrentFolderFile("default", filepath.Join("toLink", "a")); !ok { 2506 t.Fatalf("File missing in db") 2507 } else if !file.Deleted { 2508 t.Errorf("File below symlink has not been marked as deleted") 2509 } 2510} 2511 2512// TestIssue4573 tests that contents of an unavailable dir aren't marked deleted 2513func TestIssue4573(t *testing.T) { 2514 if runtime.GOOS == "windows" { 2515 t.Skip("Can't make the dir inaccessible on windows") 2516 } 2517 2518 w, fcfg, wCancel := tmpDefaultWrapper() 2519 defer wCancel() 2520 testFs := fcfg.Filesystem() 2521 defer os.RemoveAll(testFs.URI()) 2522 2523 must(t, testFs.MkdirAll("inaccessible", 0755)) 2524 defer testFs.Chmod("inaccessible", 0777) 2525 2526 file := filepath.Join("inaccessible", "a") 2527 fd, err := testFs.Create(file) 2528 must(t, err) 2529 fd.Close() 2530 2531 m := setupModel(t, w) 2532 defer cleanupModel(m) 2533 2534 must(t, testFs.Chmod("inaccessible", 0000)) 2535 2536 m.ScanFolder("default") 2537 2538 if file, ok := m.testCurrentFolderFile("default", file); !ok { 2539 t.Fatalf("File missing in db") 2540 } else if file.Deleted { 2541 t.Errorf("Inaccessible file has been marked as deleted.") 2542 } 2543} 2544 2545// TestInternalScan checks whether various fs operations are correctly represented 2546// in the db after scanning. 2547func TestInternalScan(t *testing.T) { 2548 w, fcfg, wCancel := tmpDefaultWrapper() 2549 defer wCancel() 2550 testFs := fcfg.Filesystem() 2551 defer os.RemoveAll(testFs.URI()) 2552 2553 testCases := map[string]func(protocol.FileInfo) bool{ 2554 "removeDir": func(f protocol.FileInfo) bool { 2555 return !f.Deleted 2556 }, 2557 "dirToFile": func(f protocol.FileInfo) bool { 2558 return f.Deleted || f.IsDirectory() 2559 }, 2560 } 2561 2562 baseDirs := []string{"dirToFile", "removeDir"} 2563 for _, dir := range baseDirs { 2564 sub := filepath.Join(dir, "subDir") 2565 for _, dir := range []string{dir, sub} { 2566 if err := testFs.MkdirAll(dir, 0775); err != nil { 2567 t.Fatalf("%v: %v", dir, err) 2568 } 2569 } 2570 testCases[sub] = func(f protocol.FileInfo) bool { 2571 return !f.Deleted 2572 } 2573 for _, dir := range []string{dir, sub} { 2574 file := filepath.Join(dir, "a") 2575 fd, err := testFs.Create(file) 2576 must(t, err) 2577 fd.Close() 2578 testCases[file] = func(f protocol.FileInfo) bool { 2579 return !f.Deleted 2580 } 2581 } 2582 } 2583 2584 m := setupModel(t, w) 2585 defer cleanupModel(m) 2586 2587 for _, dir := range baseDirs { 2588 must(t, testFs.RemoveAll(dir)) 2589 } 2590 2591 fd, err := testFs.Create("dirToFile") 2592 must(t, err) 2593 fd.Close() 2594 2595 m.ScanFolder("default") 2596 2597 for path, cond := range testCases { 2598 if f, ok := m.testCurrentFolderFile("default", path); !ok { 2599 t.Fatalf("%v missing in db", path) 2600 } else if cond(f) { 2601 t.Errorf("Incorrect db entry for %v", path) 2602 } 2603 } 2604} 2605 2606func TestCustomMarkerName(t *testing.T) { 2607 testOs := &fatalOs{t} 2608 2609 fcfg := testFolderConfigTmp() 2610 fcfg.ID = "default" 2611 fcfg.RescanIntervalS = 1 2612 fcfg.MarkerName = "myfile" 2613 cfg, cancel := createTmpWrapper(config.Configuration{ 2614 Folders: []config.FolderConfiguration{fcfg}, 2615 Devices: []config.DeviceConfiguration{ 2616 { 2617 DeviceID: device1, 2618 }, 2619 }, 2620 }) 2621 defer cancel() 2622 2623 testOs.RemoveAll(fcfg.Path) 2624 2625 m := newModel(t, cfg, myID, "syncthing", "dev", nil) 2626 set := newFileSet(t, "default", defaultFs, m.db) 2627 set.Update(protocol.LocalDeviceID, []protocol.FileInfo{ 2628 {Name: "dummyfile"}, 2629 }) 2630 2631 sub := m.evLogger.Subscribe(events.StateChanged) 2632 defer sub.Unsubscribe() 2633 m.ServeBackground() 2634 defer cleanupModelAndRemoveDir(m, fcfg.Path) 2635 2636 waitForState(t, sub, "default", "folder path missing") 2637 2638 testOs.Mkdir(fcfg.Path, 0700) 2639 fd := testOs.Create(filepath.Join(fcfg.Path, "myfile")) 2640 fd.Close() 2641 2642 waitForState(t, sub, "default", "") 2643} 2644 2645func TestRemoveDirWithContent(t *testing.T) { 2646 m, _, fcfg, wcfgCancel := setupModelWithConnection(t) 2647 defer wcfgCancel() 2648 tfs := fcfg.Filesystem() 2649 defer cleanupModelAndRemoveDir(m, tfs.URI()) 2650 2651 tfs.MkdirAll("dirwith", 0755) 2652 content := filepath.Join("dirwith", "content") 2653 fd, err := tfs.Create(content) 2654 must(t, err) 2655 fd.Close() 2656 2657 must(t, m.ScanFolder(fcfg.ID)) 2658 2659 dir, ok := m.testCurrentFolderFile(fcfg.ID, "dirwith") 2660 if !ok { 2661 t.Fatalf("Can't get dir \"dirwith\" after initial scan") 2662 } 2663 dir.Deleted = true 2664 dir.Version = dir.Version.Update(device1.Short()).Update(device1.Short()) 2665 2666 file, ok := m.testCurrentFolderFile(fcfg.ID, content) 2667 if !ok { 2668 t.Fatalf("Can't get file \"%v\" after initial scan", content) 2669 } 2670 file.Deleted = true 2671 file.Version = file.Version.Update(device1.Short()).Update(device1.Short()) 2672 2673 must(t, m.IndexUpdate(device1, fcfg.ID, []protocol.FileInfo{dir, file})) 2674 2675 // Is there something we could trigger on instead of just waiting? 2676 timeout := time.NewTimer(5 * time.Second) 2677 for { 2678 dir, ok := m.testCurrentFolderFile(fcfg.ID, "dirwith") 2679 if !ok { 2680 t.Fatalf("Can't get dir \"dirwith\" after index update") 2681 } 2682 file, ok := m.testCurrentFolderFile(fcfg.ID, content) 2683 if !ok { 2684 t.Fatalf("Can't get file \"%v\" after index update", content) 2685 } 2686 if dir.Deleted && file.Deleted { 2687 return 2688 } 2689 2690 select { 2691 case <-timeout.C: 2692 if !dir.Deleted && !file.Deleted { 2693 t.Errorf("Neither the dir nor its content was deleted before timing out.") 2694 } else if !dir.Deleted { 2695 t.Errorf("The dir was not deleted before timing out.") 2696 } else { 2697 t.Errorf("The content of the dir was not deleted before timing out.") 2698 } 2699 return 2700 default: 2701 time.Sleep(100 * time.Millisecond) 2702 } 2703 } 2704} 2705 2706func TestIssue4475(t *testing.T) { 2707 m, conn, fcfg, wcfgCancel := setupModelWithConnection(t) 2708 defer wcfgCancel() 2709 defer cleanupModel(m) 2710 testFs := fcfg.Filesystem() 2711 2712 // Scenario: Dir is deleted locally and before syncing/index exchange 2713 // happens, a file is create in that dir on the remote. 2714 // This should result in the directory being recreated and added to the 2715 // db locally. 2716 2717 must(t, testFs.MkdirAll("delDir", 0755)) 2718 2719 m.ScanFolder("default") 2720 2721 if fcfg, ok := m.cfg.Folder("default"); !ok || !fcfg.SharedWith(device1) { 2722 t.Fatal("not shared with device1") 2723 } 2724 2725 fileName := filepath.Join("delDir", "file") 2726 conn.addFile(fileName, 0644, protocol.FileInfoTypeFile, nil) 2727 conn.sendIndexUpdate() 2728 2729 // Is there something we could trigger on instead of just waiting? 2730 timeout := time.NewTimer(5 * time.Second) 2731 created := false 2732 for { 2733 if !created { 2734 if _, ok := m.testCurrentFolderFile("default", fileName); ok { 2735 created = true 2736 } 2737 } else { 2738 dir, ok := m.testCurrentFolderFile("default", "delDir") 2739 if !ok { 2740 t.Fatalf("can't get dir from db") 2741 } 2742 if !dir.Deleted { 2743 return 2744 } 2745 } 2746 2747 select { 2748 case <-timeout.C: 2749 if created { 2750 t.Errorf("Timed out before file from remote was created") 2751 } else { 2752 t.Errorf("Timed out before directory was resurrected in db") 2753 } 2754 return 2755 default: 2756 time.Sleep(100 * time.Millisecond) 2757 } 2758 } 2759} 2760 2761func TestVersionRestore(t *testing.T) { 2762 // We create a bunch of files which we restore 2763 // In each file, we write the filename as the content 2764 // We verify that the content matches at the expected filenames 2765 // after the restore operation. 2766 dir, err := ioutil.TempDir("", "") 2767 must(t, err) 2768 defer os.RemoveAll(dir) 2769 2770 fcfg := newFolderConfiguration(defaultCfgWrapper, "default", "default", fs.FilesystemTypeBasic, dir) 2771 fcfg.Versioning.Type = "simple" 2772 fcfg.FSWatcherEnabled = false 2773 filesystem := fcfg.Filesystem() 2774 2775 rawConfig := config.Configuration{ 2776 Folders: []config.FolderConfiguration{fcfg}, 2777 } 2778 cfg, cancel := createTmpWrapper(rawConfig) 2779 defer cancel() 2780 2781 m := setupModel(t, cfg) 2782 defer cleanupModel(m) 2783 m.ScanFolder("default") 2784 2785 sentinel, err := time.ParseInLocation(versioner.TimeFormat, "20180101-010101", time.Local) 2786 if err != nil { 2787 t.Fatal(err) 2788 } 2789 2790 for _, file := range []string{ 2791 // Versions directory 2792 ".stversions/file~20171210-040404.txt", // will be restored 2793 ".stversions/existing~20171210-040404", // exists, should expect to be archived. 2794 ".stversions/something~20171210-040404", // will become directory, hence error 2795 ".stversions/dir/file~20171210-040404.txt", 2796 ".stversions/dir/file~20171210-040405.txt", 2797 ".stversions/dir/file~20171210-040406.txt", 2798 ".stversions/very/very/deep/one~20171210-040406.txt", // lives deep down, no directory exists. 2799 ".stversions/dir/existing~20171210-040406.txt", // exists, should expect to be archived. 2800 ".stversions/dir/cat", // untagged which was used by trashcan, supported 2801 2802 // "file.txt" will be restored 2803 "existing", 2804 "something/file", // Becomes directory 2805 "dir/file.txt", 2806 "dir/existing.txt", 2807 } { 2808 if runtime.GOOS == "windows" { 2809 file = filepath.FromSlash(file) 2810 } 2811 dir := filepath.Dir(file) 2812 must(t, filesystem.MkdirAll(dir, 0755)) 2813 if fd, err := filesystem.Create(file); err != nil { 2814 t.Fatal(err) 2815 } else if _, err := fd.Write([]byte(file)); err != nil { 2816 t.Fatal(err) 2817 } else if err := fd.Close(); err != nil { 2818 t.Fatal(err) 2819 } else if err := filesystem.Chtimes(file, sentinel, sentinel); err != nil { 2820 t.Fatal(err) 2821 } 2822 } 2823 2824 versions, err := m.GetFolderVersions("default") 2825 must(t, err) 2826 expectedVersions := map[string]int{ 2827 "file.txt": 1, 2828 "existing": 1, 2829 "something": 1, 2830 "dir/file.txt": 3, 2831 "dir/existing.txt": 1, 2832 "very/very/deep/one.txt": 1, 2833 "dir/cat": 1, 2834 } 2835 2836 for name, vers := range versions { 2837 cnt, ok := expectedVersions[name] 2838 if !ok { 2839 t.Errorf("unexpected %s", name) 2840 } 2841 if len(vers) != cnt { 2842 t.Errorf("%s: %d != %d", name, cnt, len(vers)) 2843 } 2844 // Delete, so we can check if we didn't hit something we expect afterwards. 2845 delete(expectedVersions, name) 2846 } 2847 2848 for name := range expectedVersions { 2849 t.Errorf("not found expected %s", name) 2850 } 2851 2852 // Restoring non existing folder fails. 2853 _, err = m.RestoreFolderVersions("does not exist", nil) 2854 if err == nil { 2855 t.Errorf("expected an error") 2856 } 2857 2858 makeTime := func(s string) time.Time { 2859 tm, err := time.ParseInLocation(versioner.TimeFormat, s, time.Local) 2860 if err != nil { 2861 t.Error(err) 2862 } 2863 return tm.Truncate(time.Second) 2864 } 2865 2866 restore := map[string]time.Time{ 2867 "file.txt": makeTime("20171210-040404"), 2868 "existing": makeTime("20171210-040404"), 2869 "something": makeTime("20171210-040404"), 2870 "dir/file.txt": makeTime("20171210-040406"), 2871 "dir/existing.txt": makeTime("20171210-040406"), 2872 "very/very/deep/one.txt": makeTime("20171210-040406"), 2873 } 2874 2875 beforeRestore := time.Now().Truncate(time.Second) 2876 2877 ferr, err := m.RestoreFolderVersions("default", restore) 2878 must(t, err) 2879 2880 if err, ok := ferr["something"]; len(ferr) > 1 || !ok || !errors.Is(err, versioner.ErrDirectory) { 2881 t.Fatalf("incorrect error or count: %d %s", len(ferr), ferr) 2882 } 2883 2884 // Failed items are not expected to be restored. 2885 // Remove them from expectations 2886 for name := range ferr { 2887 delete(restore, name) 2888 } 2889 2890 // Check that content of files matches to the version they've been restored. 2891 for file, version := range restore { 2892 if runtime.GOOS == "windows" { 2893 file = filepath.FromSlash(file) 2894 } 2895 tag := version.In(time.Local).Truncate(time.Second).Format(versioner.TimeFormat) 2896 taggedName := filepath.Join(".stversions", versioner.TagFilename(file, tag)) 2897 fd, err := filesystem.Open(file) 2898 if err != nil { 2899 t.Error(err) 2900 } 2901 defer fd.Close() 2902 2903 content, err := ioutil.ReadAll(fd) 2904 if err != nil { 2905 t.Error(err) 2906 } 2907 if !bytes.Equal(content, []byte(taggedName)) { 2908 t.Errorf("%s: %s != %s", file, string(content), taggedName) 2909 } 2910 } 2911 2912 // Simple versioner uses now for timestamp generation, so we can check 2913 // if existing stuff was correctly archived as we restored (oppose to deleteD), and version time as after beforeRestore 2914 expectArchived := map[string]struct{}{ 2915 "existing": {}, 2916 "dir/file.txt": {}, 2917 "dir/existing.txt": {}, 2918 } 2919 2920 allFileVersions, err := m.GetFolderVersions("default") 2921 must(t, err) 2922 for file, versions := range allFileVersions { 2923 key := file 2924 if runtime.GOOS == "windows" { 2925 file = filepath.FromSlash(file) 2926 } 2927 for _, version := range versions { 2928 if version.VersionTime.Equal(beforeRestore) || version.VersionTime.After(beforeRestore) { 2929 fd, err := filesystem.Open(".stversions/" + versioner.TagFilename(file, version.VersionTime.Format(versioner.TimeFormat))) 2930 must(t, err) 2931 defer fd.Close() 2932 2933 content, err := ioutil.ReadAll(fd) 2934 if err != nil { 2935 t.Error(err) 2936 } 2937 // Even if they are at the archived path, content should have the non 2938 // archived name. 2939 if !bytes.Equal(content, []byte(file)) { 2940 t.Errorf("%s (%s): %s != %s", file, fd.Name(), string(content), file) 2941 } 2942 _, ok := expectArchived[key] 2943 if !ok { 2944 t.Error("unexpected archived file with future timestamp", file, version.VersionTime) 2945 } 2946 delete(expectArchived, key) 2947 } 2948 } 2949 } 2950 2951 if len(expectArchived) != 0 { 2952 t.Fatal("missed some archived files", expectArchived) 2953 } 2954} 2955 2956func TestPausedFolders(t *testing.T) { 2957 // Create a separate wrapper not to pollute other tests. 2958 wrapper, cancel := createTmpWrapper(defaultCfgWrapper.RawCopy()) 2959 defer cancel() 2960 m := setupModel(t, wrapper) 2961 defer cleanupModel(m) 2962 2963 if err := m.ScanFolder("default"); err != nil { 2964 t.Error(err) 2965 } 2966 2967 pausedConfig := wrapper.RawCopy() 2968 pausedConfig.Folders[0].Paused = true 2969 replace(t, wrapper, pausedConfig) 2970 2971 if err := m.ScanFolder("default"); err != ErrFolderPaused { 2972 t.Errorf("Expected folder paused error, received: %v", err) 2973 } 2974 2975 if err := m.ScanFolder("nonexistent"); err != ErrFolderMissing { 2976 t.Errorf("Expected missing folder error, received: %v", err) 2977 } 2978} 2979 2980func TestIssue4094(t *testing.T) { 2981 testOs := &fatalOs{t} 2982 2983 // Create a separate wrapper not to pollute other tests. 2984 wrapper, cancel := createTmpWrapper(config.Configuration{}) 2985 defer cancel() 2986 m := newModel(t, wrapper, myID, "syncthing", "dev", nil) 2987 m.ServeBackground() 2988 defer cleanupModel(m) 2989 2990 // Force the model to wire itself and add the folders 2991 folderPath := "nonexistent" 2992 defer testOs.RemoveAll(folderPath) 2993 cfg := defaultCfgWrapper.RawCopy() 2994 fcfg := config.FolderConfiguration{ 2995 ID: "folder1", 2996 Path: folderPath, 2997 Paused: true, 2998 Devices: []config.FolderDeviceConfiguration{ 2999 {DeviceID: device1}, 3000 }, 3001 } 3002 cfg.Folders = []config.FolderConfiguration{fcfg} 3003 replace(t, wrapper, cfg) 3004 3005 if err := m.SetIgnores(fcfg.ID, []string{"foo"}); err != nil { 3006 t.Fatalf("failed setting ignores: %v", err) 3007 } 3008 3009 if _, err := fcfg.Filesystem().Lstat(".stignore"); err != nil { 3010 t.Fatalf("failed stating .stignore: %v", err) 3011 } 3012} 3013 3014func TestIssue4903(t *testing.T) { 3015 testOs := &fatalOs{t} 3016 3017 wrapper, cancel := createTmpWrapper(config.Configuration{}) 3018 defer cancel() 3019 m := setupModel(t, wrapper) 3020 defer cleanupModel(m) 3021 3022 // Force the model to wire itself and add the folders 3023 folderPath := "nonexistent" 3024 defer testOs.RemoveAll(folderPath) 3025 cfg := defaultCfgWrapper.RawCopy() 3026 fcfg := config.FolderConfiguration{ 3027 ID: "folder1", 3028 Path: folderPath, 3029 Paused: true, 3030 Devices: []config.FolderDeviceConfiguration{ 3031 {DeviceID: device1}, 3032 }, 3033 } 3034 cfg.Folders = []config.FolderConfiguration{fcfg} 3035 replace(t, wrapper, cfg) 3036 3037 if err := fcfg.CheckPath(); err != config.ErrPathMissing { 3038 t.Fatalf("expected path missing error, got: %v", err) 3039 } 3040 3041 if _, err := fcfg.Filesystem().Lstat("."); !fs.IsNotExist(err) { 3042 t.Fatalf("Expected missing path error, got: %v", err) 3043 } 3044} 3045 3046func TestIssue5002(t *testing.T) { 3047 // recheckFile should not panic when given an index equal to the number of blocks 3048 3049 m := setupModel(t, defaultCfgWrapper) 3050 defer cleanupModel(m) 3051 3052 if err := m.ScanFolder("default"); err != nil { 3053 t.Error(err) 3054 } 3055 3056 file, ok := m.testCurrentFolderFile("default", "foo") 3057 if !ok { 3058 t.Fatal("test file should exist") 3059 } 3060 blockSize := int32(file.BlockSize()) 3061 3062 m.recheckFile(protocol.LocalDeviceID, "default", "foo", file.Size-int64(blockSize), []byte{1, 2, 3, 4}, 0) 3063 m.recheckFile(protocol.LocalDeviceID, "default", "foo", file.Size, []byte{1, 2, 3, 4}, 0) // panic 3064 m.recheckFile(protocol.LocalDeviceID, "default", "foo", file.Size+int64(blockSize), []byte{1, 2, 3, 4}, 0) 3065} 3066 3067func TestParentOfUnignored(t *testing.T) { 3068 m, cancel := newState(t, defaultCfg) 3069 defer cleanupModel(m) 3070 defer cancel() 3071 defer defaultFolderConfig.Filesystem().Remove(".stignore") 3072 3073 m.SetIgnores("default", []string{"!quux", "*"}) 3074 3075 if parent, ok := m.testCurrentFolderFile("default", "baz"); !ok { 3076 t.Errorf(`Directory "baz" missing in db`) 3077 } else if parent.IsIgnored() { 3078 t.Errorf(`Directory "baz" is ignored`) 3079 } 3080} 3081 3082// TestFolderRestartZombies reproduces issue 5233, where multiple concurrent folder 3083// restarts would leave more than one folder runner alive. 3084func TestFolderRestartZombies(t *testing.T) { 3085 wrapper, cancel := createTmpWrapper(defaultCfg.Copy()) 3086 defer cancel() 3087 waiter, err := wrapper.Modify(func(cfg *config.Configuration) { 3088 cfg.Options.RawMaxFolderConcurrency = -1 3089 _, i, _ := cfg.Folder("default") 3090 cfg.Folders[i].FilesystemType = fs.FilesystemTypeFake 3091 }) 3092 must(t, err) 3093 waiter.Wait() 3094 folderCfg, _ := wrapper.Folder("default") 3095 3096 m := setupModel(t, wrapper) 3097 defer cleanupModel(m) 3098 3099 // Make sure the folder is up and running, because we want to count it. 3100 m.ScanFolder("default") 3101 3102 // Check how many running folders we have running before the test. 3103 if r := atomic.LoadInt32(&m.foldersRunning); r != 1 { 3104 t.Error("Expected one running folder, not", r) 3105 } 3106 3107 // Run a few parallel configuration changers for one second. Each waits 3108 // for the commit to complete, but there are many of them. 3109 var wg sync.WaitGroup 3110 for i := 0; i < 25; i++ { 3111 wg.Add(1) 3112 go func() { 3113 defer wg.Done() 3114 t0 := time.Now() 3115 for time.Since(t0) < time.Second { 3116 fcfg := folderCfg.Copy() 3117 fcfg.MaxConflicts = rand.Int() // safe change that should cause a folder restart 3118 setFolder(t, wrapper, fcfg) 3119 } 3120 }() 3121 } 3122 3123 // Wait for the above to complete and check how many folders we have 3124 // running now. It should not have increased. 3125 wg.Wait() 3126 // Make sure the folder is up and running, because we want to count it. 3127 m.ScanFolder("default") 3128 if r := atomic.LoadInt32(&m.foldersRunning); r != 1 { 3129 t.Error("Expected one running folder, not", r) 3130 } 3131} 3132 3133func TestRequestLimit(t *testing.T) { 3134 wrapper, cancel := createTmpWrapper(defaultCfg.Copy()) 3135 defer cancel() 3136 waiter, err := wrapper.Modify(func(cfg *config.Configuration) { 3137 _, i, _ := cfg.Device(device1) 3138 cfg.Devices[i].MaxRequestKiB = 1 3139 }) 3140 must(t, err) 3141 waiter.Wait() 3142 m, _ := setupModelWithConnectionFromWrapper(t, wrapper) 3143 defer cleanupModel(m) 3144 3145 file := "tmpfile" 3146 befReq := time.Now() 3147 first, err := m.Request(device1, "default", file, 0, 2000, 0, nil, 0, false) 3148 if err != nil { 3149 t.Fatalf("First request failed: %v", err) 3150 } 3151 reqDur := time.Since(befReq) 3152 returned := make(chan struct{}) 3153 go func() { 3154 second, err := m.Request(device1, "default", file, 0, 2000, 0, nil, 0, false) 3155 if err != nil { 3156 t.Errorf("Second request failed: %v", err) 3157 } 3158 close(returned) 3159 second.Close() 3160 }() 3161 time.Sleep(10 * reqDur) 3162 select { 3163 case <-returned: 3164 t.Fatalf("Second request returned before first was done") 3165 default: 3166 } 3167 first.Close() 3168 select { 3169 case <-returned: 3170 case <-time.After(time.Second): 3171 t.Fatalf("Second request did not return after first was done") 3172 } 3173} 3174 3175// TestConnCloseOnRestart checks that there is no deadlock when calling Close 3176// on a protocol connection that has a blocking reader (blocking writer can't 3177// be done as the test requires clusterconfigs to go through). 3178func TestConnCloseOnRestart(t *testing.T) { 3179 oldCloseTimeout := protocol.CloseTimeout 3180 protocol.CloseTimeout = 100 * time.Millisecond 3181 defer func() { 3182 protocol.CloseTimeout = oldCloseTimeout 3183 }() 3184 3185 w, fcfg, wCancel := tmpDefaultWrapper() 3186 defer wCancel() 3187 m := setupModel(t, w) 3188 defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) 3189 3190 br := &testutils.BlockingRW{} 3191 nw := &testutils.NoopRW{} 3192 m.AddConnection(protocol.NewConnection(device1, br, nw, testutils.NoopCloser{}, m, new(protocolmocks.ConnectionInfo), protocol.CompressionNever, nil), protocol.Hello{}) 3193 m.pmut.RLock() 3194 if len(m.closed) != 1 { 3195 t.Fatalf("Expected just one conn (len(m.conn) == %v)", len(m.conn)) 3196 } 3197 closed := m.closed[device1] 3198 m.pmut.RUnlock() 3199 3200 waiter, err := w.RemoveDevice(device1) 3201 if err != nil { 3202 t.Fatal(err) 3203 } 3204 done := make(chan struct{}) 3205 go func() { 3206 waiter.Wait() 3207 close(done) 3208 }() 3209 select { 3210 case <-done: 3211 case <-time.After(5 * time.Second): 3212 t.Fatal("Timed out before config took effect") 3213 } 3214 select { 3215 case <-closed: 3216 case <-time.After(5 * time.Second): 3217 t.Fatal("Timed out before connection was closed") 3218 } 3219} 3220 3221func TestModTimeWindow(t *testing.T) { 3222 w, fcfg, wCancel := tmpDefaultWrapper() 3223 defer wCancel() 3224 tfs := fcfg.Filesystem() 3225 fcfg.RawModTimeWindowS = 2 3226 setFolder(t, w, fcfg) 3227 m := setupModel(t, w) 3228 defer cleanupModelAndRemoveDir(m, tfs.URI()) 3229 3230 name := "foo" 3231 3232 fd, err := tfs.Create(name) 3233 must(t, err) 3234 stat, err := fd.Stat() 3235 must(t, err) 3236 modTime := stat.ModTime() 3237 fd.Close() 3238 3239 m.ScanFolders() 3240 3241 // Get current version 3242 3243 fi, ok := m.testCurrentFolderFile("default", name) 3244 if !ok { 3245 t.Fatal("File missing") 3246 } 3247 v := fi.Version 3248 3249 // Update time on disk 1s 3250 3251 err = tfs.Chtimes(name, time.Now(), modTime.Add(time.Second)) 3252 must(t, err) 3253 3254 m.ScanFolders() 3255 3256 // No change due to within window 3257 3258 fi, _ = m.testCurrentFolderFile("default", name) 3259 if !fi.Version.Equal(v) { 3260 t.Fatalf("Got version %v, expected %v", fi.Version, v) 3261 } 3262 3263 // Update to be outside window 3264 3265 err = tfs.Chtimes(name, time.Now(), modTime.Add(2*time.Second)) 3266 must(t, err) 3267 3268 m.ScanFolders() 3269 3270 // Version should have updated 3271 3272 fi, _ = m.testCurrentFolderFile("default", name) 3273 if fi.Version.Compare(v) != protocol.Greater { 3274 t.Fatalf("Got result %v, expected %v", fi.Version.Compare(v), protocol.Greater) 3275 } 3276} 3277 3278func TestDevicePause(t *testing.T) { 3279 m, _, fcfg, wcfgCancel := setupModelWithConnection(t) 3280 defer wcfgCancel() 3281 defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) 3282 3283 sub := m.evLogger.Subscribe(events.DevicePaused) 3284 defer sub.Unsubscribe() 3285 3286 m.pmut.RLock() 3287 closed := m.closed[device1] 3288 m.pmut.RUnlock() 3289 3290 pauseDevice(t, m.cfg, device1, true) 3291 3292 timeout := time.NewTimer(5 * time.Second) 3293 select { 3294 case <-sub.C(): 3295 select { 3296 case <-closed: 3297 case <-timeout.C: 3298 t.Fatal("Timed out before connection was closed") 3299 } 3300 case <-timeout.C: 3301 t.Fatal("Timed out before device was paused") 3302 } 3303} 3304 3305func TestDeviceWasSeen(t *testing.T) { 3306 m, _, fcfg, wcfgCancel := setupModelWithConnection(t) 3307 defer wcfgCancel() 3308 defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) 3309 3310 m.deviceWasSeen(device1) 3311 3312 stats, err := m.DeviceStatistics() 3313 if err != nil { 3314 t.Error("Unexpected error:", err) 3315 } 3316 entry := stats[device1] 3317 if time.Since(entry.LastSeen) > time.Second { 3318 t.Error("device should have been seen now") 3319 } 3320} 3321 3322func TestNewLimitedRequestResponse(t *testing.T) { 3323 l0 := util.NewSemaphore(0) 3324 l1 := util.NewSemaphore(1024) 3325 l2 := (*util.Semaphore)(nil) 3326 3327 // Should take 500 bytes from any non-unlimited non-nil limiters. 3328 res := newLimitedRequestResponse(500, l0, l1, l2) 3329 3330 if l1.Available() != 1024-500 { 3331 t.Error("should have taken bytes from limited limiter") 3332 } 3333 3334 // Closing the result should return the bytes. 3335 res.Close() 3336 3337 // Try to take 1024 bytes to make sure the bytes were returned. 3338 done := make(chan struct{}) 3339 go func() { 3340 l1.Take(1024) 3341 close(done) 3342 }() 3343 select { 3344 case <-done: 3345 case <-time.After(time.Second): 3346 t.Error("Bytes weren't returned in a timely fashion") 3347 } 3348} 3349 3350func TestSummaryPausedNoError(t *testing.T) { 3351 wcfg, fcfg, wcfgCancel := tmpDefaultWrapper() 3352 defer wcfgCancel() 3353 pauseFolder(t, wcfg, fcfg.ID, true) 3354 m := setupModel(t, wcfg) 3355 defer cleanupModel(m) 3356 3357 fss := NewFolderSummaryService(wcfg, m, myID, events.NoopLogger) 3358 if _, err := fss.Summary(fcfg.ID); err != nil { 3359 t.Error("Expected no error getting a summary for a paused folder:", err) 3360 } 3361} 3362 3363func TestFolderAPIErrors(t *testing.T) { 3364 wcfg, fcfg, wcfgCancel := tmpDefaultWrapper() 3365 defer wcfgCancel() 3366 pauseFolder(t, wcfg, fcfg.ID, true) 3367 m := setupModel(t, wcfg) 3368 defer cleanupModel(m) 3369 3370 methods := []func(folder string) error{ 3371 m.ScanFolder, 3372 func(folder string) error { 3373 return m.ScanFolderSubdirs(folder, nil) 3374 }, 3375 func(folder string) error { 3376 _, err := m.GetFolderVersions(folder) 3377 return err 3378 }, 3379 func(folder string) error { 3380 _, err := m.RestoreFolderVersions(folder, nil) 3381 return err 3382 }, 3383 } 3384 3385 for i, method := range methods { 3386 if err := method(fcfg.ID); err != ErrFolderPaused { 3387 t.Errorf(`Expected "%v", got "%v" (method no %v)`, ErrFolderPaused, err, i) 3388 } 3389 if err := method("notexisting"); err != ErrFolderMissing { 3390 t.Errorf(`Expected "%v", got "%v" (method no %v)`, ErrFolderMissing, err, i) 3391 } 3392 } 3393} 3394 3395func TestRenameSequenceOrder(t *testing.T) { 3396 wcfg, fcfg, wcfgCancel := tmpDefaultWrapper() 3397 defer wcfgCancel() 3398 m := setupModel(t, wcfg) 3399 defer cleanupModel(m) 3400 3401 numFiles := 20 3402 3403 ffs := fcfg.Filesystem() 3404 for i := 0; i < numFiles; i++ { 3405 v := fmt.Sprintf("%d", i) 3406 must(t, writeFile(ffs, v, []byte(v), 0644)) 3407 } 3408 3409 m.ScanFolders() 3410 3411 count := 0 3412 snap := dbSnapshot(t, m, "default") 3413 snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileIntf) bool { 3414 count++ 3415 return true 3416 }) 3417 snap.Release() 3418 3419 if count != numFiles { 3420 t.Errorf("Unexpected count: %d != %d", count, numFiles) 3421 } 3422 3423 // Modify all the files, other than the ones we expect to rename 3424 for i := 0; i < numFiles; i++ { 3425 if i == 3 || i == 17 || i == 16 || i == 4 { 3426 continue 3427 } 3428 v := fmt.Sprintf("%d", i) 3429 must(t, writeFile(ffs, v, []byte(v+"-new"), 0644)) 3430 } 3431 // Rename 3432 must(t, ffs.Rename("3", "17")) 3433 must(t, ffs.Rename("16", "4")) 3434 3435 // Scan 3436 m.ScanFolders() 3437 3438 // Verify sequence of a appearing is followed by c disappearing. 3439 snap = dbSnapshot(t, m, "default") 3440 defer snap.Release() 3441 3442 var firstExpectedSequence int64 3443 var secondExpectedSequence int64 3444 failed := false 3445 snap.WithHaveSequence(0, func(i protocol.FileIntf) bool { 3446 t.Log(i) 3447 if i.FileName() == "17" { 3448 firstExpectedSequence = i.SequenceNo() + 1 3449 } 3450 if i.FileName() == "4" { 3451 secondExpectedSequence = i.SequenceNo() + 1 3452 } 3453 if i.FileName() == "3" { 3454 failed = i.SequenceNo() != firstExpectedSequence || failed 3455 } 3456 if i.FileName() == "16" { 3457 failed = i.SequenceNo() != secondExpectedSequence || failed 3458 } 3459 return true 3460 }) 3461 if failed { 3462 t.Fail() 3463 } 3464} 3465 3466func TestRenameSameFile(t *testing.T) { 3467 wcfg, fcfg, wcfgCancel := tmpDefaultWrapper() 3468 defer wcfgCancel() 3469 m := setupModel(t, wcfg) 3470 defer cleanupModel(m) 3471 3472 ffs := fcfg.Filesystem() 3473 must(t, writeFile(ffs, "file", []byte("file"), 0644)) 3474 3475 m.ScanFolders() 3476 3477 count := 0 3478 snap := dbSnapshot(t, m, "default") 3479 snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileIntf) bool { 3480 count++ 3481 return true 3482 }) 3483 snap.Release() 3484 3485 if count != 1 { 3486 t.Errorf("Unexpected count: %d != %d", count, 1) 3487 } 3488 3489 must(t, ffs.Rename("file", "file1")) 3490 must(t, osutil.Copy(fs.CopyRangeMethodStandard, ffs, ffs, "file1", "file0")) 3491 must(t, osutil.Copy(fs.CopyRangeMethodStandard, ffs, ffs, "file1", "file2")) 3492 must(t, osutil.Copy(fs.CopyRangeMethodStandard, ffs, ffs, "file1", "file3")) 3493 must(t, osutil.Copy(fs.CopyRangeMethodStandard, ffs, ffs, "file1", "file4")) 3494 3495 m.ScanFolders() 3496 3497 snap = dbSnapshot(t, m, "default") 3498 defer snap.Release() 3499 3500 prevSeq := int64(0) 3501 seen := false 3502 snap.WithHaveSequence(0, func(i protocol.FileIntf) bool { 3503 if i.SequenceNo() <= prevSeq { 3504 t.Fatalf("non-increasing sequences: %d <= %d", i.SequenceNo(), prevSeq) 3505 } 3506 if i.FileName() == "file" { 3507 if seen { 3508 t.Fatal("already seen file") 3509 } 3510 seen = true 3511 } 3512 prevSeq = i.SequenceNo() 3513 return true 3514 }) 3515} 3516 3517func TestRenameEmptyFile(t *testing.T) { 3518 wcfg, fcfg, wcfgCancel := tmpDefaultWrapper() 3519 defer wcfgCancel() 3520 m := setupModel(t, wcfg) 3521 defer cleanupModel(m) 3522 3523 ffs := fcfg.Filesystem() 3524 3525 must(t, writeFile(ffs, "file", []byte("data"), 0644)) 3526 must(t, writeFile(ffs, "empty", nil, 0644)) 3527 3528 m.ScanFolders() 3529 3530 snap := dbSnapshot(t, m, "default") 3531 defer snap.Release() 3532 empty, eok := snap.Get(protocol.LocalDeviceID, "empty") 3533 if !eok { 3534 t.Fatal("failed to find empty file") 3535 } 3536 file, fok := snap.Get(protocol.LocalDeviceID, "file") 3537 if !fok { 3538 t.Fatal("failed to find non-empty file") 3539 } 3540 3541 count := 0 3542 snap.WithBlocksHash(empty.BlocksHash, func(_ protocol.FileIntf) bool { 3543 count++ 3544 return true 3545 }) 3546 3547 if count != 0 { 3548 t.Fatalf("Found %d entries for empty file, expected 0", count) 3549 } 3550 3551 count = 0 3552 snap.WithBlocksHash(file.BlocksHash, func(_ protocol.FileIntf) bool { 3553 count++ 3554 return true 3555 }) 3556 3557 if count != 1 { 3558 t.Fatalf("Found %d entries for non-empty file, expected 1", count) 3559 } 3560 3561 must(t, ffs.Rename("file", "new-file")) 3562 must(t, ffs.Rename("empty", "new-empty")) 3563 3564 // Scan 3565 m.ScanFolders() 3566 3567 snap = dbSnapshot(t, m, "default") 3568 defer snap.Release() 3569 3570 count = 0 3571 snap.WithBlocksHash(empty.BlocksHash, func(_ protocol.FileIntf) bool { 3572 count++ 3573 return true 3574 }) 3575 3576 if count != 0 { 3577 t.Fatalf("Found %d entries for empty file, expected 0", count) 3578 } 3579 3580 count = 0 3581 snap.WithBlocksHash(file.BlocksHash, func(i protocol.FileIntf) bool { 3582 count++ 3583 if i.FileName() != "new-file" { 3584 t.Fatalf("unexpected file name %s, expected new-file", i.FileName()) 3585 } 3586 return true 3587 }) 3588 3589 if count != 1 { 3590 t.Fatalf("Found %d entries for non-empty file, expected 1", count) 3591 } 3592} 3593 3594func TestBlockListMap(t *testing.T) { 3595 wcfg, fcfg, wcfgCancel := tmpDefaultWrapper() 3596 defer wcfgCancel() 3597 m := setupModel(t, wcfg) 3598 defer cleanupModel(m) 3599 3600 ffs := fcfg.Filesystem() 3601 must(t, writeFile(ffs, "one", []byte("content"), 0644)) 3602 must(t, writeFile(ffs, "two", []byte("content"), 0644)) 3603 must(t, writeFile(ffs, "three", []byte("content"), 0644)) 3604 must(t, writeFile(ffs, "four", []byte("content"), 0644)) 3605 must(t, writeFile(ffs, "five", []byte("content"), 0644)) 3606 3607 m.ScanFolders() 3608 3609 snap := dbSnapshot(t, m, "default") 3610 defer snap.Release() 3611 fi, ok := snap.Get(protocol.LocalDeviceID, "one") 3612 if !ok { 3613 t.Error("failed to find existing file") 3614 } 3615 var paths []string 3616 3617 snap.WithBlocksHash(fi.BlocksHash, func(fi protocol.FileIntf) bool { 3618 paths = append(paths, fi.FileName()) 3619 return true 3620 }) 3621 snap.Release() 3622 3623 expected := []string{"one", "two", "three", "four", "five"} 3624 if !equalStringsInAnyOrder(paths, expected) { 3625 t.Errorf("expected %q got %q", expected, paths) 3626 } 3627 3628 // Fudge the files around 3629 // Remove 3630 must(t, ffs.Remove("one")) 3631 3632 // Modify 3633 must(t, ffs.Remove("two")) 3634 must(t, writeFile(ffs, "two", []byte("mew-content"), 0644)) 3635 3636 // Rename 3637 must(t, ffs.Rename("three", "new-three")) 3638 3639 // Change type 3640 must(t, ffs.Remove("four")) 3641 must(t, ffs.Mkdir("four", 0644)) 3642 3643 m.ScanFolders() 3644 3645 // Check we're left with 2 of the 5 3646 snap = dbSnapshot(t, m, "default") 3647 defer snap.Release() 3648 3649 paths = paths[:0] 3650 snap.WithBlocksHash(fi.BlocksHash, func(fi protocol.FileIntf) bool { 3651 paths = append(paths, fi.FileName()) 3652 return true 3653 }) 3654 snap.Release() 3655 3656 expected = []string{"new-three", "five"} 3657 if !equalStringsInAnyOrder(paths, expected) { 3658 t.Errorf("expected %q got %q", expected, paths) 3659 } 3660} 3661 3662func TestScanRenameCaseOnly(t *testing.T) { 3663 wcfg, fcfg, wcfgCancel := tmpDefaultWrapper() 3664 defer wcfgCancel() 3665 m := setupModel(t, wcfg) 3666 defer cleanupModel(m) 3667 3668 ffs := fcfg.Filesystem() 3669 name := "foo" 3670 must(t, writeFile(ffs, name, []byte("contents"), 0644)) 3671 3672 m.ScanFolders() 3673 3674 snap := dbSnapshot(t, m, fcfg.ID) 3675 defer snap.Release() 3676 found := false 3677 snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileIntf) bool { 3678 if found { 3679 t.Fatal("got more than one file") 3680 } 3681 if i.FileName() != name { 3682 t.Fatalf("got file %v, expected %v", i.FileName(), name) 3683 } 3684 found = true 3685 return true 3686 }) 3687 snap.Release() 3688 3689 upper := strings.ToUpper(name) 3690 must(t, ffs.Rename(name, upper)) 3691 m.ScanFolders() 3692 3693 snap = dbSnapshot(t, m, fcfg.ID) 3694 defer snap.Release() 3695 found = false 3696 snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileIntf) bool { 3697 if i.FileName() == name { 3698 if i.IsDeleted() { 3699 return true 3700 } 3701 t.Fatal("renamed file not deleted") 3702 } 3703 if i.FileName() != upper { 3704 t.Fatalf("got file %v, expected %v", i.FileName(), upper) 3705 } 3706 if found { 3707 t.Fatal("got more than the expected files") 3708 } 3709 found = true 3710 return true 3711 }) 3712} 3713 3714func TestClusterConfigOnFolderAdd(t *testing.T) { 3715 testConfigChangeTriggersClusterConfigs(t, false, true, nil, func(wrapper config.Wrapper) { 3716 fcfg := testFolderConfigTmp() 3717 fcfg.ID = "second" 3718 fcfg.Label = "second" 3719 fcfg.Devices = []config.FolderDeviceConfiguration{{ 3720 DeviceID: device2, 3721 IntroducedBy: protocol.EmptyDeviceID, 3722 }} 3723 setFolder(t, wrapper, fcfg) 3724 }) 3725} 3726 3727func TestClusterConfigOnFolderShare(t *testing.T) { 3728 testConfigChangeTriggersClusterConfigs(t, true, true, nil, func(cfg config.Wrapper) { 3729 fcfg := cfg.FolderList()[0] 3730 fcfg.Devices = []config.FolderDeviceConfiguration{{ 3731 DeviceID: device2, 3732 IntroducedBy: protocol.EmptyDeviceID, 3733 }} 3734 setFolder(t, cfg, fcfg) 3735 }) 3736} 3737 3738func TestClusterConfigOnFolderUnshare(t *testing.T) { 3739 testConfigChangeTriggersClusterConfigs(t, true, false, nil, func(cfg config.Wrapper) { 3740 fcfg := cfg.FolderList()[0] 3741 fcfg.Devices = nil 3742 setFolder(t, cfg, fcfg) 3743 }) 3744} 3745 3746func TestClusterConfigOnFolderRemove(t *testing.T) { 3747 testConfigChangeTriggersClusterConfigs(t, true, false, nil, func(cfg config.Wrapper) { 3748 rcfg := cfg.RawCopy() 3749 rcfg.Folders = nil 3750 replace(t, cfg, rcfg) 3751 }) 3752} 3753 3754func TestClusterConfigOnFolderPause(t *testing.T) { 3755 testConfigChangeTriggersClusterConfigs(t, true, false, nil, func(cfg config.Wrapper) { 3756 pauseFolder(t, cfg, cfg.FolderList()[0].ID, true) 3757 }) 3758} 3759 3760func TestClusterConfigOnFolderUnpause(t *testing.T) { 3761 testConfigChangeTriggersClusterConfigs(t, true, false, func(cfg config.Wrapper) { 3762 pauseFolder(t, cfg, cfg.FolderList()[0].ID, true) 3763 }, func(cfg config.Wrapper) { 3764 pauseFolder(t, cfg, cfg.FolderList()[0].ID, false) 3765 }) 3766} 3767 3768func TestAddFolderCompletion(t *testing.T) { 3769 // Empty folders are always 100% complete. 3770 comp := newFolderCompletion(db.Counts{}, db.Counts{}, 0) 3771 comp.add(newFolderCompletion(db.Counts{}, db.Counts{}, 0)) 3772 if comp.CompletionPct != 100 { 3773 t.Error(comp.CompletionPct) 3774 } 3775 3776 // Completion is of the whole 3777 comp = newFolderCompletion(db.Counts{Bytes: 100}, db.Counts{}, 0) // 100% complete 3778 comp.add(newFolderCompletion(db.Counts{Bytes: 400}, db.Counts{Bytes: 50}, 0)) // 82.5% complete 3779 if comp.CompletionPct != 90 { // 100 * (1 - 50/500) 3780 t.Error(comp.CompletionPct) 3781 } 3782} 3783 3784func TestScanDeletedROChangedOnSR(t *testing.T) { 3785 m, _, fcfg, wCancel := setupModelWithConnection(t) 3786 ffs := fcfg.Filesystem() 3787 defer wCancel() 3788 defer cleanupModelAndRemoveDir(m, ffs.URI()) 3789 fcfg.Type = config.FolderTypeReceiveOnly 3790 setFolder(t, m.cfg, fcfg) 3791 3792 name := "foo" 3793 3794 must(t, writeFile(ffs, name, []byte(name), 0644)) 3795 m.ScanFolders() 3796 3797 file, ok := m.testCurrentFolderFile(fcfg.ID, name) 3798 if !ok { 3799 t.Fatal("file missing in db") 3800 } 3801 // A remote must have the file, otherwise the deletion below is 3802 // automatically resolved as not a ro-changed item. 3803 must(t, m.IndexUpdate(device1, fcfg.ID, []protocol.FileInfo{file})) 3804 3805 must(t, ffs.Remove(name)) 3806 m.ScanFolders() 3807 3808 if receiveOnlyChangedSize(t, m, fcfg.ID).Deleted != 1 { 3809 t.Fatal("expected one receive only changed deleted item") 3810 } 3811 3812 fcfg.Type = config.FolderTypeSendReceive 3813 setFolder(t, m.cfg, fcfg) 3814 m.ScanFolders() 3815 3816 if receiveOnlyChangedSize(t, m, fcfg.ID).Deleted != 0 { 3817 t.Fatal("expected no receive only changed deleted item") 3818 } 3819 if localSize(t, m, fcfg.ID).Deleted != 1 { 3820 t.Fatal("expected one local deleted item") 3821 } 3822} 3823 3824func testConfigChangeTriggersClusterConfigs(t *testing.T, expectFirst, expectSecond bool, pre func(config.Wrapper), fn func(config.Wrapper)) { 3825 t.Helper() 3826 wcfg, _, wcfgCancel := tmpDefaultWrapper() 3827 defer wcfgCancel() 3828 m := setupModel(t, wcfg) 3829 defer cleanupModel(m) 3830 3831 setDevice(t, wcfg, newDeviceConfiguration(wcfg.DefaultDevice(), device2, "device2")) 3832 3833 if pre != nil { 3834 pre(wcfg) 3835 } 3836 3837 cc1 := make(chan struct{}, 1) 3838 cc2 := make(chan struct{}, 1) 3839 fc1 := newFakeConnection(device1, m) 3840 fc1.ClusterConfigCalls(func(_ protocol.ClusterConfig) { 3841 cc1 <- struct{}{} 3842 }) 3843 fc2 := newFakeConnection(device2, m) 3844 fc2.ClusterConfigCalls(func(_ protocol.ClusterConfig) { 3845 cc2 <- struct{}{} 3846 }) 3847 m.AddConnection(fc1, protocol.Hello{}) 3848 m.AddConnection(fc2, protocol.Hello{}) 3849 3850 // Initial CCs 3851 select { 3852 case <-cc1: 3853 default: 3854 t.Fatal("missing initial CC from device1") 3855 } 3856 select { 3857 case <-cc2: 3858 default: 3859 t.Fatal("missing initial CC from device2") 3860 } 3861 3862 t.Log("Applying config change") 3863 3864 fn(wcfg) 3865 3866 timeout := time.NewTimer(time.Second) 3867 if expectFirst { 3868 select { 3869 case <-cc1: 3870 case <-timeout.C: 3871 t.Errorf("timed out before receiving cluste rconfig for first device") 3872 } 3873 } 3874 if expectSecond { 3875 select { 3876 case <-cc2: 3877 case <-timeout.C: 3878 t.Errorf("timed out before receiving cluste rconfig for second device") 3879 } 3880 } 3881} 3882 3883// The end result of the tested scenario is that the global version entry has an 3884// empty version vector and is not deleted, while everything is actually deleted. 3885// That then causes these files to be considered as needed, while they are not. 3886// https://github.com/syncthing/syncthing/issues/6961 3887func TestIssue6961(t *testing.T) { 3888 wcfg, fcfg, wcfgCancel := tmpDefaultWrapper() 3889 defer wcfgCancel() 3890 tfs := fcfg.Filesystem() 3891 waiter, err := wcfg.Modify(func(cfg *config.Configuration) { 3892 cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device2, "device2")) 3893 fcfg.Type = config.FolderTypeReceiveOnly 3894 fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{DeviceID: device2}) 3895 cfg.SetFolder(fcfg) 3896 }) 3897 must(t, err) 3898 waiter.Wait() 3899 // Always recalc/repair when opening a fileset. 3900 m := newModel(t, wcfg, myID, "syncthing", "dev", nil) 3901 m.db.Close() 3902 m.db, err = db.NewLowlevel(backend.OpenMemory(), m.evLogger, db.WithRecheckInterval(time.Millisecond)) 3903 if err != nil { 3904 t.Fatal(err) 3905 } 3906 m.ServeBackground() 3907 defer cleanupModelAndRemoveDir(m, tfs.URI()) 3908 addFakeConn(m, device1, fcfg.ID) 3909 addFakeConn(m, device2, fcfg.ID) 3910 m.ScanFolders() 3911 3912 name := "foo" 3913 version := protocol.Vector{}.Update(device1.Short()) 3914 3915 // Remote, valid and existing file 3916 must(t, m.Index(device1, fcfg.ID, []protocol.FileInfo{{Name: name, Version: version, Sequence: 1}})) 3917 // Remote, invalid (receive-only) and existing file 3918 must(t, m.Index(device2, fcfg.ID, []protocol.FileInfo{{Name: name, RawInvalid: true, Sequence: 1}})) 3919 // Create a local file 3920 if fd, err := tfs.OpenFile(name, fs.OptCreate, 0666); err != nil { 3921 t.Fatal(err) 3922 } else { 3923 fd.Close() 3924 } 3925 if info, err := tfs.Lstat(name); err != nil { 3926 t.Fatal(err) 3927 } else { 3928 l.Infoln("intest", info.Mode) 3929 } 3930 m.ScanFolders() 3931 3932 // Get rid of valid global 3933 waiter, err = wcfg.RemoveDevice(device1) 3934 if err != nil { 3935 t.Fatal(err) 3936 } 3937 waiter.Wait() 3938 3939 // Delete the local file 3940 must(t, tfs.Remove(name)) 3941 m.ScanFolders() 3942 3943 // Drop ther remote index, add some other file. 3944 must(t, m.Index(device2, fcfg.ID, []protocol.FileInfo{{Name: "bar", RawInvalid: true, Sequence: 1}})) 3945 3946 // Pause and unpause folder to create new db.FileSet and thus recalculate everything 3947 pauseFolder(t, wcfg, fcfg.ID, true) 3948 pauseFolder(t, wcfg, fcfg.ID, false) 3949 3950 if comp := m.testCompletion(device2, fcfg.ID); comp.NeedDeletes != 0 { 3951 t.Error("Expected 0 needed deletes, got", comp.NeedDeletes) 3952 } else { 3953 t.Log(comp) 3954 } 3955} 3956 3957func TestCompletionEmptyGlobal(t *testing.T) { 3958 m, _, fcfg, wcfgCancel := setupModelWithConnection(t) 3959 defer wcfgCancel() 3960 defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI()) 3961 files := []protocol.FileInfo{{Name: "foo", Version: protocol.Vector{}.Update(myID.Short()), Sequence: 1}} 3962 m.fmut.Lock() 3963 m.folderFiles[fcfg.ID].Update(protocol.LocalDeviceID, files) 3964 m.fmut.Unlock() 3965 files[0].Deleted = true 3966 files[0].Version = files[0].Version.Update(device1.Short()) 3967 must(t, m.IndexUpdate(device1, fcfg.ID, files)) 3968 comp := m.testCompletion(protocol.LocalDeviceID, fcfg.ID) 3969 if comp.CompletionPct != 95 { 3970 t.Error("Expected completion of 95%, got", comp.CompletionPct) 3971 } 3972} 3973 3974func TestNeedMetaAfterIndexReset(t *testing.T) { 3975 w, fcfg, wCancel := tmpDefaultWrapper() 3976 defer wCancel() 3977 addDevice2(t, w, fcfg) 3978 m := setupModel(t, w) 3979 defer cleanupModelAndRemoveDir(m, fcfg.Path) 3980 addFakeConn(m, device1, fcfg.ID) 3981 addFakeConn(m, device2, fcfg.ID) 3982 3983 var seq int64 = 1 3984 files := []protocol.FileInfo{{Name: "foo", Size: 10, Version: protocol.Vector{}.Update(device1.Short()), Sequence: seq}} 3985 3986 // Start with two remotes having one file, then both deleting it, then 3987 // only one adding it again. 3988 must(t, m.Index(device1, fcfg.ID, files)) 3989 must(t, m.Index(device2, fcfg.ID, files)) 3990 seq++ 3991 files[0].SetDeleted(device2.Short()) 3992 files[0].Sequence = seq 3993 must(t, m.IndexUpdate(device2, fcfg.ID, files)) 3994 must(t, m.IndexUpdate(device1, fcfg.ID, files)) 3995 seq++ 3996 files[0].Deleted = false 3997 files[0].Size = 20 3998 files[0].Version = files[0].Version.Update(device1.Short()) 3999 files[0].Sequence = seq 4000 must(t, m.IndexUpdate(device1, fcfg.ID, files)) 4001 4002 if comp := m.testCompletion(device2, fcfg.ID); comp.NeedItems != 1 { 4003 t.Error("Expected one needed item for device2, got", comp.NeedItems) 4004 } 4005 4006 // Pretend we had an index reset on device 1 4007 must(t, m.Index(device1, fcfg.ID, files)) 4008 if comp := m.testCompletion(device2, fcfg.ID); comp.NeedItems != 1 { 4009 t.Error("Expected one needed item for device2, got", comp.NeedItems) 4010 } 4011} 4012 4013func TestCcCheckEncryption(t *testing.T) { 4014 if testing.Short() { 4015 t.Skip("skipping on short testing - generating encryption tokens is slow") 4016 } 4017 4018 w, fcfg, wCancel := tmpDefaultWrapper() 4019 defer wCancel() 4020 m := setupModel(t, w) 4021 m.cancel() 4022 defer cleanupModel(m) 4023 4024 pw := "foo" 4025 token := protocol.PasswordToken(fcfg.ID, pw) 4026 m.folderEncryptionPasswordTokens[fcfg.ID] = token 4027 4028 testCases := []struct { 4029 tokenRemote, tokenLocal []byte 4030 isEncryptedRemote, isEncryptedLocal bool 4031 expectedErr error 4032 }{ 4033 { 4034 tokenRemote: token, 4035 tokenLocal: token, 4036 expectedErr: errEncryptionInvConfigRemote, 4037 }, 4038 { 4039 isEncryptedRemote: true, 4040 isEncryptedLocal: true, 4041 expectedErr: errEncryptionInvConfigLocal, 4042 }, 4043 { 4044 tokenRemote: token, 4045 tokenLocal: nil, 4046 isEncryptedRemote: false, 4047 isEncryptedLocal: false, 4048 expectedErr: errEncryptionNotEncryptedLocal, 4049 }, 4050 { 4051 tokenRemote: token, 4052 tokenLocal: nil, 4053 isEncryptedRemote: true, 4054 isEncryptedLocal: false, 4055 expectedErr: nil, 4056 }, 4057 { 4058 tokenRemote: token, 4059 tokenLocal: nil, 4060 isEncryptedRemote: false, 4061 isEncryptedLocal: true, 4062 expectedErr: nil, 4063 }, 4064 { 4065 tokenRemote: nil, 4066 tokenLocal: token, 4067 isEncryptedRemote: true, 4068 isEncryptedLocal: false, 4069 expectedErr: nil, 4070 }, 4071 { 4072 tokenRemote: nil, 4073 tokenLocal: token, 4074 isEncryptedRemote: false, 4075 isEncryptedLocal: true, 4076 expectedErr: nil, 4077 }, 4078 { 4079 tokenRemote: nil, 4080 tokenLocal: token, 4081 isEncryptedRemote: false, 4082 isEncryptedLocal: false, 4083 expectedErr: errEncryptionNotEncryptedLocal, 4084 }, 4085 { 4086 tokenRemote: nil, 4087 tokenLocal: nil, 4088 isEncryptedRemote: true, 4089 isEncryptedLocal: false, 4090 expectedErr: errEncryptionPlainForRemoteEncrypted, 4091 }, 4092 { 4093 tokenRemote: nil, 4094 tokenLocal: nil, 4095 isEncryptedRemote: false, 4096 isEncryptedLocal: true, 4097 expectedErr: errEncryptionPlainForReceiveEncrypted, 4098 }, 4099 { 4100 tokenRemote: nil, 4101 tokenLocal: nil, 4102 isEncryptedRemote: false, 4103 isEncryptedLocal: false, 4104 expectedErr: nil, 4105 }, 4106 } 4107 4108 for i, tc := range testCases { 4109 tfcfg := fcfg.Copy() 4110 if tc.isEncryptedLocal { 4111 tfcfg.Type = config.FolderTypeReceiveEncrypted 4112 m.folderEncryptionPasswordTokens[fcfg.ID] = token 4113 } 4114 dcfg := config.FolderDeviceConfiguration{DeviceID: device1} 4115 if tc.isEncryptedRemote { 4116 dcfg.EncryptionPassword = pw 4117 } 4118 4119 deviceInfos := &clusterConfigDeviceInfo{ 4120 remote: protocol.Device{ID: device1, EncryptionPasswordToken: tc.tokenRemote}, 4121 local: protocol.Device{ID: myID, EncryptionPasswordToken: tc.tokenLocal}, 4122 } 4123 err := m.ccCheckEncryption(tfcfg, dcfg, deviceInfos, false) 4124 if err != tc.expectedErr { 4125 t.Errorf("Testcase %v: Expected error %v, got %v", i, tc.expectedErr, err) 4126 } 4127 4128 if tc.expectedErr == nil { 4129 err := m.ccCheckEncryption(tfcfg, dcfg, deviceInfos, true) 4130 if tc.isEncryptedRemote || tc.isEncryptedLocal { 4131 if err != nil { 4132 t.Errorf("Testcase %v: Expected no error, got %v", i, err) 4133 } 4134 } else { 4135 if err != errEncryptionNotEncryptedUntrusted { 4136 t.Errorf("Testcase %v: Expected error %v, got %v", i, errEncryptionNotEncryptedUntrusted, err) 4137 } 4138 } 4139 } 4140 4141 if err != nil || (!tc.isEncryptedRemote && !tc.isEncryptedLocal) { 4142 continue 4143 } 4144 4145 if tc.isEncryptedLocal { 4146 m.folderEncryptionPasswordTokens[fcfg.ID] = []byte("notAMatch") 4147 } else { 4148 dcfg.EncryptionPassword = "notAMatch" 4149 } 4150 err = m.ccCheckEncryption(tfcfg, dcfg, deviceInfos, false) 4151 if err != errEncryptionPassword { 4152 t.Errorf("Testcase %v: Expected error %v, got %v", i, errEncryptionPassword, err) 4153 } 4154 } 4155} 4156 4157func TestCCFolderNotRunning(t *testing.T) { 4158 // Create the folder, but don't start it. 4159 w, fcfg, wCancel := tmpDefaultWrapper() 4160 defer wCancel() 4161 tfs := fcfg.Filesystem() 4162 m := newModel(t, w, myID, "syncthing", "dev", nil) 4163 defer cleanupModelAndRemoveDir(m, tfs.URI()) 4164 4165 // A connection can happen before all the folders are started. 4166 cc, _ := m.generateClusterConfig(device1) 4167 if l := len(cc.Folders); l != 1 { 4168 t.Fatalf("Expected 1 folder in CC, got %v", l) 4169 } 4170 folder := cc.Folders[0] 4171 if id := folder.ID; id != fcfg.ID { 4172 t.Fatalf("Expected folder %v, got %v", fcfg.ID, id) 4173 } 4174 if l := len(folder.Devices); l != 2 { 4175 t.Fatalf("Expected 2 devices in CC, got %v", l) 4176 } 4177 local := folder.Devices[1] 4178 if local.ID != myID { 4179 local = folder.Devices[0] 4180 } 4181 if !folder.Paused && local.IndexID == 0 { 4182 t.Errorf("Folder isn't paused, but index-id is zero") 4183 } 4184} 4185 4186func TestPendingFolder(t *testing.T) { 4187 w, _, wCancel := tmpDefaultWrapper() 4188 defer wCancel() 4189 m := setupModel(t, w) 4190 defer cleanupModel(m) 4191 4192 setDevice(t, w, config.DeviceConfiguration{DeviceID: device2}) 4193 pfolder := "default" 4194 of := db.ObservedFolder{ 4195 Time: time.Now().Truncate(time.Second), 4196 Label: pfolder, 4197 } 4198 if err := m.db.AddOrUpdatePendingFolder(pfolder, of, device2); err != nil { 4199 t.Fatal(err) 4200 } 4201 deviceFolders, err := m.PendingFolders(protocol.EmptyDeviceID) 4202 if err != nil { 4203 t.Fatal(err) 4204 } else if pf, ok := deviceFolders[pfolder]; !ok { 4205 t.Errorf("folder %v not pending", pfolder) 4206 } else if _, ok := pf.OfferedBy[device2]; !ok { 4207 t.Errorf("folder %v not pending for device %v", pfolder, device2) 4208 } else if len(pf.OfferedBy) > 1 { 4209 t.Errorf("folder %v pending for too many devices %v", pfolder, pf.OfferedBy) 4210 } 4211 4212 device3, err := protocol.DeviceIDFromString("AIBAEAQ-CAIBAEC-AQCAIBA-EAQCAIA-BAEAQCA-IBAEAQC-CAIBAEA-QCAIBA7") 4213 if err != nil { 4214 t.Fatal(err) 4215 } 4216 setDevice(t, w, config.DeviceConfiguration{DeviceID: device3}) 4217 if err := m.db.AddOrUpdatePendingFolder(pfolder, of, device3); err != nil { 4218 t.Fatal(err) 4219 } 4220 deviceFolders, err = m.PendingFolders(device2) 4221 if err != nil { 4222 t.Fatal(err) 4223 } else if pf, ok := deviceFolders[pfolder]; !ok { 4224 t.Errorf("folder %v not pending when filtered", pfolder) 4225 } else if _, ok := pf.OfferedBy[device2]; !ok { 4226 t.Errorf("folder %v not pending for device %v when filtered", pfolder, device2) 4227 } else if _, ok := pf.OfferedBy[device3]; ok { 4228 t.Errorf("folder %v pending for device %v, but not filtered out", pfolder, device3) 4229 } 4230 4231 waiter, err := w.RemoveDevice(device3) 4232 if err != nil { 4233 t.Fatal(err) 4234 } 4235 waiter.Wait() 4236 deviceFolders, err = m.PendingFolders(protocol.EmptyDeviceID) 4237 if err != nil { 4238 t.Fatal(err) 4239 } else if pf, ok := deviceFolders[pfolder]; !ok { 4240 t.Errorf("folder %v not pending", pfolder) 4241 } else if _, ok := pf.OfferedBy[device3]; ok { 4242 t.Errorf("folder %v pending for removed device %v", pfolder, device3) 4243 } 4244 4245 waiter, err = w.RemoveFolder(pfolder) 4246 if err != nil { 4247 t.Fatal(err) 4248 } 4249 waiter.Wait() 4250 deviceFolders, err = m.PendingFolders(protocol.EmptyDeviceID) 4251 if err != nil { 4252 t.Fatal(err) 4253 } else if _, ok := deviceFolders[pfolder]; ok { 4254 t.Errorf("folder %v still pending after local removal", pfolder) 4255 } 4256} 4257 4258func equalStringsInAnyOrder(a, b []string) bool { 4259 if len(a) != len(b) { 4260 return false 4261 } 4262 sort.Strings(a) 4263 sort.Strings(b) 4264 for i := range a { 4265 if a[i] != b[i] { 4266 return false 4267 } 4268 } 4269 return true 4270} 4271