| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532 | 
							- // Copyright (C) 2016 The Syncthing Authors.
 
- //
 
- // This Source Code Form is subject to the terms of the Mozilla Public
 
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
 
- // You can obtain one at https://mozilla.org/MPL/2.0/.
 
- package model
 
- import (
 
- 	"bytes"
 
- 	"context"
 
- 	"errors"
 
- 	"os"
 
- 	"path/filepath"
 
- 	"runtime"
 
- 	"strconv"
 
- 	"strings"
 
- 	"sync"
 
- 	"testing"
 
- 	"time"
 
- 	"github.com/syncthing/syncthing/lib/config"
 
- 	"github.com/syncthing/syncthing/lib/events"
 
- 	"github.com/syncthing/syncthing/lib/fs"
 
- 	"github.com/syncthing/syncthing/lib/protocol"
 
- 	"github.com/syncthing/syncthing/lib/rand"
 
- )
 
- func TestRequestSimple(t *testing.T) {
 
- 	// Verify that the model performs a request and creates a file based on
 
- 	// an incoming index update.
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	tfs := fcfg.Filesystem(nil)
 
- 	defer cleanupModelAndRemoveDir(m, tfs.URI())
 
- 	// We listen for incoming index updates and trigger when we see one for
 
- 	// the expected test file.
 
- 	done := make(chan struct{})
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		select {
 
- 		case <-done:
 
- 			t.Error("More than one index update sent")
 
- 		default:
 
- 		}
 
- 		for _, f := range fs {
 
- 			if f.Name == "testfile" {
 
- 				close(done)
 
- 				return nil
 
- 			}
 
- 		}
 
- 		return nil
 
- 	})
 
- 	// Send an update for the test file, wait for it to sync and be reported back.
 
- 	contents := []byte("test file contents\n")
 
- 	fc.addFile("testfile", 0644, protocol.FileInfoTypeFile, contents)
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case <-done:
 
- 	case <-time.After(10 * time.Second):
 
- 		t.Fatal("timed out")
 
- 	}
 
- 	// Verify the contents
 
- 	if err := equalContents(filepath.Join(tfs.URI(), "testfile"), contents); err != nil {
 
- 		t.Error("File did not sync correctly:", err)
 
- 	}
 
- }
 
- func TestSymlinkTraversalRead(t *testing.T) {
 
- 	// Verify that a symlink can not be traversed for reading.
 
- 	if runtime.GOOS == "windows" {
 
- 		t.Skip("no symlink support on CI")
 
- 		return
 
- 	}
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
 
- 	// We listen for incoming index updates and trigger when we see one for
 
- 	// the expected test file.
 
- 	done := make(chan struct{})
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		select {
 
- 		case <-done:
 
- 			t.Error("More than one index update sent")
 
- 		default:
 
- 		}
 
- 		for _, f := range fs {
 
- 			if f.Name == "symlink" {
 
- 				close(done)
 
- 				return nil
 
- 			}
 
- 		}
 
- 		return nil
 
- 	})
 
- 	// Send an update for the symlink, wait for it to sync and be reported back.
 
- 	contents := []byte("..")
 
- 	fc.addFile("symlink", 0644, protocol.FileInfoTypeSymlink, contents)
 
- 	fc.sendIndexUpdate()
 
- 	<-done
 
- 	// Request a file by traversing the symlink
 
- 	res, err := m.Request(device1, "default", "symlink/requests_test.go", 0, 10, 0, nil, 0, false)
 
- 	if err == nil || res != nil {
 
- 		t.Error("Managed to traverse symlink")
 
- 	}
 
- }
 
- func TestSymlinkTraversalWrite(t *testing.T) {
 
- 	// Verify that a symlink can not be traversed for writing.
 
- 	if runtime.GOOS == "windows" {
 
- 		t.Skip("no symlink support on CI")
 
- 		return
 
- 	}
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
 
- 	// We listen for incoming index updates and trigger when we see one for
 
- 	// the expected names.
 
- 	done := make(chan struct{}, 1)
 
- 	badReq := make(chan string, 1)
 
- 	badIdx := make(chan string, 1)
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		for _, f := range fs {
 
- 			if f.Name == "symlink" {
 
- 				done <- struct{}{}
 
- 				return nil
 
- 			}
 
- 			if strings.HasPrefix(f.Name, "symlink") {
 
- 				badIdx <- f.Name
 
- 				return nil
 
- 			}
 
- 		}
 
- 		return nil
 
- 	})
 
- 	fc.RequestCalls(func(ctx context.Context, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
 
- 		if name != "symlink" && strings.HasPrefix(name, "symlink") {
 
- 			badReq <- name
 
- 		}
 
- 		return fc.fileData[name], nil
 
- 	})
 
- 	// Send an update for the symlink, wait for it to sync and be reported back.
 
- 	contents := []byte("..")
 
- 	fc.addFile("symlink", 0644, protocol.FileInfoTypeSymlink, contents)
 
- 	fc.sendIndexUpdate()
 
- 	<-done
 
- 	// Send an update for things behind the symlink, wait for requests for
 
- 	// blocks for any of them to come back, or index entries. Hopefully none
 
- 	// of that should happen.
 
- 	contents = []byte("testdata testdata\n")
 
- 	fc.addFile("symlink/testfile", 0644, protocol.FileInfoTypeFile, contents)
 
- 	fc.addFile("symlink/testdir", 0644, protocol.FileInfoTypeDirectory, contents)
 
- 	fc.addFile("symlink/testsyml", 0644, protocol.FileInfoTypeSymlink, contents)
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case name := <-badReq:
 
- 		t.Fatal("Should not have requested the data for", name)
 
- 	case name := <-badIdx:
 
- 		t.Fatal("Should not have sent the index entry for", name)
 
- 	case <-time.After(3 * time.Second):
 
- 		// Unfortunately not much else to trigger on here. The puller sleep
 
- 		// interval is 1s so if we didn't get any requests within two
 
- 		// iterations we should be fine.
 
- 	}
 
- }
 
- func TestRequestCreateTmpSymlink(t *testing.T) {
 
- 	// Test that an update for a temporary file is invalidated
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
 
- 	// We listen for incoming index updates and trigger when we see one for
 
- 	// the expected test file.
 
- 	goodIdx := make(chan struct{})
 
- 	name := fs.TempName("testlink")
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		for _, f := range fs {
 
- 			if f.Name == name {
 
- 				if f.IsInvalid() {
 
- 					goodIdx <- struct{}{}
 
- 				} else {
 
- 					t.Error("Received index with non-invalid temporary file")
 
- 					close(goodIdx)
 
- 				}
 
- 				return nil
 
- 			}
 
- 		}
 
- 		return nil
 
- 	})
 
- 	// Send an update for the test file, wait for it to sync and be reported back.
 
- 	fc.addFile(name, 0644, protocol.FileInfoTypeSymlink, []byte(".."))
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case <-goodIdx:
 
- 	case <-time.After(3 * time.Second):
 
- 		t.Fatal("Timed out without index entry being sent")
 
- 	}
 
- }
 
- func TestRequestVersioningSymlinkAttack(t *testing.T) {
 
- 	if runtime.GOOS == "windows" {
 
- 		t.Skip("no symlink support on Windows")
 
- 	}
 
- 	// Sets up a folder with trashcan versioning and tries to use a
 
- 	// deleted symlink to escape
 
- 	w, fcfg, wCancel := tmpDefaultWrapper()
 
- 	defer wCancel()
 
- 	defer func() {
 
- 		os.RemoveAll(fcfg.Filesystem(nil).URI())
 
- 		os.Remove(w.ConfigPath())
 
- 	}()
 
- 	fcfg.Versioning = config.VersioningConfiguration{Type: "trashcan"}
 
- 	setFolder(t, w, fcfg)
 
- 	m, fc := setupModelWithConnectionFromWrapper(t, w)
 
- 	defer cleanupModel(m)
 
- 	// Create a temporary directory that we will use as target to see if
 
- 	// we can escape to it
 
- 	tmpdir, err := os.MkdirTemp("", "syncthing-test")
 
- 	if err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	defer os.RemoveAll(tmpdir)
 
- 	// We listen for incoming index updates and trigger when we see one for
 
- 	// the expected test file.
 
- 	idx := make(chan int)
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		idx <- len(fs)
 
- 		return nil
 
- 	})
 
- 	waitForIdx := func() {
 
- 		select {
 
- 		case c := <-idx:
 
- 			if c == 0 {
 
- 				t.Fatal("Got empty index update")
 
- 			}
 
- 		case <-time.After(5 * time.Second):
 
- 			t.Fatal("timed out before receiving index update")
 
- 		}
 
- 	}
 
- 	// Send an update for the test file, wait for it to sync and be reported back.
 
- 	fc.addFile("foo", 0644, protocol.FileInfoTypeSymlink, []byte(tmpdir))
 
- 	fc.sendIndexUpdate()
 
- 	waitForIdx()
 
- 	// Delete the symlink, hoping for it to get versioned
 
- 	fc.deleteFile("foo")
 
- 	fc.sendIndexUpdate()
 
- 	waitForIdx()
 
- 	// Recreate foo and a file in it with some data
 
- 	fc.updateFile("foo", 0755, protocol.FileInfoTypeDirectory, nil)
 
- 	fc.addFile("foo/test", 0644, protocol.FileInfoTypeFile, []byte("testtesttest"))
 
- 	fc.sendIndexUpdate()
 
- 	waitForIdx()
 
- 	// Remove the test file and see if it escaped
 
- 	fc.deleteFile("foo/test")
 
- 	fc.sendIndexUpdate()
 
- 	waitForIdx()
 
- 	path := filepath.Join(tmpdir, "test")
 
- 	if _, err := os.Lstat(path); !os.IsNotExist(err) {
 
- 		t.Fatal("File escaped to", path)
 
- 	}
 
- }
 
- func TestPullInvalidIgnoredSO(t *testing.T) {
 
- 	pullInvalidIgnored(t, config.FolderTypeSendOnly)
 
- }
 
- func TestPullInvalidIgnoredSR(t *testing.T) {
 
- 	pullInvalidIgnored(t, config.FolderTypeSendReceive)
 
- }
 
- // This test checks that (un-)ignored/invalid/deleted files are treated as expected.
 
- func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
 
- 	w, wCancel := createTmpWrapper(defaultCfgWrapper.RawCopy())
 
- 	defer wCancel()
 
- 	fcfg := testFolderConfigTmp()
 
- 	fss := fcfg.Filesystem(nil)
 
- 	fcfg.Type = ft
 
- 	setFolder(t, w, fcfg)
 
- 	m := setupModel(t, w)
 
- 	defer cleanupModelAndRemoveDir(m, fss.URI())
 
- 	folderIgnoresAlwaysReload(t, m, fcfg)
 
- 	fc := addFakeConn(m, device1, fcfg.ID)
 
- 	fc.folder = "default"
 
- 	if err := m.SetIgnores("default", []string{"*ignored*"}); err != nil {
 
- 		panic(err)
 
- 	}
 
- 	contents := []byte("test file contents\n")
 
- 	otherContents := []byte("other test file contents\n")
 
- 	invIgn := "invalid:ignored"
 
- 	invDel := "invalid:deleted"
 
- 	ign := "ignoredNonExisting"
 
- 	ignExisting := "ignoredExisting"
 
- 	fc.addFile(invIgn, 0644, protocol.FileInfoTypeFile, contents)
 
- 	fc.addFile(invDel, 0644, protocol.FileInfoTypeFile, contents)
 
- 	fc.deleteFile(invDel)
 
- 	fc.addFile(ign, 0644, protocol.FileInfoTypeFile, contents)
 
- 	fc.addFile(ignExisting, 0644, protocol.FileInfoTypeFile, contents)
 
- 	writeFile(t, fss, ignExisting, otherContents)
 
- 	done := make(chan struct{})
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		expected := map[string]struct{}{invIgn: {}, ign: {}, ignExisting: {}}
 
- 		for _, f := range fs {
 
- 			if _, ok := expected[f.Name]; !ok {
 
- 				t.Errorf("Unexpected file %v was added to index", f.Name)
 
- 			}
 
- 			if !f.IsInvalid() {
 
- 				t.Errorf("File %v wasn't marked as invalid", f.Name)
 
- 			}
 
- 			delete(expected, f.Name)
 
- 		}
 
- 		for name := range expected {
 
- 			t.Errorf("File %v wasn't added to index", name)
 
- 		}
 
- 		close(done)
 
- 		return nil
 
- 	})
 
- 	sub := m.evLogger.Subscribe(events.FolderErrors)
 
- 	defer sub.Unsubscribe()
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case ev := <-sub.C():
 
- 		t.Fatalf("Errors while scanning/pulling: %v", ev)
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatalf("timed out before index was received")
 
- 	case <-done:
 
- 	}
 
- 	done = make(chan struct{})
 
- 	expected := map[string]struct{}{ign: {}, ignExisting: {}}
 
- 	var expectedMut sync.Mutex
 
- 	// The indexes will normally arrive in one update, but it is possible
 
- 	// that they arrive in separate ones.
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		expectedMut.Lock()
 
- 		for _, f := range fs {
 
- 			_, ok := expected[f.Name]
 
- 			if !ok {
 
- 				t.Errorf("Unexpected file %v was updated in index", f.Name)
 
- 				continue
 
- 			}
 
- 			if f.IsInvalid() {
 
- 				t.Errorf("File %v is still marked as invalid", f.Name)
 
- 			}
 
- 			if f.Name == ign {
 
- 				// The unignored deleted file should have an
 
- 				// empty version, to make it not override
 
- 				// existing global files.
 
- 				if !f.Deleted {
 
- 					t.Errorf("File %v was not marked as deleted", f.Name)
 
- 				}
 
- 				if len(f.Version.Counters) != 0 {
 
- 					t.Errorf("File %v has version %v, expected empty", f.Name, f.Version)
 
- 				}
 
- 			} else {
 
- 				// The unignored existing file should have a
 
- 				// version with only a local counter, to make
 
- 				// it conflict changed global files.
 
- 				if f.Deleted {
 
- 					t.Errorf("File %v is marked as deleted", f.Name)
 
- 				}
 
- 				if len(f.Version.Counters) != 1 || f.Version.Counter(myID.Short()) == 0 {
 
- 					t.Errorf("File %v has version %v, expected one entry for ourselves", f.Name, f.Version)
 
- 				}
 
- 			}
 
- 			delete(expected, f.Name)
 
- 		}
 
- 		if len(expected) == 0 {
 
- 			close(done)
 
- 		}
 
- 		expectedMut.Unlock()
 
- 		return nil
 
- 	})
 
- 	// Make sure pulling doesn't interfere, as index updates are racy and
 
- 	// thus we cannot distinguish between scan and pull results.
 
- 	fc.RequestCalls(func(ctx context.Context, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
 
- 		return nil, nil
 
- 	})
 
- 	if err := m.SetIgnores("default", []string{"*:ignored*"}); err != nil {
 
- 		panic(err)
 
- 	}
 
- 	select {
 
- 	case <-time.After(5 * time.Second):
 
- 		expectedMut.Lock()
 
- 		t.Fatal("timed out before receiving index updates for all existing files, missing", expected)
 
- 		expectedMut.Unlock()
 
- 	case <-done:
 
- 	}
 
- }
 
- func TestIssue4841(t *testing.T) {
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
 
- 	received := make(chan []protocol.FileInfo)
 
- 	fc.setIndexFn(func(_ context.Context, _ string, fs []protocol.FileInfo) error {
 
- 		received <- fs
 
- 		return nil
 
- 	})
 
- 	checkReceived := func(fs []protocol.FileInfo) protocol.FileInfo {
 
- 		t.Helper()
 
- 		if len(fs) != 1 {
 
- 			t.Fatalf("Sent index with %d files, should be 1", len(fs))
 
- 		}
 
- 		if fs[0].Name != "foo" {
 
- 			t.Fatalf(`Sent index with file %v, should be "foo"`, fs[0].Name)
 
- 		}
 
- 		return fs[0]
 
- 	}
 
- 	// Setup file from remote that was ignored locally
 
- 	folder := m.folderRunners[defaultFolderConfig.ID].(*sendReceiveFolder)
 
- 	folder.updateLocals([]protocol.FileInfo{{
 
- 		Name:       "foo",
 
- 		Type:       protocol.FileInfoTypeFile,
 
- 		LocalFlags: protocol.FlagLocalIgnored,
 
- 		Version:    protocol.Vector{}.Update(device1.Short()),
 
- 	}})
 
- 	checkReceived(<-received)
 
- 	// Scan without ignore patterns with "foo" not existing locally
 
- 	if err := m.ScanFolder("default"); err != nil {
 
- 		t.Fatal("Failed scanning:", err)
 
- 	}
 
- 	select {
 
- 	case <-time.After(10 * time.Second):
 
- 		t.Fatal("timed out")
 
- 	case r := <-received:
 
- 		f := checkReceived(r)
 
- 		if !f.Version.Equal(protocol.Vector{}) {
 
- 			t.Errorf("Got Version == %v, expected empty version", f.Version)
 
- 		}
 
- 	}
 
- }
 
- func TestRescanIfHaveInvalidContent(t *testing.T) {
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	tfs := fcfg.Filesystem(nil)
 
- 	defer cleanupModelAndRemoveDir(m, tfs.URI())
 
- 	payload := []byte("hello")
 
- 	writeFile(t, tfs, "foo", payload)
 
- 	received := make(chan []protocol.FileInfo)
 
- 	fc.setIndexFn(func(_ context.Context, _ string, fs []protocol.FileInfo) error {
 
- 		received <- fs
 
- 		return nil
 
- 	})
 
- 	checkReceived := func(fs []protocol.FileInfo) protocol.FileInfo {
 
- 		t.Helper()
 
- 		if len(fs) != 1 {
 
- 			t.Fatalf("Sent index with %d files, should be 1", len(fs))
 
- 		}
 
- 		if fs[0].Name != "foo" {
 
- 			t.Fatalf(`Sent index with file %v, should be "foo"`, fs[0].Name)
 
- 		}
 
- 		return fs[0]
 
- 	}
 
- 	// Scan without ignore patterns with "foo" not existing locally
 
- 	if err := m.ScanFolder("default"); err != nil {
 
- 		t.Fatal("Failed scanning:", err)
 
- 	}
 
- 	f := checkReceived(<-received)
 
- 	if f.Blocks[0].WeakHash != 103547413 {
 
- 		t.Fatalf("unexpected weak hash: %d != 103547413", f.Blocks[0].WeakHash)
 
- 	}
 
- 	res, err := m.Request(device1, "default", "foo", 0, int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
 
- 	if err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	buf := res.Data()
 
- 	if !bytes.Equal(buf, payload) {
 
- 		t.Errorf("%s != %s", buf, payload)
 
- 	}
 
- 	payload = []byte("bye")
 
- 	buf = make([]byte, len(payload))
 
- 	writeFile(t, tfs, "foo", payload)
 
- 	_, err = m.Request(device1, "default", "foo", 0, int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
 
- 	if err == nil {
 
- 		t.Fatalf("expected failure")
 
- 	}
 
- 	select {
 
- 	case fs := <-received:
 
- 		f := checkReceived(fs)
 
- 		if f.Blocks[0].WeakHash != 41943361 {
 
- 			t.Fatalf("unexpected weak hash: %d != 41943361", f.Blocks[0].WeakHash)
 
- 		}
 
- 	case <-time.After(time.Second):
 
- 		t.Fatalf("timed out")
 
- 	}
 
- }
 
- func TestParentDeletion(t *testing.T) {
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	testFs := fcfg.Filesystem(nil)
 
- 	defer cleanupModelAndRemoveDir(m, testFs.URI())
 
- 	parent := "foo"
 
- 	child := filepath.Join(parent, "bar")
 
- 	received := make(chan []protocol.FileInfo)
 
- 	fc.addFile(parent, 0777, protocol.FileInfoTypeDirectory, nil)
 
- 	fc.addFile(child, 0777, protocol.FileInfoTypeDirectory, nil)
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		received <- fs
 
- 		return nil
 
- 	})
 
- 	fc.sendIndexUpdate()
 
- 	// Get back index from initial setup
 
- 	select {
 
- 	case fs := <-received:
 
- 		if len(fs) != 2 {
 
- 			t.Fatalf("Sent index with %d files, should be 2", len(fs))
 
- 		}
 
- 	case <-time.After(time.Second):
 
- 		t.Fatalf("timed out")
 
- 	}
 
- 	// Delete parent dir
 
- 	must(t, testFs.RemoveAll(parent))
 
- 	// Scan only the child dir (not the parent)
 
- 	if err := m.ScanFolderSubdirs("default", []string{child}); err != nil {
 
- 		t.Fatal("Failed scanning:", err)
 
- 	}
 
- 	select {
 
- 	case fs := <-received:
 
- 		if len(fs) != 1 {
 
- 			t.Fatalf("Sent index with %d files, should be 1", len(fs))
 
- 		}
 
- 		if fs[0].Name != child {
 
- 			t.Fatalf(`Sent index with file "%v", should be "%v"`, fs[0].Name, child)
 
- 		}
 
- 	case <-time.After(time.Second):
 
- 		t.Fatalf("timed out")
 
- 	}
 
- 	// Recreate the child dir on the remote
 
- 	fc.updateFile(child, 0777, protocol.FileInfoTypeDirectory, nil)
 
- 	fc.sendIndexUpdate()
 
- 	// Wait for the child dir to be recreated and sent to the remote
 
- 	select {
 
- 	case fs := <-received:
 
- 		l.Debugln("sent:", fs)
 
- 		found := false
 
- 		for _, f := range fs {
 
- 			if f.Name == child {
 
- 				if f.Deleted {
 
- 					t.Fatalf(`File "%v" is still deleted`, child)
 
- 				}
 
- 				found = true
 
- 			}
 
- 		}
 
- 		if !found {
 
- 			t.Fatalf(`File "%v" is missing in index`, child)
 
- 		}
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatalf("timed out")
 
- 	}
 
- }
 
- // TestRequestSymlinkWindows checks that symlinks aren't marked as deleted on windows
 
- // Issue: https://github.com/syncthing/syncthing/issues/5125
 
- func TestRequestSymlinkWindows(t *testing.T) {
 
- 	if runtime.GOOS != "windows" {
 
- 		t.Skip("windows specific test")
 
- 	}
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
 
- 	received := make(chan []protocol.FileInfo)
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		select {
 
- 		case <-received:
 
- 			t.Error("More than one index update sent")
 
- 		default:
 
- 		}
 
- 		received <- fs
 
- 		return nil
 
- 	})
 
- 	fc.addFile("link", 0644, protocol.FileInfoTypeSymlink, nil)
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case fs := <-received:
 
- 		close(received)
 
- 		// expected first index
 
- 		if len(fs) != 1 {
 
- 			t.Fatalf("Expected just one file in index, got %v", fs)
 
- 		}
 
- 		f := fs[0]
 
- 		if f.Name != "link" {
 
- 			t.Fatalf(`Got file info with path "%v", expected "link"`, f.Name)
 
- 		}
 
- 		if !f.IsInvalid() {
 
- 			t.Errorf(`File info was not marked as invalid`)
 
- 		}
 
- 	case <-time.After(time.Second):
 
- 		t.Fatalf("timed out before pull was finished")
 
- 	}
 
- 	sub := m.evLogger.Subscribe(events.StateChanged | events.LocalIndexUpdated)
 
- 	defer sub.Unsubscribe()
 
- 	m.ScanFolder("default")
 
- 	for {
 
- 		select {
 
- 		case ev := <-sub.C():
 
- 			switch data := ev.Data.(map[string]interface{}); {
 
- 			case ev.Type == events.LocalIndexUpdated:
 
- 				t.Fatalf("Local index was updated unexpectedly: %v", data)
 
- 			case ev.Type == events.StateChanged:
 
- 				if data["from"] == "scanning" {
 
- 					return
 
- 				}
 
- 			}
 
- 		case <-time.After(5 * time.Second):
 
- 			t.Fatalf("Timed out before scan finished")
 
- 		}
 
- 	}
 
- }
 
- func equalContents(path string, contents []byte) error {
 
- 	if bs, err := os.ReadFile(path); err != nil {
 
- 		return err
 
- 	} else if !bytes.Equal(bs, contents) {
 
- 		return errors.New("incorrect data")
 
- 	}
 
- 	return nil
 
- }
 
- func TestRequestRemoteRenameChanged(t *testing.T) {
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	tfs := fcfg.Filesystem(nil)
 
- 	tmpDir := tfs.URI()
 
- 	defer cleanupModelAndRemoveDir(m, tfs.URI())
 
- 	received := make(chan []protocol.FileInfo)
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		select {
 
- 		case <-received:
 
- 			t.Error("More than one index update sent")
 
- 		default:
 
- 		}
 
- 		received <- fs
 
- 		return nil
 
- 	})
 
- 	// setup
 
- 	a := "a"
 
- 	b := "b"
 
- 	data := map[string][]byte{
 
- 		a: []byte("aData"),
 
- 		b: []byte("bData"),
 
- 	}
 
- 	for _, n := range [2]string{a, b} {
 
- 		fc.addFile(n, 0644, protocol.FileInfoTypeFile, data[n])
 
- 	}
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case fs := <-received:
 
- 		close(received)
 
- 		if len(fs) != 2 {
 
- 			t.Fatalf("Received index with %v indexes instead of 2", len(fs))
 
- 		}
 
- 	case <-time.After(10 * time.Second):
 
- 		t.Fatal("timed out")
 
- 	}
 
- 	for _, n := range [2]string{a, b} {
 
- 		must(t, equalContents(filepath.Join(tmpDir, n), data[n]))
 
- 	}
 
- 	var gotA, gotB, gotConfl bool
 
- 	done := make(chan struct{})
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		select {
 
- 		case <-done:
 
- 			t.Error("Received more index updates than expected")
 
- 			return nil
 
- 		default:
 
- 		}
 
- 		for _, f := range fs {
 
- 			switch {
 
- 			case f.Name == a:
 
- 				if gotA {
 
- 					t.Error("Got more than one index update for", f.Name)
 
- 				}
 
- 				gotA = true
 
- 			case f.Name == b:
 
- 				if gotB {
 
- 					t.Error("Got more than one index update for", f.Name)
 
- 				}
 
- 				if f.Version.Counter(fc.id.Short()) == 0 {
 
- 					// This index entry might be superseeded
 
- 					// by the final one or sent before it separately.
 
- 					break
 
- 				}
 
- 				gotB = true
 
- 			case strings.HasPrefix(f.Name, "b.sync-conflict-"):
 
- 				if gotConfl {
 
- 					t.Error("Got more than one index update for conflicts of", f.Name)
 
- 				}
 
- 				gotConfl = true
 
- 			default:
 
- 				t.Error("Got unexpected file in index update", f.Name)
 
- 			}
 
- 		}
 
- 		if gotA && gotB && gotConfl {
 
- 			close(done)
 
- 		}
 
- 		return nil
 
- 	})
 
- 	fd, err := tfs.OpenFile(b, fs.OptReadWrite, 0644)
 
- 	if err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	otherData := []byte("otherData")
 
- 	if _, err = fd.Write(otherData); err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	fd.Close()
 
- 	// rename
 
- 	fc.deleteFile(a)
 
- 	fc.updateFile(b, 0644, protocol.FileInfoTypeFile, data[a])
 
- 	// Make sure the remote file for b is newer and thus stays global -> local conflict
 
- 	fc.mut.Lock()
 
- 	for i := range fc.files {
 
- 		if fc.files[i].Name == b {
 
- 			fc.files[i].ModifiedS += 100
 
- 			break
 
- 		}
 
- 	}
 
- 	fc.mut.Unlock()
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case <-done:
 
- 	case <-time.After(10 * time.Second):
 
- 		t.Errorf("timed out without receiving all expected index updates")
 
- 	}
 
- 	// Check outcome
 
- 	tfs.Walk(".", func(path string, info fs.FileInfo, err error) error {
 
- 		switch {
 
- 		case path == a:
 
- 			t.Errorf(`File "a" was not removed`)
 
- 		case path == b:
 
- 			if err := equalContents(filepath.Join(tmpDir, b), data[a]); err != nil {
 
- 				t.Error(`File "b" has unexpected content (renamed from a on remote)`)
 
- 			}
 
- 		case strings.HasPrefix(path, b+".sync-conflict-"):
 
- 			if err := equalContents(filepath.Join(tmpDir, path), otherData); err != nil {
 
- 				t.Error(`Sync conflict of "b" has unexptected content`)
 
- 			}
 
- 		case path == "." || strings.HasPrefix(path, ".stfolder"):
 
- 		default:
 
- 			t.Error("Found unexpected file", path)
 
- 		}
 
- 		return nil
 
- 	})
 
- }
 
- func TestRequestRemoteRenameConflict(t *testing.T) {
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	tfs := fcfg.Filesystem(nil)
 
- 	tmpDir := tfs.URI()
 
- 	defer cleanupModelAndRemoveDir(m, tmpDir)
 
- 	recv := make(chan int)
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		recv <- len(fs)
 
- 		return nil
 
- 	})
 
- 	// setup
 
- 	a := "a"
 
- 	b := "b"
 
- 	data := map[string][]byte{
 
- 		a: []byte("aData"),
 
- 		b: []byte("bData"),
 
- 	}
 
- 	for _, n := range [2]string{a, b} {
 
- 		fc.addFile(n, 0644, protocol.FileInfoTypeFile, data[n])
 
- 	}
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case i := <-recv:
 
- 		if i != 2 {
 
- 			t.Fatalf("received %v items in index, expected 1", i)
 
- 		}
 
- 	case <-time.After(10 * time.Second):
 
- 		t.Fatal("timed out")
 
- 	}
 
- 	for _, n := range [2]string{a, b} {
 
- 		must(t, equalContents(filepath.Join(tmpDir, n), data[n]))
 
- 	}
 
- 	fd, err := tfs.OpenFile(b, fs.OptReadWrite, 0644)
 
- 	if err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	otherData := []byte("otherData")
 
- 	if _, err = fd.Write(otherData); err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	fd.Close()
 
- 	m.ScanFolders()
 
- 	select {
 
- 	case i := <-recv:
 
- 		if i != 1 {
 
- 			t.Fatalf("received %v items in index, expected 1", i)
 
- 		}
 
- 	case <-time.After(10 * time.Second):
 
- 		t.Fatal("timed out")
 
- 	}
 
- 	// make sure the following rename is more recent (not concurrent)
 
- 	time.Sleep(2 * time.Second)
 
- 	// rename
 
- 	fc.deleteFile(a)
 
- 	fc.updateFile(b, 0644, protocol.FileInfoTypeFile, data[a])
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case <-recv:
 
- 	case <-time.After(10 * time.Second):
 
- 		t.Fatal("timed out")
 
- 	}
 
- 	// Check outcome
 
- 	foundB := false
 
- 	foundBConfl := false
 
- 	tfs.Walk(".", func(path string, info fs.FileInfo, err error) error {
 
- 		switch {
 
- 		case path == a:
 
- 			t.Errorf(`File "a" was not removed`)
 
- 		case path == b:
 
- 			foundB = true
 
- 		case strings.HasPrefix(path, b) && strings.Contains(path, ".sync-conflict-"):
 
- 			foundBConfl = true
 
- 		}
 
- 		return nil
 
- 	})
 
- 	if !foundB {
 
- 		t.Errorf(`File "b" was removed`)
 
- 	}
 
- 	if !foundBConfl {
 
- 		t.Errorf(`No conflict file for "b" was created`)
 
- 	}
 
- }
 
- func TestRequestDeleteChanged(t *testing.T) {
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	tfs := fcfg.Filesystem(nil)
 
- 	defer cleanupModelAndRemoveDir(m, tfs.URI())
 
- 	done := make(chan struct{})
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		select {
 
- 		case <-done:
 
- 			t.Error("More than one index update sent")
 
- 		default:
 
- 		}
 
- 		close(done)
 
- 		return nil
 
- 	})
 
- 	// setup
 
- 	a := "a"
 
- 	data := []byte("aData")
 
- 	fc.addFile(a, 0644, protocol.FileInfoTypeFile, data)
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case <-done:
 
- 		done = make(chan struct{})
 
- 	case <-time.After(10 * time.Second):
 
- 		t.Fatal("timed out")
 
- 	}
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		select {
 
- 		case <-done:
 
- 			t.Error("More than one index update sent")
 
- 		default:
 
- 		}
 
- 		close(done)
 
- 		return nil
 
- 	})
 
- 	fd, err := tfs.OpenFile(a, fs.OptReadWrite, 0644)
 
- 	if err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	otherData := []byte("otherData")
 
- 	if _, err = fd.Write(otherData); err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	fd.Close()
 
- 	// rename
 
- 	fc.deleteFile(a)
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case <-done:
 
- 	case <-time.After(10 * time.Second):
 
- 		t.Fatal("timed out")
 
- 	}
 
- 	// Check outcome
 
- 	if _, err := tfs.Lstat(a); err != nil {
 
- 		if fs.IsNotExist(err) {
 
- 			t.Error(`Modified file "a" was removed`)
 
- 		} else {
 
- 			t.Error(`Error stating file "a":`, err)
 
- 		}
 
- 	}
 
- }
 
- func TestNeedFolderFiles(t *testing.T) {
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	tfs := fcfg.Filesystem(nil)
 
- 	tmpDir := tfs.URI()
 
- 	defer cleanupModelAndRemoveDir(m, tmpDir)
 
- 	sub := m.evLogger.Subscribe(events.RemoteIndexUpdated)
 
- 	defer sub.Unsubscribe()
 
- 	errPreventSync := errors.New("you aren't getting any of this")
 
- 	fc.RequestCalls(func(ctx context.Context, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
 
- 		return nil, errPreventSync
 
- 	})
 
- 	data := []byte("foo")
 
- 	num := 20
 
- 	for i := 0; i < num; i++ {
 
- 		fc.addFile(strconv.Itoa(i), 0644, protocol.FileInfoTypeFile, data)
 
- 	}
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case <-sub.C():
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatal("Timed out before receiving index")
 
- 	}
 
- 	progress, queued, rest, err := m.NeedFolderFiles(fcfg.ID, 1, 100)
 
- 	must(t, err)
 
- 	if got := len(progress) + len(queued) + len(rest); got != num {
 
- 		t.Errorf("Got %v needed items, expected %v", got, num)
 
- 	}
 
- 	exp := 10
 
- 	for page := 1; page < 3; page++ {
 
- 		progress, queued, rest, err := m.NeedFolderFiles(fcfg.ID, page, exp)
 
- 		must(t, err)
 
- 		if got := len(progress) + len(queued) + len(rest); got != exp {
 
- 			t.Errorf("Got %v needed items on page %v, expected %v", got, page, exp)
 
- 		}
 
- 	}
 
- }
 
- // TestIgnoreDeleteUnignore checks that the deletion of an ignored file is not
 
- // propagated upon un-ignoring.
 
- // https://github.com/syncthing/syncthing/issues/6038
 
- func TestIgnoreDeleteUnignore(t *testing.T) {
 
- 	w, fcfg, wCancel := tmpDefaultWrapper()
 
- 	defer wCancel()
 
- 	m := setupModel(t, w)
 
- 	fss := fcfg.Filesystem(nil)
 
- 	tmpDir := fss.URI()
 
- 	defer cleanupModelAndRemoveDir(m, tmpDir)
 
- 	folderIgnoresAlwaysReload(t, m, fcfg)
 
- 	m.ScanFolders()
 
- 	fc := addFakeConn(m, device1, fcfg.ID)
 
- 	fc.folder = "default"
 
- 	fc.mut.Lock()
 
- 	fc.mut.Unlock()
 
- 	file := "foobar"
 
- 	contents := []byte("test file contents\n")
 
- 	basicCheck := func(fs []protocol.FileInfo) {
 
- 		t.Helper()
 
- 		if len(fs) != 1 {
 
- 			t.Fatal("expected a single index entry, got", len(fs))
 
- 		} else if fs[0].Name != file {
 
- 			t.Fatalf("expected a index entry for %v, got one for %v", file, fs[0].Name)
 
- 		}
 
- 	}
 
- 	done := make(chan struct{})
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		basicCheck(fs)
 
- 		close(done)
 
- 		return nil
 
- 	})
 
- 	writeFile(t, fss, file, contents)
 
- 	m.ScanFolders()
 
- 	select {
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatalf("timed out before index was received")
 
- 	case <-done:
 
- 	}
 
- 	done = make(chan struct{})
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		basicCheck(fs)
 
- 		f := fs[0]
 
- 		if !f.IsInvalid() {
 
- 			t.Errorf("Received non-invalid index update")
 
- 		}
 
- 		close(done)
 
- 		return nil
 
- 	})
 
- 	if err := m.SetIgnores("default", []string{"foobar"}); err != nil {
 
- 		panic(err)
 
- 	}
 
- 	select {
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatal("timed out before receiving index update")
 
- 	case <-done:
 
- 	}
 
- 	done = make(chan struct{})
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		basicCheck(fs)
 
- 		f := fs[0]
 
- 		if f.IsInvalid() {
 
- 			t.Errorf("Received invalid index update")
 
- 		}
 
- 		if !f.Version.Equal(protocol.Vector{}) && f.Deleted {
 
- 			t.Error("Received deleted index entry with non-empty version")
 
- 		}
 
- 		l.Infoln(f)
 
- 		close(done)
 
- 		return nil
 
- 	})
 
- 	if err := fss.Remove(file); err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	if err := m.SetIgnores("default", []string{}); err != nil {
 
- 		panic(err)
 
- 	}
 
- 	select {
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatalf("timed out before index was received")
 
- 	case <-done:
 
- 	}
 
- }
 
- // TestRequestLastFileProgress checks that the last pulled file (here only) is registered
 
- // as in progress.
 
- func TestRequestLastFileProgress(t *testing.T) {
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	tfs := fcfg.Filesystem(nil)
 
- 	defer cleanupModelAndRemoveDir(m, tfs.URI())
 
- 	done := make(chan struct{})
 
- 	fc.RequestCalls(func(ctx context.Context, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
 
- 		defer close(done)
 
- 		progress, queued, rest, err := m.NeedFolderFiles(folder, 1, 10)
 
- 		must(t, err)
 
- 		if len(queued)+len(rest) != 0 {
 
- 			t.Error(`There should not be any queued or "rest" items`)
 
- 		}
 
- 		if len(progress) != 1 {
 
- 			t.Error("Expected exactly one item in progress.")
 
- 		}
 
- 		return fc.fileData[name], nil
 
- 	})
 
- 	contents := []byte("test file contents\n")
 
- 	fc.addFile("testfile", 0644, protocol.FileInfoTypeFile, contents)
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case <-done:
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatal("Timed out before file was requested")
 
- 	}
 
- }
 
- func TestRequestIndexSenderPause(t *testing.T) {
 
- 	done := make(chan struct{})
 
- 	defer close(done)
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	tfs := fcfg.Filesystem(nil)
 
- 	defer cleanupModelAndRemoveDir(m, tfs.URI())
 
- 	indexChan := make(chan []protocol.FileInfo)
 
- 	fc.setIndexFn(func(ctx context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		select {
 
- 		case indexChan <- fs:
 
- 		case <-done:
 
- 		case <-ctx.Done():
 
- 		}
 
- 		return nil
 
- 	})
 
- 	var seq int64 = 1
 
- 	files := []protocol.FileInfo{{Name: "foo", Size: 10, Version: protocol.Vector{}.Update(myID.Short()), Sequence: seq}}
 
- 	// Both devices connected, noone paused
 
- 	localIndexUpdate(m, fcfg.ID, files)
 
- 	select {
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatal("timed out before receiving index")
 
- 	case <-indexChan:
 
- 	}
 
- 	// Remote paused
 
- 	cc := basicClusterConfig(device1, myID, fcfg.ID)
 
- 	cc.Folders[0].Paused = true
 
- 	m.ClusterConfig(device1, cc)
 
- 	seq++
 
- 	files[0].Sequence = seq
 
- 	files[0].Version = files[0].Version.Update(myID.Short())
 
- 	localIndexUpdate(m, fcfg.ID, files)
 
- 	// I don't see what to hook into to ensure an index update is not sent.
 
- 	dur := 50 * time.Millisecond
 
- 	if !testing.Short() {
 
- 		dur = 2 * time.Second
 
- 	}
 
- 	select {
 
- 	case <-time.After(dur):
 
- 	case <-indexChan:
 
- 		t.Error("Received index despite remote being paused")
 
- 	}
 
- 	// Remote unpaused
 
- 	cc.Folders[0].Paused = false
 
- 	m.ClusterConfig(device1, cc)
 
- 	select {
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatal("timed out before receiving index")
 
- 	case <-indexChan:
 
- 	}
 
- 	// Local paused and resume
 
- 	pauseFolder(t, m.cfg, fcfg.ID, true)
 
- 	pauseFolder(t, m.cfg, fcfg.ID, false)
 
- 	seq++
 
- 	files[0].Sequence = seq
 
- 	files[0].Version = files[0].Version.Update(myID.Short())
 
- 	localIndexUpdate(m, fcfg.ID, files)
 
- 	select {
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatal("timed out before receiving index")
 
- 	case <-indexChan:
 
- 	}
 
- 	// Local and remote paused, then first resume remote, then local
 
- 	cc.Folders[0].Paused = true
 
- 	m.ClusterConfig(device1, cc)
 
- 	pauseFolder(t, m.cfg, fcfg.ID, true)
 
- 	cc.Folders[0].Paused = false
 
- 	m.ClusterConfig(device1, cc)
 
- 	pauseFolder(t, m.cfg, fcfg.ID, false)
 
- 	seq++
 
- 	files[0].Sequence = seq
 
- 	files[0].Version = files[0].Version.Update(myID.Short())
 
- 	localIndexUpdate(m, fcfg.ID, files)
 
- 	select {
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatal("timed out before receiving index")
 
- 	case <-indexChan:
 
- 	}
 
- 	// Folder removed on remote
 
- 	cc = protocol.ClusterConfig{}
 
- 	m.ClusterConfig(device1, cc)
 
- 	seq++
 
- 	files[0].Sequence = seq
 
- 	files[0].Version = files[0].Version.Update(myID.Short())
 
- 	localIndexUpdate(m, fcfg.ID, files)
 
- 	select {
 
- 	case <-time.After(dur):
 
- 	case <-indexChan:
 
- 		t.Error("Received index despite remote not having the folder")
 
- 	}
 
- }
 
- func TestRequestIndexSenderClusterConfigBeforeStart(t *testing.T) {
 
- 	w, fcfg, wCancel := tmpDefaultWrapper()
 
- 	defer wCancel()
 
- 	tfs := fcfg.Filesystem(nil)
 
- 	dir1 := "foo"
 
- 	dir2 := "bar"
 
- 	// Initialise db with an entry and then stop everything again
 
- 	must(t, tfs.Mkdir(dir1, 0777))
 
- 	m := newModel(t, w, myID, "syncthing", "dev", nil)
 
- 	defer cleanupModelAndRemoveDir(m, tfs.URI())
 
- 	m.ServeBackground()
 
- 	m.ScanFolders()
 
- 	m.cancel()
 
- 	<-m.stopped
 
- 	// Add connection (sends incoming cluster config) before starting the new model
 
- 	m = &testModel{
 
- 		model:    NewModel(m.cfg, m.id, m.clientName, m.clientVersion, m.db, m.protectedFiles, m.evLogger).(*model),
 
- 		evCancel: m.evCancel,
 
- 		stopped:  make(chan struct{}),
 
- 	}
 
- 	defer cleanupModel(m)
 
- 	fc := addFakeConn(m, device1, fcfg.ID)
 
- 	done := make(chan struct{})
 
- 	defer close(done) // Must be the last thing to be deferred, thus first to run.
 
- 	indexChan := make(chan []protocol.FileInfo, 1)
 
- 	ccChan := make(chan protocol.ClusterConfig, 1)
 
- 	fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		select {
 
- 		case indexChan <- fs:
 
- 		case <-done:
 
- 		}
 
- 		return nil
 
- 	})
 
- 	fc.ClusterConfigCalls(func(cc protocol.ClusterConfig) {
 
- 		select {
 
- 		case ccChan <- cc:
 
- 		case <-done:
 
- 		}
 
- 	})
 
- 	m.ServeBackground()
 
- 	timeout := time.After(5 * time.Second)
 
- 	// Check that cluster-config is resent after adding folders when starting model
 
- 	select {
 
- 	case <-timeout:
 
- 		t.Fatal("timed out before receiving cluster-config")
 
- 	case <-ccChan:
 
- 	}
 
- 	// Check that an index is sent for the newly added item
 
- 	must(t, tfs.Mkdir(dir2, 0777))
 
- 	m.ScanFolders()
 
- 	select {
 
- 	case <-timeout:
 
- 		t.Fatal("timed out before receiving index")
 
- 	case <-indexChan:
 
- 	}
 
- }
 
- func TestRequestReceiveEncrypted(t *testing.T) {
 
- 	if testing.Short() {
 
- 		t.Skip("skipping on short testing - scrypt is too slow")
 
- 	}
 
- 	w, fcfg, wCancel := tmpDefaultWrapper()
 
- 	defer wCancel()
 
- 	tfs := fcfg.Filesystem(nil)
 
- 	fcfg.Type = config.FolderTypeReceiveEncrypted
 
- 	setFolder(t, w, fcfg)
 
- 	encToken := protocol.PasswordToken(fcfg.ID, "pw")
 
- 	must(t, tfs.Mkdir(config.DefaultMarkerName, 0777))
 
- 	must(t, writeEncryptionToken(encToken, fcfg))
 
- 	m := setupModel(t, w)
 
- 	defer cleanupModelAndRemoveDir(m, tfs.URI())
 
- 	files := genFiles(2)
 
- 	files[1].LocalFlags = protocol.FlagLocalReceiveOnly
 
- 	m.fmut.RLock()
 
- 	fset := m.folderFiles[fcfg.ID]
 
- 	m.fmut.RUnlock()
 
- 	fset.Update(protocol.LocalDeviceID, files)
 
- 	indexChan := make(chan []protocol.FileInfo, 10)
 
- 	done := make(chan struct{})
 
- 	defer close(done)
 
- 	fc := newFakeConnection(device1, m)
 
- 	fc.folder = fcfg.ID
 
- 	fc.setIndexFn(func(_ context.Context, _ string, fs []protocol.FileInfo) error {
 
- 		select {
 
- 		case indexChan <- fs:
 
- 		case <-done:
 
- 		}
 
- 		return nil
 
- 	})
 
- 	m.AddConnection(fc, protocol.Hello{})
 
- 	m.ClusterConfig(device1, protocol.ClusterConfig{
 
- 		Folders: []protocol.Folder{
 
- 			{
 
- 				ID: "default",
 
- 				Devices: []protocol.Device{
 
- 					{
 
- 						ID:                      myID,
 
- 						EncryptionPasswordToken: encToken,
 
- 					},
 
- 					{ID: device1},
 
- 				},
 
- 			},
 
- 		},
 
- 	})
 
- 	select {
 
- 	case fs := <-indexChan:
 
- 		if len(fs) != 1 {
 
- 			t.Error("Expected index with one file, got", fs)
 
- 		}
 
- 		if got := fs[0].Name; got != files[0].Name {
 
- 			t.Errorf("Expected file %v, got %v", got, files[0].Name)
 
- 		}
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatal("timed out before receiving index")
 
- 	}
 
- 	// Detects deletion, as we never really created the file on disk
 
- 	// Shouldn't send anything because receive-encrypted
 
- 	must(t, m.ScanFolder(fcfg.ID))
 
- 	// One real file to be sent
 
- 	name := "foo"
 
- 	data := make([]byte, 2000)
 
- 	rand.Read(data)
 
- 	fc.addFile(name, 0664, protocol.FileInfoTypeFile, data)
 
- 	fc.sendIndexUpdate()
 
- 	select {
 
- 	case fs := <-indexChan:
 
- 		if len(fs) != 1 {
 
- 			t.Error("Expected index with one file, got", fs)
 
- 		}
 
- 		if got := fs[0].Name; got != name {
 
- 			t.Errorf("Expected file %v, got %v", got, files[0].Name)
 
- 		}
 
- 	case <-time.After(5 * time.Second):
 
- 		t.Fatal("timed out before receiving index")
 
- 	}
 
- 	// Simulate request from device that is untrusted too, i.e. with non-empty, but garbage hash
 
- 	_, err := m.Request(device1, fcfg.ID, name, 0, 1064, 0, []byte("garbage"), 0, false)
 
- 	must(t, err)
 
- 	changed, err := m.LocalChangedFolderFiles(fcfg.ID, 1, 10)
 
- 	must(t, err)
 
- 	if l := len(changed); l != 1 {
 
- 		t.Errorf("Expected one locally changed file, got %v", l)
 
- 	} else if changed[0].Name != files[0].Name {
 
- 		t.Errorf("Expected %v, got %v", files[0].Name, changed[0].Name)
 
- 	}
 
- }
 
- func TestRequestGlobalInvalidToValid(t *testing.T) {
 
- 	done := make(chan struct{})
 
- 	defer close(done)
 
- 	m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
 
- 	defer wcfgCancel()
 
- 	fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{DeviceID: device2})
 
- 	waiter, err := m.cfg.Modify(func(cfg *config.Configuration) {
 
- 		cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device2, "device2"))
 
- 		fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{DeviceID: device2})
 
- 		cfg.SetFolder(fcfg)
 
- 	})
 
- 	must(t, err)
 
- 	waiter.Wait()
 
- 	addFakeConn(m, device2, fcfg.ID)
 
- 	tfs := fcfg.Filesystem(nil)
 
- 	defer cleanupModelAndRemoveDir(m, tfs.URI())
 
- 	indexChan := make(chan []protocol.FileInfo, 1)
 
- 	fc.setIndexFn(func(ctx context.Context, folder string, fs []protocol.FileInfo) error {
 
- 		select {
 
- 		case indexChan <- fs:
 
- 		case <-done:
 
- 		case <-ctx.Done():
 
- 		}
 
- 		return nil
 
- 	})
 
- 	name := "foo"
 
- 	// Setup device with valid file, do not send index yet
 
- 	contents := []byte("test file contents\n")
 
- 	fc.addFile(name, 0644, protocol.FileInfoTypeFile, contents)
 
- 	// Third device ignoring the same file
 
- 	fc.mut.Lock()
 
- 	file := fc.files[0]
 
- 	fc.mut.Unlock()
 
- 	file.SetIgnored()
 
- 	m.IndexUpdate(device2, fcfg.ID, []protocol.FileInfo{prepareFileInfoForIndex(file)})
 
- 	// Wait for the ignored file to be received and possible pulled
 
- 	timeout := time.After(10 * time.Second)
 
- 	globalUpdated := false
 
- 	for {
 
- 		select {
 
- 		case <-timeout:
 
- 			t.Fatalf("timed out (globalUpdated == %v)", globalUpdated)
 
- 		default:
 
- 			time.Sleep(10 * time.Millisecond)
 
- 		}
 
- 		if !globalUpdated {
 
- 			_, ok, err := m.CurrentGlobalFile(fcfg.ID, name)
 
- 			if err != nil {
 
- 				t.Fatal(err)
 
- 			}
 
- 			if !ok {
 
- 				continue
 
- 			}
 
- 			globalUpdated = true
 
- 		}
 
- 		snap, err := m.DBSnapshot(fcfg.ID)
 
- 		if err != nil {
 
- 			t.Fatal(err)
 
- 		}
 
- 		need := snap.NeedSize(protocol.LocalDeviceID)
 
- 		snap.Release()
 
- 		if need.Files == 0 {
 
- 			break
 
- 		}
 
- 	}
 
- 	// Send the valid file
 
- 	fc.sendIndexUpdate()
 
- 	gotInvalid := false
 
- 	for {
 
- 		select {
 
- 		case <-timeout:
 
- 			t.Fatal("timed out before receiving index")
 
- 		case fs := <-indexChan:
 
- 			if len(fs) != 1 {
 
- 				t.Fatalf("Expected one file in index, got %v", len(fs))
 
- 			}
 
- 			if !fs[0].IsInvalid() {
 
- 				return
 
- 			}
 
- 			if gotInvalid {
 
- 				t.Fatal("Received two invalid index updates")
 
- 			}
 
- 			t.Log("got index with invalid file")
 
- 			gotInvalid = true
 
- 		}
 
- 	}
 
- }
 
 
  |