mirror of
https://github.com/syncthing/notify.git
synced 2026-06-07 08:57:32 +00:00
add canonical function
It's supposed to work like realpath(3), but implemented in Go. It should use system-specific syscalls.
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
package notify
|
||||
|
||||
import "os"
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const sep = string(os.PathSeparator)
|
||||
|
||||
@@ -13,6 +17,37 @@ func nonil(err ...error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// canonical resolves any symlink in the given path and returns it in a clean form.
|
||||
// It expects the path to be absolute. It fails to resolve circular symlinks by
|
||||
// maintaining a simple iteration limit.
|
||||
//
|
||||
// TODO(rjeczalik): replace with realpath?
|
||||
func canonical(p string) (string, error) {
|
||||
for i, depth := 1, 1; i < len(p); i, depth = i+1, depth+1 {
|
||||
if depth > 128 {
|
||||
return "", &os.PathError{Op: "canonical", Path: p, Err: errDepth}
|
||||
}
|
||||
if j := strings.IndexRune(p[i:], '/'); j == -1 {
|
||||
i = len(p)
|
||||
} else {
|
||||
i = i + j
|
||||
}
|
||||
fi, err := os.Lstat(p[:i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
s, err := os.Readlink(p[:i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
p = "/" + s + p[i:]
|
||||
i = 1 // no guarantee s is canonical, start all over
|
||||
}
|
||||
}
|
||||
return filepath.Clean(p), nil
|
||||
}
|
||||
|
||||
// Joinevents TODO
|
||||
func joinevents(events []Event) (e Event) {
|
||||
if len(events) == 0 {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// +build darwin
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCanonicalDarwin(t *testing.T) {
|
||||
cases := [...]caseCanonical{
|
||||
{"/etc", "/private/etc"},
|
||||
{"/etc/defaults", "/private/etc/defaults"},
|
||||
{"/etc/hosts", "/private/etc/hosts"},
|
||||
{"/tmp", "/private/tmp"},
|
||||
{"/var", "/private/var"},
|
||||
}
|
||||
testCanonical(t, cases[:])
|
||||
}
|
||||
|
||||
func TestCanonicalDarwinMultiple(t *testing.T) {
|
||||
etcsym, err := symlink("/etc", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpsym, err := symlink("/tmp", "")
|
||||
if err != nil {
|
||||
t.Fatal(nonil(err, os.Remove(etcsym)))
|
||||
}
|
||||
defer removeall(etcsym, tmpsym)
|
||||
cases := [...]caseCanonical{
|
||||
{etcsym, "/private/etc"},
|
||||
{etcsym + "/hosts", "/private/etc/hosts"},
|
||||
{tmpsym, "/private/tmp"},
|
||||
}
|
||||
testCanonical(t, cases[:])
|
||||
}
|
||||
+101
@@ -1,10 +1,111 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func tmpfile(s string) (string, error) {
|
||||
f, err := ioutil.TempFile(filepath.Split(s))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = nonil(f.Sync(), f.Close()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return f.Name(), nil
|
||||
}
|
||||
|
||||
func symlink(src, dst string) (string, error) {
|
||||
name, err := tmpfile(dst)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = nonil(os.Remove(name), os.Symlink(src, name)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func removeall(s ...string) {
|
||||
for _, s := range s {
|
||||
os.Remove(s)
|
||||
}
|
||||
}
|
||||
|
||||
type caseCanonical struct {
|
||||
path string
|
||||
full string
|
||||
}
|
||||
|
||||
func testCanonical(t *testing.T, cases []caseCanonical) {
|
||||
for i, cas := range cases {
|
||||
full, err := canonical(cas.path)
|
||||
if err != nil {
|
||||
t.Errorf("want err=nil; got %v (i=%d)", err, i)
|
||||
continue
|
||||
}
|
||||
if full != cas.full {
|
||||
t.Errorf("want full=%q; got %q (i=%d)", cas.full, full, i)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonical(t *testing.T) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal("os.Getwd()=%v", err)
|
||||
}
|
||||
wdsym, err := symlink(wd, "")
|
||||
if err != nil {
|
||||
t.Fatalf(`symlink(%q, "")=%v`, wd, err)
|
||||
}
|
||||
td := filepath.Join(wd, "testdata")
|
||||
tdsym, err := symlink(td, td)
|
||||
if err != nil {
|
||||
t.Errorf("symlink(%q, %q)=%v", td, td, nonil(err, os.Remove(wdsym)))
|
||||
}
|
||||
defer removeall(wdsym, tdsym)
|
||||
vfstxt := filepath.Join(td, "vfs.txt")
|
||||
cases := [...]caseCanonical{
|
||||
{wdsym, wd},
|
||||
{tdsym, td},
|
||||
{filepath.Join(wdsym, "notify.go"), filepath.Join(wd, "notify.go")},
|
||||
{filepath.Join(tdsym, "vfs.txt"), vfstxt},
|
||||
{filepath.Join(wdsym, filepath.Base(tdsym), "vfs.txt"), vfstxt},
|
||||
}
|
||||
testCanonical(t, cases[:])
|
||||
}
|
||||
|
||||
func TestCanonicalCircular(t *testing.T) {
|
||||
tmp1, err := tmpfile("circular")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmp2, err := tmpfile("circular")
|
||||
if err != nil {
|
||||
t.Fatal(nonil(err, os.Remove(tmp1)))
|
||||
}
|
||||
defer removeall(tmp1, tmp2)
|
||||
// Symlink tmp1 -> tmp2.
|
||||
if err = nonil(os.Remove(tmp1), os.Symlink(tmp2, tmp1)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Symlnik tmp2 -> tmp1.
|
||||
if err = nonil(os.Remove(tmp2), os.Symlink(tmp1, tmp2)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = canonical(tmp1); err == nil {
|
||||
t.Fatalf("want canonical(%q)!=nil", tmp1)
|
||||
}
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
t.Fatalf("want canonical(%q)=os.PathError; got %T", tmp1, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinevents(t *testing.T) {
|
||||
cases := [...]struct {
|
||||
evs []Event
|
||||
|
||||
@@ -4,8 +4,6 @@ package notify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
@@ -30,37 +28,6 @@ type FSEvent struct {
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// canonical resolves any symlink in the given path and returns it in a clean form.
|
||||
// It expects the path to be absolute. It fails to resolve circular symlinks by
|
||||
// maintaining a simple iteration limit.
|
||||
//
|
||||
// TODO(rjeczalik): replace with realpath?
|
||||
func canonical(p string) (string, error) {
|
||||
for i, depth := 1, 1; i < len(p); i, depth = i+1, depth+1 {
|
||||
if depth > 128 {
|
||||
return "", &os.PathError{Op: "canonical", Path: p, Err: errDepth}
|
||||
}
|
||||
if j := strings.IndexRune(p[i:], '/'); j == -1 {
|
||||
i = len(p)
|
||||
} else {
|
||||
i = i + j
|
||||
}
|
||||
fi, err := os.Lstat(p[:i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
s, err := os.Readlink(p[:i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
p = "/" + s + p[i:]
|
||||
i = 1 // no guarantee s is canonical, start all over
|
||||
}
|
||||
}
|
||||
return filepath.Clean(p), nil
|
||||
}
|
||||
|
||||
// splitflags separates event flags from single set into slice of flags.
|
||||
func splitflags(set uint32) (e []uint32) {
|
||||
for i := uint32(1); set != 0; i, set = i<<1, set>>1 {
|
||||
|
||||
@@ -3,114 +3,10 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func tmpfile(s string) (string, error) {
|
||||
f, err := ioutil.TempFile("/tmp", s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = nonil(f.Sync(), f.Close()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return f.Name(), nil
|
||||
}
|
||||
|
||||
func symlink(s string) (string, error) {
|
||||
name, err := tmpfile("symlink")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = nonil(os.Remove(name), os.Symlink(s, name)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func removeall(s ...string) {
|
||||
for _, s := range s {
|
||||
os.Remove(s)
|
||||
}
|
||||
}
|
||||
|
||||
type caseCanonical struct {
|
||||
path string
|
||||
full string
|
||||
}
|
||||
|
||||
func testCanonical(t *testing.T, cases []caseCanonical) {
|
||||
for i, cas := range cases {
|
||||
full, err := canonical(cas.path)
|
||||
if err != nil {
|
||||
t.Errorf("want err=nil; got %v (i=%d)", err, i)
|
||||
continue
|
||||
}
|
||||
if full != cas.full {
|
||||
t.Errorf("want full=%q; got %q (i=%d)", cas.full, full, i)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalize(t *testing.T) {
|
||||
cases := [...]caseCanonical{
|
||||
{"/etc", "/private/etc"},
|
||||
{"/etc/defaults", "/private/etc/defaults"},
|
||||
{"/etc/hosts", "/private/etc/hosts"},
|
||||
{"/tmp", "/private/tmp"},
|
||||
{"/var", "/private/var"},
|
||||
}
|
||||
testCanonical(t, cases[:])
|
||||
}
|
||||
|
||||
func TestCanonicalizeMultiple(t *testing.T) {
|
||||
link1, err := symlink("/etc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
link2, err := symlink("/tmp")
|
||||
if err != nil {
|
||||
t.Fatal(nonil(err, os.Remove(link1)))
|
||||
}
|
||||
defer removeall(link1, link2)
|
||||
cases := [...]caseCanonical{
|
||||
{link1, "/private/etc"},
|
||||
{link1 + "/hosts", "/private/etc/hosts"},
|
||||
{link2, "/private/tmp"},
|
||||
}
|
||||
testCanonical(t, cases[:])
|
||||
}
|
||||
|
||||
func TestCanonicalizeCircular(t *testing.T) {
|
||||
tmp1, err := tmpfile("circular")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmp2, err := tmpfile("circular")
|
||||
if err != nil {
|
||||
t.Fatal(nonil(err, os.Remove(tmp1)))
|
||||
}
|
||||
defer removeall(tmp1, tmp2)
|
||||
// Symlink tmp1 -> tmp2.
|
||||
if err = nonil(os.Remove(tmp1), os.Symlink(tmp2, tmp1)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Symlnik tmp2 -> tmp1.
|
||||
if err = nonil(os.Remove(tmp2), os.Symlink(tmp1, tmp2)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = canonical(tmp1); err == nil {
|
||||
t.Fatalf("want canonical(%q)!=nil", tmp1)
|
||||
}
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
t.Fatalf("want canonical(%q)=os.PathError; got %T", tmp1, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitflags(t *testing.T) {
|
||||
cases := [...]struct {
|
||||
set uint32
|
||||
|
||||
Reference in New Issue
Block a user