unix: add IoctlRetInt for Linux

Add IoctlRetInt() for ioctls that use syscall's return value
as an output parameter. Add a unit test for Linux, and the
NS_GET_ defines that it requires (see ioctl_ns(2)).

Motivation:

Currently, x/sys/unix provides a few functions to deal with ioctls.
In particular, IoctlGetInt(), which "performs an ioctl operation
which gets an integer value". It does that by passing a pointer
to an integer to a syscall and returning that integer. The value
returned from syscall is treated as success/failure flag
(0 means success, -1 means failure, and in such case errno
is used to figure out the underlying error).

It appears that there are a few ioctls in Linux and at least one
in Solaris, which do not use the above way to return an int, instead
they use the syscall's return value (in case it's not negative).

As Linux ioctl(2) man page says,

> RETURN VALUE
> Usually, on success zero is returned. A few ioctl() requests
> use the return value as an output parameter and return
> a nonnegative value on success. On error, -1 is returned,
> and errno is set appropriately.

Currently I am aware of at least 6 Linux ioctls that do that
(return the value directly):

* LOOP_CTL_* ioctls on /dev/loop-control (all 3 of them). Source: loop(4)
* NS_* ioctls (3 out of 4) on /proc/PID/ns/*. Source: ioctl_ns(2)

And one in Solaris:

* I_FIND ioctl, source: streamio(7i).

There might be some more ioctls like the ones above, but since
ioctls are scarcely documented, it is hard to say how many more.

Obviously, using IoctlGetInt() for such ioctls would be a big
mistake, as 0 will always be returned. For example, there was
a bug in Docker's pkg/loopback (moby/moby#39801).

[v6: make it linux-specific for now]

Fixes golang/go#33966

Change-Id: Ie64f72fd84101b955ba14519a357e06a66d685d0
Reviewed-on: https://go-review.googlesource.com/c/sys/+/192780
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
diff --git a/unix/ioctl.go b/unix/ioctl.go
index 6502a43..3559e5d 100644
--- a/unix/ioctl.go
+++ b/unix/ioctl.go
@@ -43,6 +43,9 @@
 
 // IoctlGetInt performs an ioctl operation which gets an integer value
 // from fd, using the specified request number.
+//
+// A few ioctl requests use the return value as an output parameter;
+// for those, IoctlRetInt should be used instead of this function.
 func IoctlGetInt(fd int, req uint) (int, error) {
 	var value int
 	err := ioctl(fd, req, uintptr(unsafe.Pointer(&value)))
diff --git a/unix/mkerrors.sh b/unix/mkerrors.sh
index 14624b9..85cfbd0 100755
--- a/unix/mkerrors.sh
+++ b/unix/mkerrors.sh
@@ -206,6 +206,7 @@
 #include <linux/netfilter/nfnetlink.h>
 #include <linux/netlink.h>
 #include <linux/net_namespace.h>
+#include <linux/nsfs.h>
 #include <linux/perf_event.h>
 #include <linux/random.h>
 #include <linux/reboot.h>
@@ -451,6 +452,7 @@
 		$2 ~ /^SYSCTL_VERS/ ||
 		$2 !~ "MNT_BITS" &&
 		$2 ~ /^(MS|MNT|UMOUNT)_/ ||
+		$2 ~ /^NS_GET_/ ||
 		$2 ~ /^TUN(SET|GET|ATTACH|DETACH)/ ||
 		$2 ~ /^(O|F|[ES]?FD|NAME|S|PTRACE|PT)_/ ||
 		$2 ~ /^KEXEC_/ ||
diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go
index 4649398..fe30b95 100644
--- a/unix/syscall_linux.go
+++ b/unix/syscall_linux.go
@@ -71,6 +71,17 @@
 // ioctl itself should not be exposed directly, but additional get/set
 // functions for specific types are permissible.
 
+// IoctlRetInt performs an ioctl operation specified by req on a device
+// associated with opened file descriptor fd, and returns a non-negative
+// integer that is returned by the ioctl syscall.
+func IoctlRetInt(fd int, req uint) (int, error) {
+	ret, _, err := Syscall(SYS_IOCTL, uintptr(fd), uintptr(req), 0)
+	if err != 0 {
+		return 0, err
+	}
+	return int(ret), nil
+}
+
 // IoctlSetPointerInt performs an ioctl operation which sets an
 // integer value on fd, using the specified request number. The ioctl
 // argument is called with a pointer to the integer value, rather than
diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go
index 6ceeb42..634e27d 100644
--- a/unix/syscall_linux_test.go
+++ b/unix/syscall_linux_test.go
@@ -38,6 +38,25 @@
 	t.Logf("%d bits of entropy available", v)
 }
 
+func TestIoctlRetInt(t *testing.T) {
+	f, err := os.Open("/proc/self/ns/mnt")
+	if err != nil {
+		t.Skipf("skipping test, %v", err)
+	}
+	defer f.Close()
+
+	v, err := unix.IoctlRetInt(int(f.Fd()), unix.NS_GET_NSTYPE)
+	if err != nil {
+		if err == unix.ENOTTY {
+			t.Skipf("old kernel? (need Linux >= 4.11)")
+		}
+		t.Fatalf("failed to perform ioctl: %v", err)
+	}
+	if v != unix.CLONE_NEWNS {
+		t.Fatalf("unexpected return from ioctl; expected %v, got %v", v, unix.CLONE_NEWNS)
+	}
+}
+
 func TestIoctlGetRTCTime(t *testing.T) {
 	f, err := os.Open("/dev/rtc0")
 	if err != nil {
diff --git a/unix/zerrors_linux_386.go b/unix/zerrors_linux_386.go
index 3fb475b..2839b3d 100644
--- a/unix/zerrors_linux_386.go
+++ b/unix/zerrors_linux_386.go
@@ -1408,6 +1408,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0xb703
+	NS_GET_OWNER_UID                     = 0xb704
+	NS_GET_PARENT                        = 0xb702
+	NS_GET_USERNS                        = 0xb701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_amd64.go b/unix/zerrors_linux_amd64.go
index 9c4e19f..99e3a3d 100644
--- a/unix/zerrors_linux_amd64.go
+++ b/unix/zerrors_linux_amd64.go
@@ -1408,6 +1408,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0xb703
+	NS_GET_OWNER_UID                     = 0xb704
+	NS_GET_PARENT                        = 0xb702
+	NS_GET_USERNS                        = 0xb701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_arm.go b/unix/zerrors_linux_arm.go
index a1f038c..f5f5ee5 100644
--- a/unix/zerrors_linux_arm.go
+++ b/unix/zerrors_linux_arm.go
@@ -1406,6 +1406,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0xb703
+	NS_GET_OWNER_UID                     = 0xb704
+	NS_GET_PARENT                        = 0xb702
+	NS_GET_USERNS                        = 0xb701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_arm64.go b/unix/zerrors_linux_arm64.go
index 504ce13..64573bd 100644
--- a/unix/zerrors_linux_arm64.go
+++ b/unix/zerrors_linux_arm64.go
@@ -1409,6 +1409,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0xb703
+	NS_GET_OWNER_UID                     = 0xb704
+	NS_GET_PARENT                        = 0xb702
+	NS_GET_USERNS                        = 0xb701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_mips.go b/unix/zerrors_linux_mips.go
index 58b6429..3e948be 100644
--- a/unix/zerrors_linux_mips.go
+++ b/unix/zerrors_linux_mips.go
@@ -1406,6 +1406,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0x2000b703
+	NS_GET_OWNER_UID                     = 0x2000b704
+	NS_GET_PARENT                        = 0x2000b702
+	NS_GET_USERNS                        = 0x2000b701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_mips64.go b/unix/zerrors_linux_mips64.go
index 35e33de..8ac128b 100644
--- a/unix/zerrors_linux_mips64.go
+++ b/unix/zerrors_linux_mips64.go
@@ -1406,6 +1406,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0x2000b703
+	NS_GET_OWNER_UID                     = 0x2000b704
+	NS_GET_PARENT                        = 0x2000b702
+	NS_GET_USERNS                        = 0x2000b701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_mips64le.go b/unix/zerrors_linux_mips64le.go
index 574fcd8..e8845a7 100644
--- a/unix/zerrors_linux_mips64le.go
+++ b/unix/zerrors_linux_mips64le.go
@@ -1406,6 +1406,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0x2000b703
+	NS_GET_OWNER_UID                     = 0x2000b704
+	NS_GET_PARENT                        = 0x2000b702
+	NS_GET_USERNS                        = 0x2000b701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_mipsle.go b/unix/zerrors_linux_mipsle.go
index cdf0cf5..338c044 100644
--- a/unix/zerrors_linux_mipsle.go
+++ b/unix/zerrors_linux_mipsle.go
@@ -1406,6 +1406,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0x2000b703
+	NS_GET_OWNER_UID                     = 0x2000b704
+	NS_GET_PARENT                        = 0x2000b702
+	NS_GET_USERNS                        = 0x2000b701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_ppc64.go b/unix/zerrors_linux_ppc64.go
index eefdb32..a696532 100644
--- a/unix/zerrors_linux_ppc64.go
+++ b/unix/zerrors_linux_ppc64.go
@@ -1407,6 +1407,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80000000
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0x2000b703
+	NS_GET_OWNER_UID                     = 0x2000b704
+	NS_GET_PARENT                        = 0x2000b702
+	NS_GET_USERNS                        = 0x2000b701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_ppc64le.go b/unix/zerrors_linux_ppc64le.go
index 78db210..9197b33 100644
--- a/unix/zerrors_linux_ppc64le.go
+++ b/unix/zerrors_linux_ppc64le.go
@@ -1407,6 +1407,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80000000
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0x2000b703
+	NS_GET_OWNER_UID                     = 0x2000b704
+	NS_GET_PARENT                        = 0x2000b702
+	NS_GET_USERNS                        = 0x2000b701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_riscv64.go b/unix/zerrors_linux_riscv64.go
index 0cd07f9..d1e023e 100644
--- a/unix/zerrors_linux_riscv64.go
+++ b/unix/zerrors_linux_riscv64.go
@@ -1406,6 +1406,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0xb703
+	NS_GET_OWNER_UID                     = 0xb704
+	NS_GET_PARENT                        = 0xb702
+	NS_GET_USERNS                        = 0xb701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_s390x.go b/unix/zerrors_linux_s390x.go
index ac4f1d9..1dfacf1 100644
--- a/unix/zerrors_linux_s390x.go
+++ b/unix/zerrors_linux_s390x.go
@@ -1406,6 +1406,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0xb703
+	NS_GET_OWNER_UID                     = 0xb704
+	NS_GET_PARENT                        = 0xb702
+	NS_GET_USERNS                        = 0xb701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80
diff --git a/unix/zerrors_linux_sparc64.go b/unix/zerrors_linux_sparc64.go
index 8a12f14..b78e49f 100644
--- a/unix/zerrors_linux_sparc64.go
+++ b/unix/zerrors_linux_sparc64.go
@@ -1410,6 +1410,10 @@
 	NLM_F_ROOT                           = 0x100
 	NOFLSH                               = 0x80
 	NSFS_MAGIC                           = 0x6e736673
+	NS_GET_NSTYPE                        = 0x2000b703
+	NS_GET_OWNER_UID                     = 0x2000b704
+	NS_GET_PARENT                        = 0x2000b702
+	NS_GET_USERNS                        = 0x2000b701
 	OCFS2_SUPER_MAGIC                    = 0x7461636f
 	OCRNL                                = 0x8
 	OFDEL                                = 0x80