| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 | 
							- // Copyright (C) 2018 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"
 
- 	"path/filepath"
 
- 	"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/scanner"
 
- )
 
- func TestRecvOnlyRevertDeletes(t *testing.T) {
 
- 	// Make sure that we delete extraneous files and directories when we hit
 
- 	// Revert.
 
- 	// Get us a model up and running
 
- 	m, f, wcfgCancel := setupROFolder(t)
 
- 	defer wcfgCancel()
 
- 	ffs := f.Filesystem(nil)
 
- 	defer cleanupModel(m)
 
- 	addFakeConn(m, device1, f.ID)
 
- 	// Create some test data
 
- 	for _, dir := range []string{".stfolder", "ignDir", "unknownDir"} {
 
- 		must(t, ffs.MkdirAll(dir, 0755))
 
- 	}
 
- 	writeFilePerm(t, ffs, "ignDir/ignFile", []byte("hello\n"), 0644)
 
- 	writeFilePerm(t, ffs, "unknownDir/unknownFile", []byte("hello\n"), 0644)
 
- 	writeFilePerm(t, ffs, ".stignore", []byte("ignDir\n"), 0644)
 
- 	knownFiles := setupKnownFiles(t, ffs, []byte("hello\n"))
 
- 	// Send and index update for the known stuff
 
- 	must(t, m.Index(device1, "ro", knownFiles))
 
- 	f.updateLocalsFromScanning(knownFiles)
 
- 	size := globalSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
 
- 	}
 
- 	// Scan, should discover the other stuff in the folder
 
- 	must(t, m.ScanFolder("ro"))
 
- 	// We should now have two files and two directories, with global state unchanged.
 
- 	size = globalSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Global: expected 2 files and 2 directories: %+v", size)
 
- 	}
 
- 	size = localSize(t, m, "ro")
 
- 	if size.Files != 2 || size.Directories != 2 {
 
- 		t.Fatalf("Local: expected 2 files and 2 directories: %+v", size)
 
- 	}
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Files+size.Directories == 0 {
 
- 		t.Fatalf("ROChanged: expected something: %+v", size)
 
- 	}
 
- 	// Revert should delete the unknown stuff
 
- 	m.Revert("ro")
 
- 	// These should still exist
 
- 	for _, p := range []string{"knownDir/knownFile", "ignDir/ignFile"} {
 
- 		if _, err := ffs.Stat(p); err != nil {
 
- 			t.Error("Unexpected error:", err)
 
- 		}
 
- 	}
 
- 	// These should have been removed
 
- 	for _, p := range []string{"unknownDir", "unknownDir/unknownFile"} {
 
- 		if _, err := ffs.Stat(p); !fs.IsNotExist(err) {
 
- 			t.Error("Unexpected existing thing:", p)
 
- 		}
 
- 	}
 
- 	// We should now have one file and directory again.
 
- 	size = globalSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Global: expected 1 files and 1 directories: %+v", size)
 
- 	}
 
- 	size = localSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Local: expected 1 files and 1 directories: %+v", size)
 
- 	}
 
- }
 
- func TestRecvOnlyRevertNeeds(t *testing.T) {
 
- 	// Make sure that a new file gets picked up and considered latest, then
 
- 	// gets considered old when we hit Revert.
 
- 	// Get us a model up and running
 
- 	m, f, wcfgCancel := setupROFolder(t)
 
- 	defer wcfgCancel()
 
- 	ffs := f.Filesystem(nil)
 
- 	defer cleanupModel(m)
 
- 	addFakeConn(m, device1, f.ID)
 
- 	// Create some test data
 
- 	must(t, ffs.MkdirAll(".stfolder", 0755))
 
- 	oldData := []byte("hello\n")
 
- 	knownFiles := setupKnownFiles(t, ffs, oldData)
 
- 	// Send and index update for the known stuff
 
- 	must(t, m.Index(device1, "ro", knownFiles))
 
- 	f.updateLocalsFromScanning(knownFiles)
 
- 	// Scan the folder.
 
- 	must(t, m.ScanFolder("ro"))
 
- 	// Everything should be in sync.
 
- 	size := globalSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
 
- 	}
 
- 	size = localSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
 
- 	}
 
- 	size = needSizeLocal(t, m, "ro")
 
- 	if size.Files+size.Directories > 0 {
 
- 		t.Fatalf("Need: expected nothing: %+v", size)
 
- 	}
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Files+size.Directories > 0 {
 
- 		t.Fatalf("ROChanged: expected nothing: %+v", size)
 
- 	}
 
- 	// Update the file.
 
- 	newData := []byte("totally different data\n")
 
- 	writeFilePerm(t, ffs, "knownDir/knownFile", newData, 0644)
 
- 	// Rescan.
 
- 	must(t, m.ScanFolder("ro"))
 
- 	// We now have a newer file than the rest of the cluster. Global state should reflect this.
 
- 	size = globalSize(t, m, "ro")
 
- 	const sizeOfDir = 128
 
- 	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
 
- 		t.Fatalf("Global: expected no change due to the new file: %+v", size)
 
- 	}
 
- 	size = localSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
 
- 		t.Fatalf("Local: expected the new file to be reflected: %+v", size)
 
- 	}
 
- 	size = needSizeLocal(t, m, "ro")
 
- 	if size.Files+size.Directories > 0 {
 
- 		t.Fatalf("Need: expected nothing: %+v", size)
 
- 	}
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Files+size.Directories == 0 {
 
- 		t.Fatalf("ROChanged: expected something: %+v", size)
 
- 	}
 
- 	// We hit the Revert button. The file that was new should become old.
 
- 	m.Revert("ro")
 
- 	size = globalSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
 
- 		t.Fatalf("Global: expected the global size to revert: %+v", size)
 
- 	}
 
- 	size = localSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
 
- 		t.Fatalf("Local: expected the local size to remain: %+v", size)
 
- 	}
 
- 	size = needSizeLocal(t, m, "ro")
 
- 	if size.Files != 1 || size.Bytes != int64(len(oldData)) {
 
- 		t.Fatalf("Local: expected to need the old file data: %+v", size)
 
- 	}
 
- }
 
- func TestRecvOnlyUndoChanges(t *testing.T) {
 
- 	// Get us a model up and running
 
- 	m, f, wcfgCancel := setupROFolder(t)
 
- 	defer wcfgCancel()
 
- 	ffs := f.Filesystem(nil)
 
- 	defer cleanupModel(m)
 
- 	addFakeConn(m, device1, f.ID)
 
- 	// Create some test data
 
- 	must(t, ffs.MkdirAll(".stfolder", 0755))
 
- 	oldData := []byte("hello\n")
 
- 	knownFiles := setupKnownFiles(t, ffs, oldData)
 
- 	// Send an index update for the known stuff
 
- 	must(t, m.Index(device1, "ro", knownFiles))
 
- 	f.updateLocalsFromScanning(knownFiles)
 
- 	// Scan the folder.
 
- 	must(t, m.ScanFolder("ro"))
 
- 	// Everything should be in sync.
 
- 	size := globalSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
 
- 	}
 
- 	size = localSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
 
- 	}
 
- 	size = needSizeLocal(t, m, "ro")
 
- 	if size.Files+size.Directories > 0 {
 
- 		t.Fatalf("Need: expected nothing: %+v", size)
 
- 	}
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Files+size.Directories > 0 {
 
- 		t.Fatalf("ROChanged: expected nothing: %+v", size)
 
- 	}
 
- 	// Create a file and modify another
 
- 	const file = "foo"
 
- 	writeFilePerm(t, ffs, file, []byte("hello\n"), 0644)
 
- 	writeFilePerm(t, ffs, "knownDir/knownFile", []byte("bye\n"), 0644)
 
- 	must(t, m.ScanFolder("ro"))
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Files != 2 {
 
- 		t.Fatalf("Receive only: expected 2 files: %+v", size)
 
- 	}
 
- 	// Remove the file again and undo the modification
 
- 	must(t, ffs.Remove(file))
 
- 	writeFilePerm(t, ffs, "knownDir/knownFile", oldData, 0644)
 
- 	must(t, ffs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime()))
 
- 	must(t, m.ScanFolder("ro"))
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Files+size.Directories+size.Deleted != 0 {
 
- 		t.Fatalf("Receive only: expected all zero: %+v", size)
 
- 	}
 
- }
 
- func TestRecvOnlyDeletedRemoteDrop(t *testing.T) {
 
- 	// Get us a model up and running
 
- 	m, f, wcfgCancel := setupROFolder(t)
 
- 	defer wcfgCancel()
 
- 	ffs := f.Filesystem(nil)
 
- 	defer cleanupModel(m)
 
- 	addFakeConn(m, device1, f.ID)
 
- 	// Create some test data
 
- 	must(t, ffs.MkdirAll(".stfolder", 0755))
 
- 	oldData := []byte("hello\n")
 
- 	knownFiles := setupKnownFiles(t, ffs, oldData)
 
- 	// Send an index update for the known stuff
 
- 	must(t, m.Index(device1, "ro", knownFiles))
 
- 	f.updateLocalsFromScanning(knownFiles)
 
- 	// Scan the folder.
 
- 	must(t, m.ScanFolder("ro"))
 
- 	// Everything should be in sync.
 
- 	size := globalSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
 
- 	}
 
- 	size = localSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
 
- 	}
 
- 	size = needSizeLocal(t, m, "ro")
 
- 	if size.Files+size.Directories > 0 {
 
- 		t.Fatalf("Need: expected nothing: %+v", size)
 
- 	}
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Files+size.Directories > 0 {
 
- 		t.Fatalf("ROChanged: expected nothing: %+v", size)
 
- 	}
 
- 	// Delete our file
 
- 	must(t, ffs.Remove(knownFiles[1].Name))
 
- 	must(t, m.ScanFolder("ro"))
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Deleted != 1 {
 
- 		t.Fatalf("Receive only: expected 1 deleted: %+v", size)
 
- 	}
 
- 	// Drop the remote
 
- 	f.fset.Drop(device1)
 
- 	must(t, m.ScanFolder("ro"))
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Deleted != 0 {
 
- 		t.Fatalf("Receive only: expected no deleted: %+v", size)
 
- 	}
 
- }
 
- func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
 
- 	// Get us a model up and running
 
- 	m, f, wcfgCancel := setupROFolder(t)
 
- 	defer wcfgCancel()
 
- 	ffs := f.Filesystem(nil)
 
- 	defer cleanupModel(m)
 
- 	addFakeConn(m, device1, f.ID)
 
- 	// Create some test data
 
- 	must(t, ffs.MkdirAll(".stfolder", 0755))
 
- 	oldData := []byte("hello\n")
 
- 	knownFiles := setupKnownFiles(t, ffs, oldData)
 
- 	// Send an index update for the known stuff
 
- 	must(t, m.Index(device1, "ro", knownFiles))
 
- 	f.updateLocalsFromScanning(knownFiles)
 
- 	// Scan the folder.
 
- 	must(t, m.ScanFolder("ro"))
 
- 	// Everything should be in sync.
 
- 	size := globalSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
 
- 	}
 
- 	size = localSize(t, m, "ro")
 
- 	if size.Files != 1 || size.Directories != 1 {
 
- 		t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
 
- 	}
 
- 	size = needSizeLocal(t, m, "ro")
 
- 	if size.Files+size.Directories > 0 {
 
- 		t.Fatalf("Need: expected nothing: %+v", size)
 
- 	}
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Files+size.Directories > 0 {
 
- 		t.Fatalf("ROChanged: expected nothing: %+v", size)
 
- 	}
 
- 	// Create a file and modify another
 
- 	const file = "foo"
 
- 	knownFile := filepath.Join("knownDir", "knownFile")
 
- 	writeFilePerm(t, ffs, file, []byte("hello\n"), 0644)
 
- 	writeFilePerm(t, ffs, knownFile, []byte("bye\n"), 0644)
 
- 	must(t, m.ScanFolder("ro"))
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Files != 2 {
 
- 		t.Fatalf("Receive only: expected 2 files: %+v", size)
 
- 	}
 
- 	// Do the same changes on the remote
 
- 	files := make([]protocol.FileInfo, 0, 2)
 
- 	snap := fsetSnapshot(t, f.fset)
 
- 	snap.WithHave(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
 
- 		if n := fi.FileName(); n != file && n != knownFile {
 
- 			return true
 
- 		}
 
- 		f := fi.(protocol.FileInfo)
 
- 		f.LocalFlags = 0
 
- 		f.Version = protocol.Vector{}.Update(device1.Short())
 
- 		files = append(files, f)
 
- 		return true
 
- 	})
 
- 	snap.Release()
 
- 	must(t, m.IndexUpdate(device1, "ro", files))
 
- 	// Ensure the pull to resolve conflicts (content identical) happened
 
- 	must(t, f.doInSync(func() error {
 
- 		f.pull()
 
- 		return nil
 
- 	}))
 
- 	size = receiveOnlyChangedSize(t, m, "ro")
 
- 	if size.Files+size.Directories+size.Deleted != 0 {
 
- 		t.Fatalf("Receive only: expected all zero: %+v", size)
 
- 	}
 
- }
 
- func TestRecvOnlyRevertOwnID(t *testing.T) {
 
- 	// If the folder was receive-only in the past, the global item might have
 
- 	// only our id in the version vector and be valid. There was a bug based on
 
- 	// the incorrect assumption that this can never happen.
 
- 	// Get us a model up and running
 
- 	m, f, wcfgCancel := setupROFolder(t)
 
- 	defer wcfgCancel()
 
- 	ffs := f.Filesystem(nil)
 
- 	defer cleanupModel(m)
 
- 	addFakeConn(m, device1, f.ID)
 
- 	// Create some test data
 
- 	must(t, ffs.MkdirAll(".stfolder", 0755))
 
- 	data := []byte("hello\n")
 
- 	name := "foo"
 
- 	writeFilePerm(t, ffs, name, data, 0644)
 
- 	// Make sure the file is scanned and locally changed
 
- 	must(t, m.ScanFolder("ro"))
 
- 	fi, ok := m.testCurrentFolderFile(f.ID, name)
 
- 	if !ok {
 
- 		t.Fatal("File missing")
 
- 	} else if !fi.IsReceiveOnlyChanged() {
 
- 		t.Fatal("File should be receiveonly changed")
 
- 	}
 
- 	fi.LocalFlags = 0
 
- 	v := fi.Version.Counters[0].Value
 
- 	fi.Version.Counters[0].Value = uint64(time.Unix(int64(v), 0).Add(-10 * time.Second).Unix())
 
- 	// Monitor the outcome
 
- 	sub := f.evLogger.Subscribe(events.LocalIndexUpdated)
 
- 	defer sub.Unsubscribe()
 
- 	ctx, cancel := context.WithCancel(context.Background())
 
- 	defer cancel()
 
- 	go func() {
 
- 		for {
 
- 			select {
 
- 			case <-ctx.Done():
 
- 				return
 
- 			case <-sub.C():
 
- 				if file, _ := m.testCurrentFolderFile(f.ID, name); file.Deleted {
 
- 					t.Error("local file was deleted")
 
- 					cancel()
 
- 				} else if file.IsEquivalent(fi, f.modTimeWindow) {
 
- 					cancel() // That's what we are waiting for
 
- 				}
 
- 			}
 
- 		}
 
- 	}()
 
- 	// Receive an index update with an older version, but valid and then revert
 
- 	must(t, m.Index(device1, f.ID, []protocol.FileInfo{fi}))
 
- 	f.Revert()
 
- 	select {
 
- 	case <-ctx.Done():
 
- 	case <-time.After(10 * time.Second):
 
- 		t.Fatal("timed out")
 
- 	}
 
- }
 
- func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.FileInfo {
 
- 	t.Helper()
 
- 	must(t, ffs.MkdirAll("knownDir", 0755))
 
- 	writeFilePerm(t, ffs, "knownDir/knownFile", data, 0644)
 
- 	t0 := time.Now().Add(-1 * time.Minute)
 
- 	must(t, ffs.Chtimes("knownDir/knownFile", t0, t0))
 
- 	fi, err := ffs.Stat("knownDir/knownFile")
 
- 	if err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), protocol.BlockSize(int64(len(data))), int64(len(data)), nil, true)
 
- 	knownFiles := []protocol.FileInfo{
 
- 		{
 
- 			Name:        "knownDir",
 
- 			Type:        protocol.FileInfoTypeDirectory,
 
- 			Permissions: 0755,
 
- 			Version:     protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
 
- 			Sequence:    42,
 
- 		},
 
- 		{
 
- 			Name:        "knownDir/knownFile",
 
- 			Type:        protocol.FileInfoTypeFile,
 
- 			Permissions: 0644,
 
- 			Size:        fi.Size(),
 
- 			ModifiedS:   fi.ModTime().Unix(),
 
- 			ModifiedNs:  int(fi.ModTime().UnixNano() % 1e9),
 
- 			Version:     protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
 
- 			Sequence:    42,
 
- 			Blocks:      blocks,
 
- 		},
 
- 	}
 
- 	return knownFiles
 
- }
 
- func setupROFolder(t *testing.T) (*testModel, *receiveOnlyFolder, context.CancelFunc) {
 
- 	t.Helper()
 
- 	w, cancel := createTmpWrapper(defaultCfg)
 
- 	cfg := w.RawCopy()
 
- 	fcfg := testFolderConfigFake()
 
- 	fcfg.ID = "ro"
 
- 	fcfg.Label = "ro"
 
- 	fcfg.Type = config.FolderTypeReceiveOnly
 
- 	cfg.Folders = []config.FolderConfiguration{fcfg}
 
- 	replace(t, w, cfg)
 
- 	m := newModel(t, w, myID, "syncthing", "dev", nil)
 
- 	m.ServeBackground()
 
- 	<-m.started
 
- 	must(t, m.ScanFolder("ro"))
 
- 	m.fmut.RLock()
 
- 	defer m.fmut.RUnlock()
 
- 	f := m.folderRunners["ro"].(*receiveOnlyFolder)
 
- 	return m, f, cancel
 
- }
 
 
  |