lib/model: Check for symlinks before deleting during pull (fixes #6090) (#6100)

This commit is contained in:
Simon Frei 2019-10-22 21:55:51 +02:00 committed by Jakob Borg
parent 72f26c1e45
commit bbdda059bd
2 changed files with 66 additions and 2 deletions

View File

@ -847,10 +847,17 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h
if !hasCur {
// We should never try to pull a deletion for a file we don't have in the DB.
l.Debugln(f, "not deleting file we don't have", file.Name)
l.Debugln(f, "not deleting file we don't have, but update db", file.Name)
dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteFile}
return
}
if err = osutil.TraversesSymlink(f.fs, filepath.Dir(file.Name)); err != nil {
l.Debugln(f, "not deleting file behind symlink on disk, but update db", file.Name)
dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteFile}
return
}
if err = f.checkToBeDeleted(cur, scanChan); err != nil {
return
}
@ -1839,6 +1846,10 @@ func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, scanChan ch
// 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, scanChan chan<- string) error {
if err := osutil.TraversesSymlink(f.fs, filepath.Dir(dir)); err != nil {
return err
}
files, _ := f.fs.DirNames(dir)
toBeDeleted := make([]string, 0, len(files))

View File

@ -23,6 +23,7 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
"github.com/syncthing/syncthing/lib/sync"
@ -124,7 +125,7 @@ func setupSendReceiveFolder(files ...protocol.FileInfo) (*model, *sendReceiveFol
func cleanupSRFolder(f *sendReceiveFolder, m *model) {
m.evLogger.Stop()
os.Remove(m.cfg.ConfigPath())
os.Remove(f.Filesystem().URI())
os.RemoveAll(f.Filesystem().URI())
}
// Layout of the files: (indexes from the above array)
@ -907,6 +908,58 @@ func TestSRConflictReplaceFileByLink(t *testing.T) {
}
}
// TestDeleteBehindSymlink checks that we don't delete or schedule a scan
// when trying to delete a file behind a symlink.
func TestDeleteBehindSymlink(t *testing.T) {
m, f := setupSendReceiveFolder()
defer cleanupSRFolder(f, m)
ffs := f.Filesystem()
destDir := createTmpDir()
defer os.RemoveAll(destDir)
destFs := fs.NewFilesystem(fs.FilesystemTypeBasic, destDir)
link := "link"
file := filepath.Join(link, "file")
must(t, ffs.MkdirAll(link, 0755))
fi := createFile(t, file, ffs)
f.updateLocalsFromScanning([]protocol.FileInfo{fi})
must(t, osutil.RenameOrCopy(ffs, destFs, file, "file"))
must(t, ffs.RemoveAll(link))
if err := osutil.DebugSymlinkForTestsOnly(destFs.URI(), filepath.Join(ffs.URI(), link)); err != nil {
if runtime.GOOS == "windows" {
// Probably we require permissions we don't have.
t.Skip("Need admin permissions or developer mode to run symlink test on Windows: " + err.Error())
} else {
t.Fatal(err)
}
}
fi.Deleted = true
fi.Version = fi.Version.Update(device1.Short())
scanChan := make(chan string, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
f.deleteFile(fi, dbUpdateChan, scanChan)
select {
case f := <-scanChan:
t.Fatalf("Received %v on scanChan", f)
case u := <-dbUpdateChan:
if u.jobType != dbUpdateDeleteFile {
t.Errorf("Expected jobType %v, got %v", dbUpdateDeleteFile, u.jobType)
}
if u.file.Name != fi.Name {
t.Errorf("Expected update for %v, got %v", fi.Name, u.file.Name)
}
default:
t.Fatalf("No db update received")
}
if _, err := destFs.Stat("file"); err != nil {
t.Errorf("Expected no error when stating file behind symlink, got %v", err)
}
}
func cleanupSharedPullerState(s *sharedPullerState) {
s.mut.Lock()
defer s.mut.Unlock()