lib/fs: Properly handle case insensitive systems (fixes #1787, fixes #2739, fixes #5708)

With this change we emulate a case sensitive filesystem on top of
insensitive filesystems. This means we correctly pick up case-only renames
and throw a case conflict error when there would be multiple files differing
only in case.

This safety check has a small performance hit (about 20% more filesystem
operations when scanning for changes). The new advanced folder option
`caseSensitiveFS` can be used to disable the safety checks, retaining the
previous behavior on systems known to be fully case sensitive.

Co-authored-by: Jakob Borg <jakob@kastelo.net>
This commit is contained in:
Simon Frei
2020-07-28 11:15:11 +02:00
committed by Jakob Borg
co-authored by Jakob Borg
parent 21dd9d6b43
commit 932d8c69de
27 changed files with 1241 additions and 187 deletions
+22 -19
View File
@@ -129,9 +129,9 @@ type sendReceiveFolder struct {
blockPullReorderer blockPullReorderer
writeLimiter *byteSemaphore
pullErrors map[string]string // errors for most recent/current iteration
oldPullErrors map[string]string // errors from previous iterations for log filtering only
pullErrorsMut sync.Mutex
pullErrors map[string]string // actual exposed pull errors
tempPullErrors map[string]string // pull errors that might be just transient
pullErrorsMut sync.Mutex
}
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 {
@@ -192,6 +192,10 @@ func (f *sendReceiveFolder) pull() bool {
changed := 0
f.pullErrorsMut.Lock()
f.pullErrors = nil
f.pullErrorsMut.Unlock()
for tries := 0; tries < maxPullerIterations; tries++ {
select {
case <-f.ctx.Done():
@@ -216,8 +220,14 @@ func (f *sendReceiveFolder) pull() bool {
}
f.pullErrorsMut.Lock()
f.pullErrors = f.tempPullErrors
f.tempPullErrors = nil
for path, err := range f.pullErrors {
l.Infof("Puller (folder %s, item %q): %v", f.Description(), path, err)
}
pullErrNum := len(f.pullErrors)
f.pullErrorsMut.Unlock()
if pullErrNum > 0 {
l.Infof("%v: Failed to sync %v items", f.Description(), pullErrNum)
f.evLogger.Log(events.FolderErrors, map[string]interface{}{
@@ -235,8 +245,7 @@ func (f *sendReceiveFolder) pull() bool {
// flagged as needed in the folder.
func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) int {
f.pullErrorsMut.Lock()
f.oldPullErrors = f.pullErrors
f.pullErrors = make(map[string]string)
f.tempPullErrors = make(map[string]string)
f.pullErrorsMut.Unlock()
snap := f.fset.Snapshot()
@@ -306,10 +315,6 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) int {
close(dbUpdateChan)
updateWg.Wait()
f.pullErrorsMut.Lock()
f.oldPullErrors = nil
f.pullErrorsMut.Unlock()
f.queue.Reset()
return changed
@@ -739,7 +744,11 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, snap *db.Snaps
}
// There is already something under that name, we need to handle that.
if info, err := f.fs.Lstat(file.Name); err == nil {
switch info, err := f.fs.Lstat(file.Name); {
case err != nil && !fs.IsNotExist(err):
f.newPullError(file.Name, errors.Wrap(err, "checking for existing symlink"))
return
case err == nil:
// Check that it is what we have in the database.
curFile, hasCurFile := f.model.CurrentFolderFile(f.folderID, file.Name)
if err := f.scanIfItemChanged(file.Name, info, curFile, hasCurFile, scanChan); err != nil {
@@ -1783,7 +1792,7 @@ func (f *sendReceiveFolder) newPullError(path string, err error) {
// We might get more than one error report for a file (i.e. error on
// Write() followed by Close()); we keep the first error as that is
// probably closer to the root cause.
if _, ok := f.pullErrors[path]; ok {
if _, ok := f.tempPullErrors[path]; ok {
return
}
@@ -1791,15 +1800,9 @@ func (f *sendReceiveFolder) newPullError(path string, err error) {
// Use "syncing" as opposed to "pulling" as the latter might be used
// for errors occurring specificly in the puller routine.
errStr := fmt.Sprintln("syncing:", err)
f.pullErrors[path] = errStr
f.tempPullErrors[path] = errStr
if oldErr, ok := f.oldPullErrors[path]; ok && oldErr == errStr {
l.Debugf("Repeat error on puller (folder %s, item %q): %v", f.Description(), path, err)
delete(f.oldPullErrors, path) // Potential repeats are now caught by f.pullErrors itself
return
}
l.Infof("Puller (folder %s, item %q): %v", f.Description(), path, err)
l.Debugf("%v new error for %v: %v", f, path, err)
}
func (f *sendReceiveFolder) Errors() []FileError {