From f97494e89bff7b15d1be5f4b2d047382a54e6094 Mon Sep 17 00:00:00 2001
From: Jakob Unterwurzacher
Date: Wed, 6 Dec 2017 21:07:24 +0100
Subject: syscallcompat: add Readlinkat

We need readlinkat to implement Readlink
symlink-race-free.
---
 internal/syscallcompat/sys_common.go      | 23 +++++++++++++++++++++++
 internal/syscallcompat/sys_common_test.go | 29 +++++++++++++++++++++++++++++
 2 files changed, 52 insertions(+)
 create mode 100644 internal/syscallcompat/sys_common.go
 create mode 100644 internal/syscallcompat/sys_common_test.go

(limited to 'internal')

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)
+		}
+	}
+}
-- 
cgit v1.2.3