diff options
author | Jakob Unterwurzacher | 2017-12-06 21:07:24 +0100 |
---|---|---|
committer | Jakob Unterwurzacher | 2017-12-06 21:07:24 +0100 |
commit | f97494e89bff7b15d1be5f4b2d047382a54e6094 (patch) | |
tree | acc6e2b5af46717c4cdb4d53cc0b5b543b8b8750 | |
parent | 6beb45e5b74aa465136687f00f2b5a51bee90395 (diff) |
syscallcompat: add Readlinkat
We need readlinkat to implement Readlink
symlink-race-free.
-rw-r--r-- | internal/syscallcompat/sys_common.go | 23 | ||||
-rw-r--r-- | internal/syscallcompat/sys_common_test.go | 29 |
2 files changed, 52 insertions, 0 deletions
diff --git a/internal/syscallcompat/sys_common.go b/internal/syscallcompat/sys_common.go new file mode 100644 index 0000000..440b42f --- /dev/null +++ b/internal/syscallcompat/sys_common.go @@ -0,0 +1,23 @@ +package syscallcompat + +import ( + "golang.org/x/sys/unix" +) + +// Readlinkat exists both in Linux and in MacOS 10.10+. We may add an +// emulated version for users on older MacOS versions if there is +// demand. +// Buffer allocation is handled internally, unlike the bare unix.Readlinkat. +func Readlinkat(dirfd int, path string) (string, error) { + // Allocate the buffer exponentially like os.Readlink does. + for bufsz := 128; ; bufsz *= 2 { + buf := make([]byte, bufsz) + n, err := unix.Readlinkat(dirfd, path, buf) + if err != nil { + return "", err + } + if n < bufsz { + return string(buf[0:n]), nil + } + } +} diff --git a/internal/syscallcompat/sys_common_test.go b/internal/syscallcompat/sys_common_test.go new file mode 100644 index 0000000..bc694ba --- /dev/null +++ b/internal/syscallcompat/sys_common_test.go @@ -0,0 +1,29 @@ +package syscallcompat + +import ( + "bytes" + "os" + "syscall" + "testing" +) + +func TestReadlinkat(t *testing.T) { + for _, targetLen := range []int{100, 500, 4000} { + target := string(bytes.Repeat([]byte("x"), targetLen)) + err := os.Symlink(target, tmpDir+"/readlinkat") + if err != nil { + t.Fatal(err) + } + target2, err := Readlinkat(tmpDirFd, "readlinkat") + if err != nil { + t.Fatal(err) + } + if target != target2 { + t.Errorf("target=%q != target2=%q", target, target2) + } + err = syscall.Unlink(tmpDir + "/readlinkat") + if err != nil { + t.Fatal(err) + } + } +} |