diff --git a/base/findbase.go b/base/findbase.go new file mode 100644 index 0000000..d8bf3fe --- /dev/null +++ b/base/findbase.go @@ -0,0 +1,67 @@ +package base + +import ( + "io/ioutil" + "os" + "path/filepath" + "syscall" +) + +// FindBase steps up the directory tree to find the top-level that is still on +// the same device as the path provided +func FindBase(path string) (string, error) { + stat, err := os.Lstat(path) + if err != nil { + return "", err + } + if stat.IsDir() { + return findBaseInfo(stat) + } + + return FindBase(filepath.Dir(path)) +} + +func findBaseInfo(stat os.FileInfo) (string, error) { + dirstat, err := os.Lstat(filepath.Dir(stat.Name())) + if err != nil { + return "", err + } + if stat.Name() == dirstat.Name() { + return stat.Name(), nil + } + + if sameDevice(stat, dirstat) { + return findBaseInfo(dirstat) + } + return stat.Name(), nil +} + +func hasPermission(path string) bool { + stat, err := os.Lstat(path) + if err != nil { + return false + } + if !stat.IsDir() { + path = filepath.Dir(path) + } + fh, err := ioutil.TempFile(path, "perm.test.") + if err != nil { + return false + } + os.Remove(fh.Name()) + return true +} + +func sameDevice(file1, file2 os.FileInfo) bool { + sys1 := file1.Sys().(*syscall.Stat_t) + sys2 := file2.Sys().(*syscall.Stat_t) + return ((major(sys1.Dev) == major(sys2.Dev)) && (minor(sys1.Dev) == minor(sys2.Dev))) +} + +func major(n uint64) uint64 { + return uint64(n / 256) +} + +func minor(n uint64) uint64 { + return uint64(n % 256) +} diff --git a/base/findbase_test.go b/base/findbase_test.go new file mode 100644 index 0000000..9d25d57 --- /dev/null +++ b/base/findbase_test.go @@ -0,0 +1,75 @@ +package base + +import ( + "io/ioutil" + "os" + "testing" +) + +func TestHasPermission(t *testing.T) { + if !hasPermission("/tmp") { + t.Error("expected to have permission to /tmp, but did not") + } + + if hasPermission("/") { + t.Error("expected to not have permission to /, but did") + } +} + +func TestSameDev(t *testing.T) { + file1, err := ioutil.TempFile("", "test") + if err != nil { + t.Fatal(err) + } + defer file1.Close() + file2, err := ioutil.TempFile("", "test") + if err != nil { + t.Fatal(err) + } + defer file2.Close() + + stat1, err := file1.Stat() + if err != nil { + t.Fatal(err) + } + stat2, err := file2.Stat() + if err != nil { + t.Fatal(err) + } + + if !sameDevice(stat1, stat2) { + t.Errorf("expected the two files to be on same device. But %q and %q are not", file1.Name(), file2.Name()) + } else { + os.Remove(stat1.Name()) + os.Remove(stat2.Name()) + } +} + +func TestNotSameDev(t *testing.T) { + file1, err := ioutil.TempFile("/tmp", "test") + if err != nil { + t.Fatal(err) + } + defer file1.Close() + file2, err := ioutil.TempFile("/home/vbatts", "test") + if err != nil { + t.Fatal(err) + } + defer file2.Close() + + stat1, err := file1.Stat() + if err != nil { + t.Fatal(err) + } + stat2, err := file2.Stat() + if err != nil { + t.Fatal(err) + } + + if sameDevice(stat1, stat2) { + t.Errorf("expected the two files _not_ to be on same device. But %q and %q are not", file1.Name(), file2.Name()) + } else { + os.Remove(stat1.Name()) + os.Remove(stat2.Name()) + } +}