unix: add openat2 for linux

openat2 is a new syscall added to Linux 5.6. It provides a superset of
openat(2) functionality, extending it with flags telling the kernel how
to resolve the paths.

For more info, see https://lwn.net/Articles/803237/

NOTE that this is a second attempt to add the call; the previous one
(https://golang.org/cl/227280) was reverted
(https://golang.org/cl/227846) due to the test case failure on ARM
(https://golang.org/issue/38357).

This CL has the test case reworked to be less assumptive to the testing
environment. In particular, it first tries if the most simplistic
openat2() call succeeds, and skips the test otherwise. It is done that
way because CI can be under under different kernels and in various
envrionments -- in particular, Docker+seccomp can result in EPERM from a
system call (which is not expected otherwise).

For previous discussions about the test case, see
https://golang.org/cl/227865.

Change-Id: I4276cf13dc29ecbdbdc9c58da0f76270f585a67f
Reviewed-on: https://go-review.googlesource.com/c/sys/+/253057
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
diff --git a/unix/linux/types.go b/unix/linux/types.go
index f3c2396..eb0fd3c 100644
--- a/unix/linux/types.go
+++ b/unix/linux/types.go
@@ -111,6 +111,7 @@
 #include <linux/netfilter.h>
 #include <linux/netlink.h>
 #include <linux/nexthop.h>
+#include <linux/openat2.h>
 #include <linux/perf_event.h>
 #include <linux/random.h>
 #include <linux/rtc.h>
@@ -874,6 +875,18 @@
 	AT_EACCESS = C.AT_EACCESS
 )
 
+type OpenHow C.struct_open_how
+
+const SizeofOpenHow = C.sizeof_struct_open_how
+
+const (
+	RESOLVE_BENEATH       = C.RESOLVE_BENEATH
+	RESOLVE_IN_ROOT       = C.RESOLVE_IN_ROOT
+	RESOLVE_NO_MAGICLINKS = C.RESOLVE_NO_MAGICLINKS
+	RESOLVE_NO_SYMLINKS   = C.RESOLVE_NO_SYMLINKS
+	RESOLVE_NO_XDEV       = C.RESOLVE_NO_XDEV
+)
+
 type PollFd C.struct_pollfd
 
 const (
diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go
index 5b3af2e..ec7e4c4 100644
--- a/unix/syscall_linux.go
+++ b/unix/syscall_linux.go
@@ -136,6 +136,12 @@
 	return openat(dirfd, path, flags|O_LARGEFILE, mode)
 }
 
+//sys	openat2(dirfd int, path string, open_how *OpenHow, size int) (fd int, err error)
+
+func Openat2(dirfd int, path string, how *OpenHow) (fd int, err error) {
+	return openat2(dirfd, path, how, SizeofOpenHow)
+}
+
 //sys	ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error)
 
 func Ppoll(fds []PollFd, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go
index 01f29ca..76555d4 100644
--- a/unix/syscall_linux_test.go
+++ b/unix/syscall_linux_test.go
@@ -13,6 +13,7 @@
 	"fmt"
 	"io/ioutil"
 	"os"
+	"path/filepath"
 	"runtime"
 	"runtime/debug"
 	"strconv"
@@ -730,3 +731,62 @@
 		count += *(*uint64)(unsafe.Pointer(&buffer))
 	}
 }
+
+func TestOpenat2(t *testing.T) {
+	how := &unix.OpenHow{
+		Flags: unix.O_RDONLY,
+	}
+	fd, err := unix.Openat2(unix.AT_FDCWD, ".", how)
+	if err != nil {
+		if err == unix.ENOSYS || err == unix.EPERM {
+			t.Skipf("openat2: %v (old kernel? need Linux >= 5.6)", err)
+		}
+		t.Fatalf("openat2: %v", err)
+	}
+	if err := unix.Close(fd); err != nil {
+		t.Fatalf("close: %v", err)
+	}
+
+	// prepare
+	tempDir, err := ioutil.TempDir("", t.Name())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tempDir)
+
+	subdir := filepath.Join(tempDir, "dir")
+	if err := os.Mkdir(subdir, 0755); err != nil {
+		t.Fatal(err)
+	}
+	symlink := filepath.Join(subdir, "symlink")
+	if err := os.Symlink("../", symlink); err != nil {
+		t.Fatal(err)
+	}
+
+	dirfd, err := unix.Open(subdir, unix.O_RDONLY, 0)
+	if err != nil {
+		t.Fatalf("open(%q): %v", subdir, err)
+	}
+	defer unix.Close(dirfd)
+
+	// openat2 with no extra flags -- should succeed
+	fd, err = unix.Openat2(dirfd, "symlink", how)
+	if err != nil {
+		t.Errorf("Openat2 should succeed, got %v", err)
+	}
+	if err := unix.Close(fd); err != nil {
+		t.Fatalf("close: %v", err)
+	}
+
+	// open with RESOLVE_BENEATH, should result in EXDEV
+	how.Resolve = unix.RESOLVE_BENEATH
+	fd, err = unix.Openat2(dirfd, "symlink", how)
+	if err == nil {
+		if err := unix.Close(fd); err != nil {
+			t.Fatalf("close: %v", err)
+		}
+	}
+	if err != unix.EXDEV {
+		t.Errorf("Openat2 should fail with EXDEV, got %v", err)
+	}
+}
diff --git a/unix/zsyscall_linux.go b/unix/zsyscall_linux.go
index 4eec7a7..2fbbbe5 100644
--- a/unix/zsyscall_linux.go
+++ b/unix/zsyscall_linux.go
@@ -83,6 +83,22 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func openat2(dirfd int, path string, open_how *OpenHow, size int) (fd int, err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(path)
+	if err != nil {
+		return
+	}
+	r0, _, e1 := Syscall6(SYS_OPENAT2, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(open_how)), uintptr(size), 0, 0)
+	fd = int(r0)
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
 	r0, _, e1 := Syscall6(SYS_PPOLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)), 0, 0)
 	n = int(r0)
diff --git a/unix/ztypes_linux.go b/unix/ztypes_linux.go
index 1879d57..68e4974 100644
--- a/unix/ztypes_linux.go
+++ b/unix/ztypes_linux.go
@@ -752,6 +752,22 @@
 	AT_EACCESS = 0x200
 )
 
+type OpenHow struct {
+	Flags   uint64
+	Mode    uint64
+	Resolve uint64
+}
+
+const SizeofOpenHow = 0x18
+
+const (
+	RESOLVE_BENEATH       = 0x8
+	RESOLVE_IN_ROOT       = 0x10
+	RESOLVE_NO_MAGICLINKS = 0x2
+	RESOLVE_NO_SYMLINKS   = 0x4
+	RESOLVE_NO_XDEV       = 0x1
+)
+
 type PollFd struct {
 	Fd      int32
 	Events  int16