lib/model, lib/ignores: Properly handle out of folder ignores and free space checks (fixes #4313, fixes #4314)

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4318
This commit is contained in:
Audrius Butkevicius 2017-08-22 06:45:00 +00:00 committed by Jakob Borg
parent b8c249cddc
commit 0a96a1150b
3 changed files with 52 additions and 33 deletions

View File

@ -70,8 +70,8 @@ func (r Result) IsCaseFolded() bool {
// called on it) and if any of the files have Changed(). To forget all // called on it) and if any of the files have Changed(). To forget all
// files, call Reset(). // files, call Reset().
type ChangeDetector interface { type ChangeDetector interface {
Remember(name string, modtime time.Time) Remember(fs fs.Filesystem, name string, modtime time.Time)
Seen(name string) bool Seen(fs fs.Filesystem, name string) bool
Changed() bool Changed() bool
Reset() Reset()
} }
@ -116,7 +116,7 @@ func New(fs fs.Filesystem, opts ...Option) *Matcher {
opt(m) opt(m)
} }
if m.changeDetector == nil { if m.changeDetector == nil {
m.changeDetector = newModtimeChecker(fs) m.changeDetector = newModtimeChecker()
} }
if m.withCache { if m.withCache {
go m.clean(2 * time.Hour) go m.clean(2 * time.Hour)
@ -128,7 +128,7 @@ func (m *Matcher) Load(file string) error {
m.mut.Lock() m.mut.Lock()
defer m.mut.Unlock() defer m.mut.Unlock()
if m.changeDetector.Seen(file) && !m.changeDetector.Changed() { if m.changeDetector.Seen(m.fs, file) && !m.changeDetector.Changed() {
return nil return nil
} }
@ -146,7 +146,7 @@ func (m *Matcher) Load(file string) error {
} }
m.changeDetector.Reset() m.changeDetector.Reset()
m.changeDetector.Remember(file, info.ModTime()) m.changeDetector.Remember(m.fs, file, info.ModTime())
return m.parseLocked(fd, file) return m.parseLocked(fd, file)
} }
@ -300,12 +300,23 @@ func hashPatterns(patterns []Pattern) string {
return fmt.Sprintf("%x", h.Sum(nil)) return fmt.Sprintf("%x", h.Sum(nil))
} }
func loadIgnoreFile(fs fs.Filesystem, file string, cd ChangeDetector) ([]string, []Pattern, error) { func loadIgnoreFile(filesystem fs.Filesystem, file string, cd ChangeDetector) ([]string, []Pattern, error) {
if cd.Seen(file) { if cd.Seen(filesystem, file) {
return nil, nil, fmt.Errorf("multiple include of ignore file %q", file) return nil, nil, fmt.Errorf("multiple include of ignore file %q", file)
} }
fd, err := fs.Open(file) // Allow escaping the folders filesystem.
// TODO: Deprecate, somehow?
if filesystem.Type() == fs.FilesystemTypeBasic {
uri := filesystem.URI()
joined := filepath.Join(uri, file)
if !strings.HasPrefix(joined, uri) {
filesystem = fs.NewFilesystem(filesystem.Type(), filepath.Dir(joined))
file = filepath.Base(joined)
}
}
fd, err := filesystem.Open(file)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -316,9 +327,9 @@ func loadIgnoreFile(fs fs.Filesystem, file string, cd ChangeDetector) ([]string,
return nil, nil, err return nil, nil, err
} }
cd.Remember(file, info.ModTime()) cd.Remember(filesystem, file, info.ModTime())
return parseIgnoreFile(fs, fd, file, cd) return parseIgnoreFile(filesystem, fd, file, cd)
} }
func parseIgnoreFile(fs fs.Filesystem, fd io.Reader, currentFile string, cd ChangeDetector) ([]string, []Pattern, error) { func parseIgnoreFile(fs fs.Filesystem, fd io.Reader, currentFile string, cd ChangeDetector) ([]string, []Pattern, error) {
@ -386,7 +397,7 @@ func parseIgnoreFile(fs fs.Filesystem, fd io.Reader, currentFile string, cd Chan
} }
patterns = append(patterns, pattern) patterns = append(patterns, pattern)
} else if strings.HasPrefix(line, "#include ") { } else if strings.HasPrefix(line, "#include ") {
includeRel := line[len("#include "):] includeRel := strings.TrimSpace(line[len("#include "):])
includeFile := filepath.Join(filepath.Dir(currentFile), includeRel) includeFile := filepath.Join(filepath.Dir(currentFile), includeRel)
_, includePatterns, err := loadIgnoreFile(fs, includeFile, cd) _, includePatterns, err := loadIgnoreFile(fs, includeFile, cd)
if err != nil { if err != nil {
@ -483,35 +494,38 @@ func WriteIgnores(filesystem fs.Filesystem, path string, content []string) error
return nil return nil
} }
type modtimeCheckerKey struct {
fs fs.Filesystem
name string
}
// modtimeChecker is the default implementation of ChangeDetector // modtimeChecker is the default implementation of ChangeDetector
type modtimeChecker struct { type modtimeChecker struct {
fs fs.Filesystem modtimes map[modtimeCheckerKey]time.Time
modtimes map[string]time.Time
} }
func newModtimeChecker(fs fs.Filesystem) *modtimeChecker { func newModtimeChecker() *modtimeChecker {
return &modtimeChecker{ return &modtimeChecker{
fs: fs, modtimes: map[modtimeCheckerKey]time.Time{},
modtimes: map[string]time.Time{},
} }
} }
func (c *modtimeChecker) Remember(name string, modtime time.Time) { func (c *modtimeChecker) Remember(fs fs.Filesystem, name string, modtime time.Time) {
c.modtimes[name] = modtime c.modtimes[modtimeCheckerKey{fs, name}] = modtime
} }
func (c *modtimeChecker) Seen(name string) bool { func (c *modtimeChecker) Seen(fs fs.Filesystem, name string) bool {
_, ok := c.modtimes[name] _, ok := c.modtimes[modtimeCheckerKey{fs, name}]
return ok return ok
} }
func (c *modtimeChecker) Reset() { func (c *modtimeChecker) Reset() {
c.modtimes = map[string]time.Time{} c.modtimes = map[modtimeCheckerKey]time.Time{}
} }
func (c *modtimeChecker) Changed() bool { func (c *modtimeChecker) Changed() bool {
for name, modtime := range c.modtimes { for key, modtime := range c.modtimes {
info, err := c.fs.Stat(name) info, err := key.fs.Stat(key.name)
if err != nil { if err != nil {
return true return true
} }

View File

@ -2304,7 +2304,7 @@ func (m *Model) checkFreeSpace(req config.Size, fs fs.Filesystem) error {
} }
if req.Percentage() { if req.Percentage() {
freePct := (1 - float64(usage.Free)/float64(usage.Total)) * 100 freePct := (float64(usage.Free) / float64(usage.Total)) * 100
if err == nil && freePct < val { if err == nil && freePct < val {
return fmt.Errorf("insufficient space in %v %v: %f %% < %v", fs.Type(), fs.URI(), freePct, req) return fmt.Errorf("insufficient space in %v %v: %f %% < %v", fs.Type(), fs.URI(), freePct, req)
} }

View File

@ -2382,27 +2382,32 @@ func (fakeAddr) String() string {
return "address" return "address"
} }
type alwaysChangedKey struct {
fs fs.Filesystem
name string
}
// alwaysChanges is an ignore.ChangeDetector that always returns true on Changed() // alwaysChanges is an ignore.ChangeDetector that always returns true on Changed()
type alwaysChanged struct { type alwaysChanged struct {
seen map[string]struct{} seen map[alwaysChangedKey]struct{}
} }
func newAlwaysChanged() *alwaysChanged { func newAlwaysChanged() *alwaysChanged {
return &alwaysChanged{ return &alwaysChanged{
seen: make(map[string]struct{}), seen: make(map[alwaysChangedKey]struct{}),
} }
} }
func (c *alwaysChanged) Remember(name string, _ time.Time) { func (c *alwaysChanged) Remember(fs fs.Filesystem, name string, _ time.Time) {
c.seen[name] = struct{}{} c.seen[alwaysChangedKey{fs, name}] = struct{}{}
} }
func (c *alwaysChanged) Reset() { func (c *alwaysChanged) Reset() {
c.seen = make(map[string]struct{}) c.seen = make(map[alwaysChangedKey]struct{})
} }
func (c *alwaysChanged) Seen(name string) bool { func (c *alwaysChanged) Seen(fs fs.Filesystem, name string) bool {
_, ok := c.seen[name] _, ok := c.seen[alwaysChangedKey{fs, name}]
return ok return ok
} }