|
@@ -57,9 +57,8 @@ type Config struct {
|
|
|
TempLifetime time.Duration
|
|
|
// If CurrentFiler is not nil, it is queried for the current file before rescanning.
|
|
|
CurrentFiler CurrentFiler
|
|
|
- // If MtimeRepo is not nil, it is used to provide mtimes on systems that
|
|
|
- // don't support setting arbitrary mtimes.
|
|
|
- MtimeRepo MtimeRepo
|
|
|
+ // The Lstater provides reliable mtimes on top of the regular filesystem.
|
|
|
+ Lstater Lstater
|
|
|
// If IgnorePerms is true, changes to permission bits will not be
|
|
|
// detected. Scanned files will get zero permission bits and the
|
|
|
// NoPermissionBits flag set.
|
|
@@ -88,10 +87,8 @@ type CurrentFiler interface {
|
|
|
CurrentFile(name string) (protocol.FileInfo, bool)
|
|
|
}
|
|
|
|
|
|
-type MtimeRepo interface {
|
|
|
- // GetMtime returns a (possibly modified) actual mtime given a file name
|
|
|
- // and its on disk mtime.
|
|
|
- GetMtime(relPath string, mtime time.Time) time.Time
|
|
|
+type Lstater interface {
|
|
|
+ Lstat(name string) (os.FileInfo, error)
|
|
|
}
|
|
|
|
|
|
func Walk(cfg Config) (chan protocol.FileInfo, error) {
|
|
@@ -103,8 +100,8 @@ func Walk(cfg Config) (chan protocol.FileInfo, error) {
|
|
|
if w.TempNamer == nil {
|
|
|
w.TempNamer = noTempNamer{}
|
|
|
}
|
|
|
- if w.MtimeRepo == nil {
|
|
|
- w.MtimeRepo = noMtimeRepo{}
|
|
|
+ if w.Lstater == nil {
|
|
|
+ w.Lstater = defaultLstater{}
|
|
|
}
|
|
|
|
|
|
return w.walk()
|
|
@@ -119,8 +116,7 @@ type walker struct {
|
|
|
func (w *walker) walk() (chan protocol.FileInfo, error) {
|
|
|
l.Debugln("Walk", w.Dir, w.Subs, w.BlockSize, w.Matcher)
|
|
|
|
|
|
- err := checkDir(w.Dir)
|
|
|
- if err != nil {
|
|
|
+ if err := w.checkDir(); err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
@@ -245,14 +241,18 @@ func (w *walker) walkAndHashFiles(fchan, dchan chan protocol.FileInfo) filepath.
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- mtime := w.MtimeRepo.GetMtime(relPath, info.ModTime())
|
|
|
+ info, err = w.Lstater.Lstat(absPath)
|
|
|
+ // An error here would be weird as we've already gotten to this point, but act on it ninetheless
|
|
|
+ if err != nil {
|
|
|
+ return skip
|
|
|
+ }
|
|
|
|
|
|
if w.TempNamer.IsTemporary(relPath) {
|
|
|
// A temporary file
|
|
|
l.Debugln("temporary:", relPath)
|
|
|
- if info.Mode().IsRegular() && mtime.Add(w.TempLifetime).Before(now) {
|
|
|
+ if info.Mode().IsRegular() && info.ModTime().Add(w.TempLifetime).Before(now) {
|
|
|
os.Remove(absPath)
|
|
|
- l.Debugln("removing temporary:", relPath, mtime)
|
|
|
+ l.Debugln("removing temporary:", relPath, info.ModTime())
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
@@ -283,17 +283,17 @@ func (w *walker) walkAndHashFiles(fchan, dchan chan protocol.FileInfo) filepath.
|
|
|
}
|
|
|
|
|
|
case info.Mode().IsDir():
|
|
|
- err = w.walkDir(relPath, info, mtime, dchan)
|
|
|
+ err = w.walkDir(relPath, info, dchan)
|
|
|
|
|
|
case info.Mode().IsRegular():
|
|
|
- err = w.walkRegular(relPath, info, mtime, fchan)
|
|
|
+ err = w.walkRegular(relPath, info, fchan)
|
|
|
}
|
|
|
|
|
|
return err
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (w *walker) walkRegular(relPath string, info os.FileInfo, mtime time.Time, fchan chan protocol.FileInfo) error {
|
|
|
+func (w *walker) walkRegular(relPath string, info os.FileInfo, fchan chan protocol.FileInfo) error {
|
|
|
curMode := uint32(info.Mode())
|
|
|
if runtime.GOOS == "windows" && osutil.IsWindowsExecutable(relPath) {
|
|
|
curMode |= 0111
|
|
@@ -310,12 +310,12 @@ func (w *walker) walkRegular(relPath string, info os.FileInfo, mtime time.Time,
|
|
|
// - has the same size as previously
|
|
|
cf, ok := w.CurrentFiler.CurrentFile(relPath)
|
|
|
permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Permissions, curMode)
|
|
|
- if ok && permUnchanged && !cf.IsDeleted() && cf.Modified == mtime.Unix() && !cf.IsDirectory() &&
|
|
|
+ if ok && permUnchanged && !cf.IsDeleted() && cf.Modified == info.ModTime().Unix() && !cf.IsDirectory() &&
|
|
|
!cf.IsSymlink() && !cf.IsInvalid() && cf.Size == info.Size() {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- l.Debugln("rescan:", cf, mtime.Unix(), info.Mode()&os.ModePerm)
|
|
|
+ l.Debugln("rescan:", cf, info.ModTime().Unix(), info.Mode()&os.ModePerm)
|
|
|
|
|
|
f := protocol.FileInfo{
|
|
|
Name: relPath,
|
|
@@ -323,7 +323,7 @@ func (w *walker) walkRegular(relPath string, info os.FileInfo, mtime time.Time,
|
|
|
Version: cf.Version.Update(w.ShortID),
|
|
|
Permissions: curMode & uint32(maskModePerm),
|
|
|
NoPermissions: w.IgnorePerms,
|
|
|
- Modified: mtime.Unix(),
|
|
|
+ Modified: info.ModTime().Unix(),
|
|
|
Size: info.Size(),
|
|
|
}
|
|
|
l.Debugln("to hash:", relPath, f)
|
|
@@ -337,7 +337,7 @@ func (w *walker) walkRegular(relPath string, info os.FileInfo, mtime time.Time,
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (w *walker) walkDir(relPath string, info os.FileInfo, mtime time.Time, dchan chan protocol.FileInfo) error {
|
|
|
+func (w *walker) walkDir(relPath string, info os.FileInfo, dchan chan protocol.FileInfo) error {
|
|
|
// A directory is "unchanged", if it
|
|
|
// - exists
|
|
|
// - has the same permissions as previously, unless we are ignoring permissions
|
|
@@ -357,7 +357,7 @@ func (w *walker) walkDir(relPath string, info os.FileInfo, mtime time.Time, dcha
|
|
|
Version: cf.Version.Update(w.ShortID),
|
|
|
Permissions: uint32(info.Mode() & maskModePerm),
|
|
|
NoPermissions: w.IgnorePerms,
|
|
|
- Modified: mtime.Unix(),
|
|
|
+ Modified: info.ModTime().Unix(),
|
|
|
}
|
|
|
l.Debugln("dir:", relPath, f)
|
|
|
|
|
@@ -457,7 +457,7 @@ func (w *walker) normalizePath(absPath, relPath string) (normPath string, skip b
|
|
|
|
|
|
// We will attempt to normalize it.
|
|
|
normalizedPath := filepath.Join(w.Dir, normPath)
|
|
|
- if _, err := osutil.Lstat(normalizedPath); os.IsNotExist(err) {
|
|
|
+ if _, err := w.Lstater.Lstat(normalizedPath); os.IsNotExist(err) {
|
|
|
// Nothing exists with the normalized filename. Good.
|
|
|
if err = os.Rename(absPath, normalizedPath); err != nil {
|
|
|
l.Infof(`Error normalizing UTF8 encoding of file "%s": %v`, relPath, err)
|
|
@@ -475,13 +475,13 @@ func (w *walker) normalizePath(absPath, relPath string) (normPath string, skip b
|
|
|
return normPath, false
|
|
|
}
|
|
|
|
|
|
-func checkDir(dir string) error {
|
|
|
- if info, err := osutil.Lstat(dir); err != nil {
|
|
|
+func (w *walker) checkDir() error {
|
|
|
+ if info, err := w.Lstater.Lstat(w.Dir); err != nil {
|
|
|
return err
|
|
|
} else if !info.IsDir() {
|
|
|
- return errors.New(dir + ": not a directory")
|
|
|
+ return errors.New(w.Dir + ": not a directory")
|
|
|
} else {
|
|
|
- l.Debugln("checkDir", dir, info)
|
|
|
+ l.Debugln("checkDir", w.Dir, info)
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
@@ -591,10 +591,10 @@ func (noTempNamer) IsTemporary(path string) bool {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
-// A no-op MtimeRepo
|
|
|
+// A no-op Lstater
|
|
|
|
|
|
-type noMtimeRepo struct{}
|
|
|
+type defaultLstater struct{}
|
|
|
|
|
|
-func (noMtimeRepo) GetMtime(relPath string, mtime time.Time) time.Time {
|
|
|
- return mtime
|
|
|
+func (defaultLstater) Lstat(name string) (os.FileInfo, error) {
|
|
|
+ return osutil.Lstat(name)
|
|
|
}
|