Skip to content

Commit

Permalink
Modified scan to extract device ID from existing stat info.
Browse files Browse the repository at this point in the history
It turns out we were being a bit silly and re-lstating each directory
during a scan to get the device ID. We already have the directory stat
information, so we can just pass that down and extract the device ID
from that.
  • Loading branch information
xenoscopic committed Jul 13, 2018
1 parent bef1a6f commit c046e9f
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 44 deletions.
12 changes: 2 additions & 10 deletions pkg/filesystem/device_posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,8 @@ import (
"github.com/pkg/errors"
)

func DeviceID(path string) (uint64, error) {
// Perform a stat on the path.
info, err := os.Lstat(path)
if err != nil {
if os.IsNotExist(err) {
return 0, err
}
return 0, errors.Wrap(err, "unable to query filesystem information")
}

// DeviceID extracts the device ID from a stat result.
func DeviceID(info os.FileInfo) (uint64, error) {
// Grab the system-specific stat type.
stat, ok := info.Sys().(*syscall.Stat_t)
if !ok {
Expand Down
43 changes: 20 additions & 23 deletions pkg/filesystem/device_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,6 @@ import (
"testing"
)

func TestDeviceIDFailOnNonExistentPath(t *testing.T) {
// If we're on Windows, the device ID is always 0, and the probing never
// fails, so skip this test in that case.
if runtime.GOOS == "windows" {
t.Skip()
}

// Attempt to grab the device ID.
if _, err := DeviceID("/path/does/not/exist"); err == nil {
t.Error("device ID probe succeeded for non-existent path")
}
}

func TestDeviceID(t *testing.T) {
if _, err := DeviceID("."); err != nil {
t.Error("device ID probe failed for current path:", err)
}
}

func TestDeviceIDsDifferent(t *testing.T) {
// If we're on Windows, the device ID is always 0, so skip this test in that
// case.
Expand All @@ -40,13 +21,21 @@ func TestDeviceIDsDifferent(t *testing.T) {
}

// Grab the device ID for the current path.
deviceID, err := DeviceID(".")
info, err := os.Lstat(".")
if err != nil {
t.Fatal("lstat failed for current path:", err)
}
deviceID, err := DeviceID(info)
if err != nil {
t.Fatal("device ID probe failed for current path:", err)
}

// Grab the device ID for the FAT32 partition.
fat32DeviceID, err := DeviceID(fat32Root)
fat32Info, err := os.Lstat(fat32Root)
if err != nil {
t.Fatal("lstat failed for FAT32 partition:", err)
}
fat32DeviceID, err := DeviceID(fat32Info)
if err != nil {
t.Fatal("device ID probe failed for FAT32 partition:", err)
}
Expand Down Expand Up @@ -75,13 +64,21 @@ func TestDeviceIDSubrootDifferent(t *testing.T) {
parent := filepath.Dir(fat32Subroot)

// Grab the device ID for the parent path.
parentDeviceID, err := DeviceID(parent)
parentInfo, err := os.Lstat(parent)
if err != nil {
t.Fatal("lstat failed for parent path:", err)
}
parentDeviceID, err := DeviceID(parentInfo)
if err != nil {
t.Fatal("device ID probe failed for parent path:", err)
}

// Grab the device ID for the FAT32 partition.
fat32SubrootDeviceID, err := DeviceID(fat32Subroot)
fat32SubrootInfo, err := os.Lstat(fat32Subroot)
if err != nil {
t.Fatal("lstat failed for FAT32 subpath:", err)
}
fat32SubrootDeviceID, err := DeviceID(fat32SubrootInfo)
if err != nil {
t.Fatal("device ID probe failed for FAT32 subpath:", err)
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/filesystem/device_windows.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package filesystem

func DeviceID(_ string) (uint64, error) {
import (
"os"
)

// DeviceID on Windows is a no-op that returns 0 and never fails. It's not
// necessary for our purposes on Windows since directory hierarchies can't span
// devices.
func DeviceID(_ os.FileInfo) (uint64, error) {
return 0, nil
}
17 changes: 7 additions & 10 deletions pkg/sync/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,21 +137,18 @@ func (s *scanner) symlink(path string, enforcePortable bool) (*Entry, error) {
}, nil
}

func (s *scanner) directory(path string, symlinkMode SymlinkMode) (*Entry, error) {
// Compute the full path to the directory.
fullPath := filepath.Join(s.root, path)

func (s *scanner) directory(path string, info os.FileInfo, symlinkMode SymlinkMode) (*Entry, error) {
// Verify that we haven't crossed a directory boundary (which might
// potentially change executability preservation or Unicode decomposition
// behavior).
if id, err := filesystem.DeviceID(fullPath); err != nil {
return nil, errors.Wrap(err, "unable to probe directory device ID")
if id, err := filesystem.DeviceID(info); err != nil {
return nil, errors.Wrap(err, "unable to extract directory device ID")
} else if id != s.deviceID {
return nil, errors.New("scan crossed filesystem boundary")
}

// Read directory contents.
directoryContents, err := filesystem.DirectoryContents(fullPath)
directoryContents, err := filesystem.DirectoryContents(filepath.Join(s.root, path))
if err != nil {
return nil, errors.Wrap(err, "unable to read directory contents")
}
Expand Down Expand Up @@ -198,7 +195,7 @@ func (s *scanner) directory(path string, symlinkMode SymlinkMode) (*Entry, error
panic("unsupported symlink mode")
}
} else if kind == EntryKind_Directory {
entry, err = s.directory(contentPath, symlinkMode)
entry, err = s.directory(contentPath, c, symlinkMode)
} else {
panic("unhandled entry kind")
}
Expand Down Expand Up @@ -271,7 +268,7 @@ func Scan(root string, hasher hash.Hash, cache *Cache, ignores []string, symlink
}
} else if mode := info.Mode(); mode&os.ModeDir != 0 {
// Grab and set the device ID for the root directory.
if id, err := filesystem.DeviceID(root); err != nil {
if id, err := filesystem.DeviceID(info); err != nil {
return nil, false, false, nil, errors.Wrap(err, "unable to probe root device ID")
} else {
s.deviceID = id
Expand All @@ -292,7 +289,7 @@ func Scan(root string, hasher hash.Hash, cache *Cache, ignores []string, symlink
}

// Perform a recursive scan.
if rootEntry, err := s.directory("", symlinkMode); err != nil {
if rootEntry, err := s.directory("", info, symlinkMode); err != nil {
return nil, false, false, nil, err
} else {
return rootEntry, s.preservesExecutability, s.recomposeUnicode, newCache, nil
Expand Down

0 comments on commit c046e9f

Please sign in to comment.