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