unix: fix last argument of pselect6 on linux

On Linux, the last argument of pselect6 system call is **not** a
sigseg_t * pointer, but instead it is a structure of the form:

    struct {
        const sigset_t *ss;     /* Pointer to signal set */
        size_t          ss_len; /* Size (in bytes) of object pointed
    };

See man 2 pselect6.

Fixes #61251

Change-Id: Id0aa122a77796713bc6d624dc395d396fbc0c5e2
GitHub-Last-Rev: cb3c6d7da9b846843a4a81898ebfdcf2e449942a
GitHub-Pull-Request: golang/sys#167
Reviewed-on: https://go-review.googlesource.com/c/sys/+/510195
Reviewed-by: Bryan Mills <bcmills@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
diff --git a/unix/linux/types.go b/unix/linux/types.go
index 469ff6b..f2cbd2d 100644
--- a/unix/linux/types.go
+++ b/unix/linux/types.go
@@ -968,6 +968,10 @@
 )
 
 type Sigset_t C.sigset_t
+type sigset_argpack struct {
+	ss    *Sigset_t
+	ssLen uintptr // Size (in bytes) of object pointed to by ss.
+}
 
 const _C__NSIG = C._NSIG
 
diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go
index 332a74b..a730878 100644
--- a/unix/syscall_linux.go
+++ b/unix/syscall_linux.go
@@ -1885,7 +1885,7 @@
 //sys	PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error)
 //sys	PivotRoot(newroot string, putold string) (err error) = SYS_PIVOT_ROOT
 //sys	Prctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (err error)
-//sys	Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) = SYS_PSELECT6
+//sys	pselect6(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *sigset_argpack) (n int, err error)
 //sys	read(fd int, p []byte) (n int, err error)
 //sys	Removexattr(path string, attr string) (err error)
 //sys	Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error)
@@ -2438,6 +2438,39 @@
 	return int(r), int(e), int(s)
 }
 
+// Pselect is a wrapper around the Linux pselect6 system call.
+// This version does not modify the timeout argument.
+func Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
+	// Per https://man7.org/linux/man-pages/man2/select.2.html#NOTES,
+	// The Linux pselect6() system call modifies its timeout argument.
+	// [Not modifying the argument] is the behavior required by POSIX.1-2001.
+	var mutableTimeout *Timespec
+	if timeout != nil {
+		mutableTimeout = new(Timespec)
+		*mutableTimeout = *timeout
+	}
+
+	// The final argument of the pselect6() system call is not a
+	// sigset_t * pointer, but is instead a structure
+	var kernelMask *sigset_argpack
+	if sigmask != nil {
+		wordBits := 32 << (^uintptr(0) >> 63) // see math.intSize
+
+		// A sigset stores one bit per signal,
+		// offset by 1 (because signal 0 does not exist).
+		// So the number of words needed is ⌈__C_NSIG - 1 / wordBits⌉.
+		sigsetWords := (_C__NSIG - 1 + wordBits - 1) / (wordBits)
+
+		sigsetBytes := uintptr(sigsetWords * (wordBits / 8))
+		kernelMask = &sigset_argpack{
+			ss:    sigmask,
+			ssLen: sigsetBytes,
+		}
+	}
+
+	return pselect6(nfd, r, w, e, mutableTimeout, kernelMask)
+}
+
 /*
  * Unimplemented
  */
diff --git a/unix/syscall_linux_amd64.go b/unix/syscall_linux_amd64.go
index 5b21fcf..70601ce 100644
--- a/unix/syscall_linux_amd64.go
+++ b/unix/syscall_linux_amd64.go
@@ -40,7 +40,7 @@
 	if timeout != nil {
 		ts = &Timespec{Sec: timeout.Sec, Nsec: timeout.Usec * 1000}
 	}
-	return Pselect(nfd, r, w, e, ts, nil)
+	return pselect6(nfd, r, w, e, ts, nil)
 }
 
 //sys	sendfile(outfd int, infd int, offset *int64, count int) (written int, err error)
diff --git a/unix/syscall_linux_arm64.go b/unix/syscall_linux_arm64.go
index a81f574..f526668 100644
--- a/unix/syscall_linux_arm64.go
+++ b/unix/syscall_linux_arm64.go
@@ -33,7 +33,7 @@
 	if timeout != nil {
 		ts = &Timespec{Sec: timeout.Sec, Nsec: timeout.Usec * 1000}
 	}
-	return Pselect(nfd, r, w, e, ts, nil)
+	return pselect6(nfd, r, w, e, ts, nil)
 }
 
 //sys	sendfile(outfd int, infd int, offset *int64, count int) (written int, err error)
diff --git a/unix/syscall_linux_loong64.go b/unix/syscall_linux_loong64.go
index 69d2d7c..f6ab02e 100644
--- a/unix/syscall_linux_loong64.go
+++ b/unix/syscall_linux_loong64.go
@@ -28,7 +28,7 @@
 	if timeout != nil {
 		ts = &Timespec{Sec: timeout.Sec, Nsec: timeout.Usec * 1000}
 	}
-	return Pselect(nfd, r, w, e, ts, nil)
+	return pselect6(nfd, r, w, e, ts, nil)
 }
 
 //sys	sendfile(outfd int, infd int, offset *int64, count int) (written int, err error)
diff --git a/unix/syscall_linux_mips64x.go b/unix/syscall_linux_mips64x.go
index 76d5640..93fe59d 100644
--- a/unix/syscall_linux_mips64x.go
+++ b/unix/syscall_linux_mips64x.go
@@ -31,7 +31,7 @@
 	if timeout != nil {
 		ts = &Timespec{Sec: timeout.Sec, Nsec: timeout.Usec * 1000}
 	}
-	return Pselect(nfd, r, w, e, ts, nil)
+	return pselect6(nfd, r, w, e, ts, nil)
 }
 
 //sys	sendfile(outfd int, infd int, offset *int64, count int) (written int, err error)
diff --git a/unix/syscall_linux_riscv64.go b/unix/syscall_linux_riscv64.go
index 35851ef..b1de100 100644
--- a/unix/syscall_linux_riscv64.go
+++ b/unix/syscall_linux_riscv64.go
@@ -32,7 +32,7 @@
 	if timeout != nil {
 		ts = &Timespec{Sec: timeout.Sec, Nsec: timeout.Usec * 1000}
 	}
-	return Pselect(nfd, r, w, e, ts, nil)
+	return pselect6(nfd, r, w, e, ts, nil)
 }
 
 //sys	sendfile(outfd int, infd int, offset *int64, count int) (written int, err error)
diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go
index 2787fe5..dee8745 100644
--- a/unix/syscall_linux_test.go
+++ b/unix/syscall_linux_test.go
@@ -444,6 +444,24 @@
 	}
 }
 
+func TestPselectWithSigmask(t *testing.T) {
+	var sigmask unix.Sigset_t
+	sigmask.Val[0] |= 1 << (uint(unix.SIGUSR1) - 1)
+	for {
+		n, err := unix.Pselect(0, nil, nil, nil, &unix.Timespec{Sec: 0, Nsec: 0}, &sigmask)
+		if err == unix.EINTR {
+			t.Logf("Pselect interrupted")
+			continue
+		} else if err != nil {
+			t.Fatalf("Pselect: %v", err)
+		}
+		if n != 0 {
+			t.Fatalf("Pselect: got %v ready file descriptors, expected 0", n)
+		}
+		break
+	}
+}
+
 func TestSchedSetaffinity(t *testing.T) {
 	var newMask unix.CPUSet
 	newMask.Zero()
diff --git a/unix/zsyscall_linux.go b/unix/zsyscall_linux.go
index 7ceec23..a07321b 100644
--- a/unix/zsyscall_linux.go
+++ b/unix/zsyscall_linux.go
@@ -1356,7 +1356,7 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
-func Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
+func pselect6(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *sigset_argpack) (n int, err error) {
 	r0, _, e1 := Syscall6(SYS_PSELECT6, uintptr(nfd), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)))
 	n = int(r0)
 	if e1 != 0 {
diff --git a/unix/ztypes_linux.go b/unix/ztypes_linux.go
index 02e2462..26ef52a 100644
--- a/unix/ztypes_linux.go
+++ b/unix/ztypes_linux.go
@@ -866,6 +866,11 @@
 	POLLNVAL = 0x20
 )
 
+type sigset_argpack struct {
+	ss    *Sigset_t
+	ssLen uintptr
+}
+
 type SignalfdSiginfo struct {
 	Signo     uint32
 	Errno     int32