Browse Source

lib/model: Cleanup redundant filesystem variables in folders (#7237)

Simon Frei 4 years ago
parent
commit
a05dc6cc47

+ 9 - 8
lib/model/folder.go

@@ -45,6 +45,7 @@ type folder struct {
 	shortID       protocol.ShortID
 	fset          *db.FileSet
 	ignores       *ignore.Matcher
+	mtimefs       fs.Filesystem
 	modTimeWindow time.Duration
 	ctx           context.Context // used internally, only accessible on serve lifetime
 	done          chan struct{}   // used externally, accessible regardless of serve
@@ -100,6 +101,7 @@ func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg conf
 		shortID:       model.shortID,
 		fset:          fset,
 		ignores:       ignores,
+		mtimefs:       fset.MtimeFS(),
 		modTimeWindow: cfg.ModTimeWindow(),
 		done:          make(chan struct{}),
 
@@ -457,7 +459,6 @@ func (f *folder) scanSubdirs(subDirs []string) error {
 	// to be cancelled.
 	scanCtx, scanCancel := context.WithCancel(f.ctx)
 	defer scanCancel()
-	mtimefs := f.fset.MtimeFS()
 
 	scanConfig := scanner.Config{
 		Folder:                f.ID,
@@ -465,7 +466,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
 		Matcher:               f.ignores,
 		TempLifetime:          time.Duration(f.model.cfg.Options().KeepTemporariesH) * time.Hour,
 		CurrentFiler:          cFiler{snap},
-		Filesystem:            mtimefs,
+		Filesystem:            f.mtimefs,
 		IgnorePerms:           f.IgnorePerms,
 		AutoNormalize:         f.AutoNormalize,
 		Hashers:               f.model.numHashers(f.ID),
@@ -530,8 +531,8 @@ func (f *folder) scanSubdirs(subDirs []string) error {
 			// We don't track it, but check if anything still exists
 			// within and delete it otherwise.
 			if fi.IsDirectory() && protocol.IsEncryptedParent(fi.Name) {
-				if names, err := mtimefs.DirNames(fi.Name); err == nil && len(names) == 0 {
-					mtimefs.Remove(fi.Name)
+				if names, err := f.mtimefs.DirNames(fi.Name); err == nil && len(names) == 0 {
+					f.mtimefs.Remove(fi.Name)
 				}
 				changes--
 				return
@@ -570,7 +571,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
 		switch f.Type {
 		case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
 		default:
-			if nf, ok := f.findRename(snap, mtimefs, res.File, alreadyUsed); ok {
+			if nf, ok := f.findRename(snap, res.File, alreadyUsed); ok {
 				batchAppend(nf, snap)
 				changes++
 			}
@@ -658,7 +659,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
 				// it's still here. Simply stat:ing it wont do as there are
 				// tons of corner cases (e.g. parent dir->symlink, missing
 				// permissions)
-				if !osutil.IsDeleted(mtimefs, file.Name) {
+				if !osutil.IsDeleted(f.mtimefs, file.Name) {
 					if ignoredParent != "" {
 						// Don't ignore parents of this not ignored item
 						toIgnore = toIgnore[:0]
@@ -727,7 +728,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
 	return nil
 }
 
-func (f *folder) findRename(snap *db.Snapshot, mtimefs fs.Filesystem, file protocol.FileInfo, alreadyUsed map[string]struct{}) (protocol.FileInfo, bool) {
+func (f *folder) findRename(snap *db.Snapshot, file protocol.FileInfo, alreadyUsed map[string]struct{}) (protocol.FileInfo, bool) {
 	if len(file.Blocks) == 0 || file.Size == 0 {
 		return protocol.FileInfo{}, false
 	}
@@ -763,7 +764,7 @@ func (f *folder) findRename(snap *db.Snapshot, mtimefs fs.Filesystem, file proto
 			return true
 		}
 
-		if !osutil.IsDeleted(mtimefs, fi.Name) {
+		if !osutil.IsDeleted(f.mtimefs, fi.Name) {
 			return true
 		}
 

+ 3 - 3
lib/model/folder_recvenc.go

@@ -27,8 +27,8 @@ type receiveEncryptedFolder struct {
 	*sendReceiveFolder
 }
 
-func newReceiveEncryptedFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem, evLogger events.Logger, ioLimiter *byteSemaphore) service {
-	return &receiveEncryptedFolder{newSendReceiveFolder(model, fset, ignores, cfg, ver, fs, evLogger, ioLimiter).(*sendReceiveFolder)}
+func newReceiveEncryptedFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *byteSemaphore) service {
+	return &receiveEncryptedFolder{newSendReceiveFolder(model, fset, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder)}
 }
 
 func (f *receiveEncryptedFolder) Revert() {
@@ -65,7 +65,7 @@ func (f *receiveEncryptedFolder) revert() {
 			return true
 		}
 
-		if err := f.inWritableDir(f.fs.Remove, fit.Name); err != nil && !fs.IsNotExist(err) {
+		if err := f.inWritableDir(f.mtimefs.Remove, fit.Name); err != nil && !fs.IsNotExist(err) {
 			f.newScanError(fit.Name, fmt.Errorf("deleting unexpected item: %w", err))
 		}
 

+ 2 - 3
lib/model/folder_recvonly.go

@@ -13,7 +13,6 @@ import (
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/db"
 	"github.com/syncthing/syncthing/lib/events"
-	"github.com/syncthing/syncthing/lib/fs"
 	"github.com/syncthing/syncthing/lib/ignore"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/syncthing/syncthing/lib/versioner"
@@ -57,8 +56,8 @@ type receiveOnlyFolder struct {
 	*sendReceiveFolder
 }
 
-func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem, evLogger events.Logger, ioLimiter *byteSemaphore) service {
-	sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, fs, evLogger, ioLimiter).(*sendReceiveFolder)
+func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *byteSemaphore) service {
+	sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder)
 	sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files
 	return &receiveOnlyFolder{sr}
 }

+ 1 - 2
lib/model/folder_sendonly.go

@@ -10,7 +10,6 @@ import (
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/db"
 	"github.com/syncthing/syncthing/lib/events"
-	"github.com/syncthing/syncthing/lib/fs"
 	"github.com/syncthing/syncthing/lib/ignore"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/syncthing/syncthing/lib/versioner"
@@ -24,7 +23,7 @@ type sendOnlyFolder struct {
 	folder
 }
 
-func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem, evLogger events.Logger, ioLimiter *byteSemaphore) service {
+func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, evLogger events.Logger, ioLimiter *byteSemaphore) service {
 	f := &sendOnlyFolder{
 		folder: newFolder(model, fset, ignores, cfg, evLogger, ioLimiter, nil),
 	}

+ 49 - 52
lib/model/folder_sendrecv.go

@@ -121,8 +121,6 @@ type dbUpdateJob struct {
 type sendReceiveFolder struct {
 	folder
 
-	fs fs.Filesystem
-
 	queue              *jobQueue
 	blockPullReorderer blockPullReorderer
 	writeLimiter       *byteSemaphore
@@ -130,10 +128,9 @@ type sendReceiveFolder struct {
 	tempPullErrors map[string]string // pull errors that might be just transient
 }
 
-func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem, evLogger events.Logger, ioLimiter *byteSemaphore) service {
+func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *byteSemaphore) service {
 	f := &sendReceiveFolder{
 		folder:             newFolder(model, fset, ignores, cfg, evLogger, ioLimiter, ver),
-		fs:                 fs,
 		queue:              newJobQueue(),
 		blockPullReorderer: newBlockPullReorderer(cfg.BlockPullOrder, model.id, cfg.DeviceIDs()),
 		writeLimiter:       newByteSemaphore(cfg.MaxConcurrentWrites),
@@ -578,7 +575,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot,
 		l.Debugf("need dir\n\t%v\n\t%v", file, curFile)
 	}
 
-	info, err := f.fs.Lstat(file.Name)
+	info, err := f.mtimefs.Lstat(file.Name)
 	switch {
 	// There is already something under that name, we need to handle that.
 	// Unless it already is a directory, as we only track permissions,
@@ -617,7 +614,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot,
 		// we can pass it to InWritableDir. We use a regular Mkdir and
 		// not MkdirAll because the parent should already exist.
 		mkdir := func(path string) error {
-			err = f.fs.Mkdir(path, mode)
+			err = f.mtimefs.Mkdir(path, mode)
 			if err != nil || f.IgnorePerms || file.NoPermissions {
 				return err
 			}
@@ -628,14 +625,14 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot,
 			}
 
 			// Stat the directory so we can check its permissions.
-			info, err := f.fs.Lstat(path)
+			info, err := f.mtimefs.Lstat(path)
 			if err != nil {
 				return err
 			}
 
 			// Mask for the bits we want to preserve and add them in to the
 			// directories permissions.
-			return f.fs.Chmod(path, mode|(info.Mode()&retainBits))
+			return f.mtimefs.Chmod(path, mode|(info.Mode()&retainBits))
 		}
 
 		if err = f.inWritableDir(mkdir, file.Name); err == nil {
@@ -655,7 +652,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot,
 	// don't handle modification times on directories, because that sucks...)
 	// It's OK to change mode bits on stuff within non-writable directories.
 	if !f.IgnorePerms && !file.NoPermissions {
-		if err := f.fs.Chmod(file.Name, mode|(info.Mode()&retainBits)); err != nil {
+		if err := f.mtimefs.Chmod(file.Name, mode|(info.Mode()&retainBits)); err != nil {
 			f.newPullError(file.Name, err)
 			return
 		}
@@ -668,7 +665,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot,
 func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) bool {
 	parent := filepath.Dir(file)
 
-	if err := osutil.TraversesSymlink(f.fs, parent); err != nil {
+	if err := osutil.TraversesSymlink(f.mtimefs, parent); err != nil {
 		f.newPullError(file, errors.Wrap(err, "checking parent dirs"))
 		return false
 	}
@@ -688,12 +685,12 @@ func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) boo
 	// And if this is an encrypted folder:
 	// Encrypted files have made-up filenames with two synthetic parent
 	// directories which don't have any meaning. Create those if necessary.
-	if _, err := f.fs.Lstat(parent); !fs.IsNotExist(err) {
+	if _, err := f.mtimefs.Lstat(parent); !fs.IsNotExist(err) {
 		l.Debugf("%v parent not missing %v", f, file)
 		return true
 	}
 	l.Debugf("%v creating parent directory of %v", f, file)
-	if err := f.fs.MkdirAll(parent, 0755); err != nil {
+	if err := f.mtimefs.MkdirAll(parent, 0755); err != nil {
 		f.newPullError(file, errors.Wrap(err, "creating parent dir"))
 		return false
 	}
@@ -746,7 +743,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, snap *db.Snaps
 	// We declare a function that acts on only the path name, so
 	// we can pass it to InWritableDir.
 	createLink := func(path string) error {
-		if err := f.fs.CreateSymlink(file.SymlinkTarget, path); err != nil {
+		if err := f.mtimefs.CreateSymlink(file.SymlinkTarget, path); err != nil {
 			return err
 		}
 		return f.maybeCopyOwner(path)
@@ -761,7 +758,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, snap *db.Snaps
 
 func (f *sendReceiveFolder) handleSymlinkCheckExisting(file protocol.FileInfo, snap *db.Snapshot, scanChan chan<- string) error {
 	// If there is already something under that name, we need to handle that.
-	info, err := f.fs.Lstat(file.Name)
+	info, err := f.mtimefs.Lstat(file.Name)
 	if err != nil {
 		if fs.IsNotExist(err) {
 			return nil
@@ -892,7 +889,7 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h
 	if f.versioner != nil && !cur.IsSymlink() {
 		err = f.inWritableDir(f.versioner.Archive, file.Name)
 	} else {
-		err = f.inWritableDir(f.fs.Remove, file.Name)
+		err = f.inWritableDir(f.mtimefs.Remove, file.Name)
 	}
 
 	if err == nil || fs.IsNotExist(err) {
@@ -901,7 +898,7 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h
 		return
 	}
 
-	if _, serr := f.fs.Lstat(file.Name); serr != nil && !fs.IsPermission(serr) {
+	if _, serr := f.mtimefs.Lstat(file.Name); serr != nil && !fs.IsPermission(serr) {
 		// We get an error just looking at the file, and it's not a permission
 		// problem. Lets assume the error is in fact some variant of "file
 		// does not exist" (possibly expressed as some parent being a file and
@@ -956,7 +953,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, sn
 	}
 	// Check that the target corresponds to what we have in the DB
 	curTarget, ok := snap.Get(protocol.LocalDeviceID, target.Name)
-	switch stat, serr := f.fs.Lstat(target.Name); {
+	switch stat, serr := f.mtimefs.Lstat(target.Name); {
 	case serr != nil:
 		var caseErr *fs.ErrCaseConflict
 		switch {
@@ -983,7 +980,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, sn
 		err = errModified
 	default:
 		var fi protocol.FileInfo
-		if fi, err = scanner.CreateFileInfo(stat, target.Name, f.fs); err == nil {
+		if fi, err = scanner.CreateFileInfo(stat, target.Name, f.mtimefs); err == nil {
 			if !fi.IsEquivalentOptional(curTarget, f.modTimeWindow, f.IgnorePerms, true, protocol.LocalAllFlags) {
 				// Target changed
 				scanChan <- target.Name
@@ -1000,13 +997,13 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, sn
 	if f.versioner != nil {
 		err = f.CheckAvailableSpace(uint64(source.Size))
 		if err == nil {
-			err = osutil.Copy(f.CopyRangeMethod, f.fs, f.fs, source.Name, tempName)
+			err = osutil.Copy(f.CopyRangeMethod, f.mtimefs, f.mtimefs, source.Name, tempName)
 			if err == nil {
 				err = f.inWritableDir(f.versioner.Archive, source.Name)
 			}
 		}
 	} else {
-		err = osutil.RenameOrCopy(f.CopyRangeMethod, f.fs, f.fs, source.Name, tempName)
+		err = osutil.RenameOrCopy(f.CopyRangeMethod, f.mtimefs, f.mtimefs, source.Name, tempName)
 	}
 	if err != nil {
 		return err
@@ -1081,12 +1078,12 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, snap *db.Snapshot
 
 	// Check for an old temporary file which might have some blocks we could
 	// reuse.
-	tempBlocks, err := scanner.HashFile(f.ctx, f.fs, tempName, file.BlockSize(), nil, false)
+	tempBlocks, err := scanner.HashFile(f.ctx, f.mtimefs, tempName, file.BlockSize(), nil, false)
 	if err != nil {
 		var caseErr *fs.ErrCaseConflict
 		if errors.As(err, &caseErr) {
-			if rerr := f.fs.Rename(caseErr.Real, tempName); rerr == nil {
-				tempBlocks, err = scanner.HashFile(f.ctx, f.fs, tempName, file.BlockSize(), nil, false)
+			if rerr := f.mtimefs.Rename(caseErr.Real, tempName); rerr == nil {
+				tempBlocks, err = scanner.HashFile(f.ctx, f.mtimefs, tempName, file.BlockSize(), nil, false)
 			}
 		}
 	}
@@ -1116,7 +1113,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, snap *db.Snapshot
 			// Otherwise, discard the file ourselves in order for the
 			// sharedpuller not to panic when it fails to exclusively create a
 			// file which already exists
-			f.inWritableDir(f.fs.Remove, tempName)
+			f.inWritableDir(f.mtimefs.Remove, tempName)
 		}
 	} else {
 		// Copy the blocks, as we don't want to shuffle them on the FileInfo
@@ -1133,7 +1130,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, snap *db.Snapshot
 		"action": "update",
 	})
 
-	s := newSharedPullerState(file, f.fs, f.folderID, tempName, blocks, reused, f.IgnorePerms || file.NoPermissions, hasCurFile, curFile, !f.DisableSparseFiles, !f.DisableFsync)
+	s := newSharedPullerState(file, f.mtimefs, f.folderID, tempName, blocks, reused, f.IgnorePerms || file.NoPermissions, hasCurFile, curFile, !f.DisableSparseFiles, !f.DisableFsync)
 
 	l.Debugf("%v need file %s; copy %d, reused %v", f, file.Name, len(blocks), len(reused))
 
@@ -1208,13 +1205,13 @@ func (f *sendReceiveFolder) shortcutFile(file, curFile protocol.FileInfo, dbUpda
 	f.queue.Done(file.Name)
 
 	if !f.IgnorePerms && !file.NoPermissions {
-		if err = f.fs.Chmod(file.Name, fs.FileMode(file.Permissions&0777)); err != nil {
+		if err = f.mtimefs.Chmod(file.Name, fs.FileMode(file.Permissions&0777)); err != nil {
 			f.newPullError(file.Name, err)
 			return
 		}
 	}
 
-	f.fs.Chtimes(file.Name, file.ModTime(), file.ModTime()) // never fails
+	f.mtimefs.Chtimes(file.Name, file.ModTime(), file.ModTime()) // never fails
 
 	dbUpdateChan <- dbUpdateJob{file, dbUpdateShortcutFile}
 }
@@ -1403,7 +1400,7 @@ func (f *sendReceiveFolder) initWeakHashFinder(state copyBlocksState) (*weakhash
 		return nil, nil
 	}
 
-	file, err := f.fs.Open(state.file.Name)
+	file, err := f.mtimefs.Open(state.file.Name)
 	if err != nil {
 		l.Debugln("weak hasher", err)
 		return nil, nil
@@ -1551,7 +1548,7 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
 func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCurFile bool, tempName string, snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error {
 	// Set the correct permission bits on the new file
 	if !f.IgnorePerms && !file.NoPermissions {
-		if err := f.fs.Chmod(tempName, fs.FileMode(file.Permissions&0777)); err != nil {
+		if err := f.mtimefs.Chmod(tempName, fs.FileMode(file.Permissions&0777)); err != nil {
 			return err
 		}
 	}
@@ -1561,7 +1558,7 @@ func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCu
 		return err
 	}
 
-	if stat, err := f.fs.Lstat(file.Name); err == nil {
+	if stat, err := f.mtimefs.Lstat(file.Name); err == nil {
 		// There is an old file or directory already in place. We need to
 		// handle that.
 
@@ -1590,12 +1587,12 @@ func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCu
 
 	// Replace the original content with the new one. If it didn't work,
 	// leave the temp file in place for reuse.
-	if err := osutil.RenameOrCopy(f.CopyRangeMethod, f.fs, f.fs, tempName, file.Name); err != nil {
+	if err := osutil.RenameOrCopy(f.CopyRangeMethod, f.mtimefs, f.mtimefs, tempName, file.Name); err != nil {
 		return err
 	}
 
 	// Set the correct timestamp on the new file
-	f.fs.Chtimes(file.Name, file.ModTime(), file.ModTime()) // never fails
+	f.mtimefs.Chtimes(file.Name, file.ModTime(), file.ModTime()) // never fails
 
 	// Record the updated file in the index
 	dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleFile}
@@ -1671,7 +1668,7 @@ func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) {
 		for dir := range changedDirs {
 			delete(changedDirs, dir)
 			if !f.FolderConfiguration.DisableFsync {
-				fd, err := f.fs.Open(dir)
+				fd, err := f.mtimefs.Open(dir)
 				if err != nil {
 					l.Debugf("fsync %q failed: %v", dir, err)
 					continue
@@ -1786,21 +1783,21 @@ func removeAvailability(availabilities []Availability, availability Availability
 func (f *sendReceiveFolder) moveForConflict(name, lastModBy string, scanChan chan<- string) error {
 	if isConflict(name) {
 		l.Infoln("Conflict for", name, "which is already a conflict copy; not copying again.")
-		if err := f.fs.Remove(name); err != nil && !fs.IsNotExist(err) {
+		if err := f.mtimefs.Remove(name); err != nil && !fs.IsNotExist(err) {
 			return errors.Wrap(err, contextRemovingOldItem)
 		}
 		return nil
 	}
 
 	if f.MaxConflicts == 0 {
-		if err := f.fs.Remove(name); err != nil && !fs.IsNotExist(err) {
+		if err := f.mtimefs.Remove(name); err != nil && !fs.IsNotExist(err) {
 			return errors.Wrap(err, contextRemovingOldItem)
 		}
 		return nil
 	}
 
 	newName := conflictName(name, lastModBy)
-	err := f.fs.Rename(name, newName)
+	err := f.mtimefs.Rename(name, newName)
 	if fs.IsNotExist(err) {
 		// We were supposed to move a file away but it does not exist. Either
 		// the user has already moved it away, or the conflict was between a
@@ -1809,11 +1806,11 @@ func (f *sendReceiveFolder) moveForConflict(name, lastModBy string, scanChan cha
 		err = nil
 	}
 	if f.MaxConflicts > -1 {
-		matches := existingConflicts(name, f.fs)
+		matches := existingConflicts(name, f.mtimefs)
 		if len(matches) > f.MaxConflicts {
 			sort.Sort(sort.Reverse(sort.StringSlice(matches)))
 			for _, match := range matches[f.MaxConflicts:] {
-				if gerr := f.fs.Remove(match); gerr != nil {
+				if gerr := f.mtimefs.Remove(match); gerr != nil {
 					l.Debugln(f, "removing extra conflict", gerr)
 				}
 			}
@@ -1871,17 +1868,17 @@ func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, snap *db.Sn
 		return f.inWritableDir(f.versioner.Archive, item.Name)
 	}
 
-	return f.inWritableDir(f.fs.Remove, item.Name)
+	return f.inWritableDir(f.mtimefs.Remove, item.Name)
 }
 
 // deleteDirOnDisk attempts to delete a directory. It checks for files/dirs inside
 // the directory and removes them if possible or returns an error if it fails
 func (f *sendReceiveFolder) deleteDirOnDisk(dir string, snap *db.Snapshot, scanChan chan<- string) error {
-	if err := osutil.TraversesSymlink(f.fs, filepath.Dir(dir)); err != nil {
+	if err := osutil.TraversesSymlink(f.mtimefs, filepath.Dir(dir)); err != nil {
 		return err
 	}
 
-	files, _ := f.fs.DirNames(dir)
+	files, _ := f.mtimefs.DirNames(dir)
 
 	toBeDeleted := make([]string, 0, len(files))
 
@@ -1910,10 +1907,10 @@ func (f *sendReceiveFolder) deleteDirOnDisk(dir string, snap *db.Snapshot, scanC
 			hasReceiveOnlyChanged = true
 			continue
 		}
-		info, err := f.fs.Lstat(fullDirFile)
+		info, err := f.mtimefs.Lstat(fullDirFile)
 		var diskFile protocol.FileInfo
 		if err == nil {
-			diskFile, err = scanner.CreateFileInfo(info, fullDirFile, f.fs)
+			diskFile, err = scanner.CreateFileInfo(info, fullDirFile, f.mtimefs)
 		}
 		if err != nil {
 			// Lets just assume the file has changed.
@@ -1950,15 +1947,15 @@ func (f *sendReceiveFolder) deleteDirOnDisk(dir string, snap *db.Snapshot, scanC
 	}
 
 	for _, del := range toBeDeleted {
-		f.fs.RemoveAll(del)
+		f.mtimefs.RemoveAll(del)
 	}
 
-	err := f.inWritableDir(f.fs.Remove, dir)
+	err := f.inWritableDir(f.mtimefs.Remove, dir)
 	if err == nil || fs.IsNotExist(err) {
 		// It was removed or it doesn't exist to start with
 		return nil
 	}
-	if _, serr := f.fs.Lstat(dir); serr != nil && !fs.IsPermission(serr) {
+	if _, serr := f.mtimefs.Lstat(dir); serr != nil && !fs.IsPermission(serr) {
 		// We get an error just looking at the directory, and it's not a
 		// permission problem. Lets assume the error is in fact some variant
 		// of "file does not exist" (possibly expressed as some parent being a
@@ -1988,7 +1985,7 @@ func (f *sendReceiveFolder) scanIfItemChanged(name string, stat fs.FileInfo, ite
 	// to the database. If there's a mismatch here, there might be local
 	// changes that we don't know about yet and we should scan before
 	// touching the item.
-	statItem, err := scanner.CreateFileInfo(stat, item.Name, f.fs)
+	statItem, err := scanner.CreateFileInfo(stat, item.Name, f.mtimefs)
 	if err != nil {
 		return errors.Wrap(err, "comparing item on disk to db")
 	}
@@ -2004,12 +2001,12 @@ func (f *sendReceiveFolder) scanIfItemChanged(name string, stat fs.FileInfo, ite
 // in the DB before the caller proceeds with actually deleting it.
 // I.e. non-nil error status means "Do not delete!" or "is already deleted".
 func (f *sendReceiveFolder) checkToBeDeleted(file, cur protocol.FileInfo, hasCur bool, scanChan chan<- string) error {
-	if err := osutil.TraversesSymlink(f.fs, filepath.Dir(file.Name)); err != nil {
+	if err := osutil.TraversesSymlink(f.mtimefs, filepath.Dir(file.Name)); err != nil {
 		l.Debugln(f, "not deleting item behind symlink on disk, but update db", file.Name)
 		return fs.ErrNotExist
 	}
 
-	stat, err := f.fs.Lstat(file.Name)
+	stat, err := f.mtimefs.Lstat(file.Name)
 	deleted := fs.IsNotExist(err) || fs.IsErrCaseConflict(err)
 	if !deleted && err != nil {
 		return err
@@ -2036,18 +2033,18 @@ func (f *sendReceiveFolder) maybeCopyOwner(path string) error {
 		return nil
 	}
 
-	info, err := f.fs.Lstat(filepath.Dir(path))
+	info, err := f.mtimefs.Lstat(filepath.Dir(path))
 	if err != nil {
 		return errors.Wrap(err, "copy owner from parent")
 	}
-	if err := f.fs.Lchown(path, info.Owner(), info.Group()); err != nil {
+	if err := f.mtimefs.Lchown(path, info.Owner(), info.Group()); err != nil {
 		return errors.Wrap(err, "copy owner from parent")
 	}
 	return nil
 }
 
 func (f *sendReceiveFolder) inWritableDir(fn func(string) error, path string) error {
-	return inWritableDir(fn, f.fs, path, f.IgnorePerms)
+	return inWritableDir(fn, f.mtimefs, path, f.IgnorePerms)
 }
 
 func (f *sendReceiveFolder) limitedWriteAt(fd io.WriterAt, data []byte, offset int64) error {

+ 12 - 11
lib/model/folder_sendrecv_test.go

@@ -807,12 +807,13 @@ func TestCopyOwner(t *testing.T) {
 	f.folder.FolderConfiguration = config.NewFolderConfiguration(m.id, f.ID, f.Label, fs.FilesystemTypeFake, "/TestCopyOwner")
 	f.folder.FolderConfiguration.CopyOwnershipFromParent = true
 
-	f.fs = f.Filesystem()
+	f.fset = newFileSet(t, f.ID, f.Filesystem(), m.db)
+	f.mtimefs = f.fset.MtimeFS()
 
 	// Create a parent dir with a certain owner/group.
 
-	f.fs.Mkdir("foo", 0755)
-	f.fs.Lchown("foo", expOwner, expGroup)
+	f.mtimefs.Mkdir("foo", 0755)
+	f.mtimefs.Lchown("foo", expOwner, expGroup)
 
 	dir := protocol.FileInfo{
 		Name:        "foo/bar",
@@ -833,7 +834,7 @@ func TestCopyOwner(t *testing.T) {
 		t.Fatal("Unexpected receive on scanChan:", toScan)
 	}
 
-	info, err := f.fs.Lstat("foo/bar")
+	info, err := f.mtimefs.Lstat("foo/bar")
 	if err != nil {
 		t.Fatal("Unexpected error (dir):", err)
 	}
@@ -869,7 +870,7 @@ func TestCopyOwner(t *testing.T) {
 	f.handleFile(file, snap, copierChan)
 	<-dbUpdateChan
 
-	info, err = f.fs.Lstat("foo/bar/baz")
+	info, err = f.mtimefs.Lstat("foo/bar/baz")
 	if err != nil {
 		t.Fatal("Unexpected error (file):", err)
 	}
@@ -892,7 +893,7 @@ func TestCopyOwner(t *testing.T) {
 		t.Fatal("Unexpected receive on scanChan:", toScan)
 	}
 
-	info, err = f.fs.Lstat("foo/bar/sym")
+	info, err = f.mtimefs.Lstat("foo/bar/sym")
 	if err != nil {
 		t.Fatal("Unexpected error (file):", err)
 	}
@@ -1215,7 +1216,7 @@ func TestPullTempFileCaseConflict(t *testing.T) {
 	file := protocol.FileInfo{Name: "foo"}
 	confl := "Foo"
 	tempNameConfl := fs.TempName(confl)
-	if fd, err := f.fs.Create(tempNameConfl); err != nil {
+	if fd, err := f.mtimefs.Create(tempNameConfl); err != nil {
 		t.Fatal(err)
 	} else {
 		if _, err := fd.Write([]byte("data")); err != nil {
@@ -1239,7 +1240,7 @@ func TestPullCaseOnlyRename(t *testing.T) {
 	// tempNameConfl := fs.TempName(confl)
 
 	name := "foo"
-	if fd, err := f.fs.Create(name); err != nil {
+	if fd, err := f.mtimefs.Create(name); err != nil {
 		t.Fatal(err)
 	} else {
 		if _, err := fd.Write([]byte("data")); err != nil {
@@ -1280,7 +1281,7 @@ func TestPullSymlinkOverExistingWindows(t *testing.T) {
 	defer cleanupSRFolder(f, m)
 
 	name := "foo"
-	if fd, err := f.fs.Create(name); err != nil {
+	if fd, err := f.mtimefs.Create(name); err != nil {
 		t.Fatal(err)
 	} else {
 		if _, err := fd.Write([]byte("data")); err != nil {
@@ -1308,7 +1309,7 @@ func TestPullSymlinkOverExistingWindows(t *testing.T) {
 	} else if !file.IsUnsupported() {
 		t.Error("symlink entry isn't marked as unsupported")
 	}
-	if _, err := f.fs.Lstat(name); err == nil {
+	if _, err := f.mtimefs.Lstat(name); err == nil {
 		t.Error("old file still exists on disk")
 	} else if !fs.IsNotExist(err) {
 		t.Error(err)
@@ -1324,7 +1325,7 @@ func TestPullDeleteCaseConflict(t *testing.T) {
 	dbUpdateChan := make(chan dbUpdateJob, 1)
 	scanChan := make(chan string)
 
-	if fd, err := f.fs.Create(name); err != nil {
+	if fd, err := f.mtimefs.Create(name); err != nil {
 		t.Fatal(err)
 	} else {
 		if _, err := fd.Write([]byte("data")); err != nil {

+ 3 - 4
lib/model/model.go

@@ -164,7 +164,7 @@ type model struct {
 	started        chan struct{}
 }
 
-type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, fs.Filesystem, events.Logger, *byteSemaphore) service
+type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, events.Logger, *byteSemaphore) service
 
 var (
 	folderFactories = make(map[config.FolderType]folderFactory)
@@ -384,8 +384,6 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio
 		}
 	}
 
-	ffs := fset.MtimeFS()
-
 	if cfg.Type == config.FolderTypeReceiveEncrypted {
 		if encryptionToken, err := readEncryptionToken(cfg); err == nil {
 			m.folderEncryptionPasswordTokens[folder] = encryptionToken
@@ -395,6 +393,7 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio
 	}
 
 	// These are our metadata files, and they should always be hidden.
+	ffs := cfg.Filesystem()
 	_ = ffs.Hide(config.DefaultMarkerName)
 	_ = ffs.Hide(".stversions")
 	_ = ffs.Hide(".stignore")
@@ -409,7 +408,7 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio
 	}
 	m.folderVersioners[folder] = ver
 
-	p := folderFactory(m, fset, ignores, cfg, ver, ffs, m.evLogger, m.folderIOLimiter)
+	p := folderFactory(m, fset, ignores, cfg, ver, m.evLogger, m.folderIOLimiter)
 
 	m.folderRunners[folder] = p