runtime: disable a signal by restoring the original disposition

Fixes #13034.
Fixes #13042.
Update #9896.

Change-Id: I189f381090223dd07086848aac2d69d2c00d80c4
Reviewed-on: https://go-review.googlesource.com/18062
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/misc/cgo/testcarchive/main3.c b/misc/cgo/testcarchive/main3.c
new file mode 100644
index 0000000..2d3e565
--- /dev/null
+++ b/misc/cgo/testcarchive/main3.c
@@ -0,0 +1,153 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Test os/signal.Notify and os/signal.Reset.
+// This is a lot like misc/cgo/testcshared/main5.c.
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+
+#include "libgo3.h"
+
+static void die(const char* msg) {
+	perror(msg);
+	exit(EXIT_FAILURE);
+}
+
+static volatile sig_atomic_t sigioSeen;
+
+static void ioHandler(int signo, siginfo_t* info, void* ctxt) {
+	sigioSeen = 1;
+}
+
+int main(int argc, char** argv) {
+	int verbose;
+	struct sigaction sa;
+	int i;
+
+	verbose = argc > 2;
+	setvbuf(stdout, NULL, _IONBF, 0);
+
+	if (verbose) {
+		printf("calling sigaction\n");
+	}
+
+	memset(&sa, 0, sizeof sa);
+	sa.sa_sigaction = ioHandler;
+	if (sigemptyset(&sa.sa_mask) < 0) {
+		die("sigemptyset");
+	}
+	sa.sa_flags = SA_SIGINFO;
+	if (sigaction(SIGIO, &sa, NULL) < 0) {
+		die("sigaction");
+	}
+
+	// At this point there should not be a Go signal handler
+	// installed for SIGIO.
+
+	if (verbose) {
+		printf("raising SIGIO\n");
+	}
+
+	if (raise(SIGIO) < 0) {
+		die("raise");
+	}
+
+	if (verbose) {
+		printf("waiting for sigioSeen\n");
+	}
+
+	// Wait until the signal has been delivered.
+	i = 0;
+	while (!sigioSeen) {
+		if (sched_yield() < 0) {
+			perror("sched_yield");
+		}
+		i++;
+		if (i > 10000) {
+			fprintf(stderr, "looping too long waiting for signal\n");
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	sigioSeen = 0;
+
+	// Tell the Go code to catch SIGIO.
+
+	if (verbose) {
+		printf("calling CatchSIGIO\n");
+	}
+
+	CatchSIGIO();
+
+	if (verbose) {
+		printf("raising SIGIO\n");
+	}
+
+	if (raise(SIGIO) < 0) {
+		die("raise");
+	}
+
+	if (verbose) {
+		printf("calling SawSIGIO\n");
+	}
+
+	if (!SawSIGIO()) {
+		fprintf(stderr, "Go handler did not see SIGIO\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (sigioSeen != 0) {
+		fprintf(stderr, "C handler saw SIGIO when only Go handler should have\n");
+		exit(EXIT_FAILURE);
+	}
+
+	// Tell the Go code to stop catching SIGIO.
+
+	if (verbose) {
+		printf("calling ResetSIGIO\n");
+	}
+
+	ResetSIGIO();
+
+	if (verbose) {
+		printf("raising SIGIO\n");
+	}
+
+	if (raise(SIGIO) < 0) {
+		die("raise");
+	}
+
+	if (verbose) {
+		printf("calling SawSIGIO\n");
+	}
+
+	if (SawSIGIO()) {
+		fprintf(stderr, "Go handler saw SIGIO after Reset\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (verbose) {
+		printf("waiting for sigioSeen\n");
+	}
+
+	// Wait until the signal has been delivered.
+	i = 0;
+	while (!sigioSeen) {
+		if (sched_yield() < 0) {
+			perror("sched_yield");
+		}
+		i++;
+		if (i > 10000) {
+			fprintf(stderr, "looping too long waiting for signal\n");
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	printf("PASS\n");
+	return 0;
+}
diff --git a/misc/cgo/testcarchive/src/libgo3/libgo3.go b/misc/cgo/testcarchive/src/libgo3/libgo3.go
new file mode 100644
index 0000000..94e5d21
--- /dev/null
+++ b/misc/cgo/testcarchive/src/libgo3/libgo3.go
@@ -0,0 +1,44 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "C"
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+)
+
+// The channel used to read SIGIO signals.
+var sigioChan chan os.Signal
+
+// CatchSIGIO starts catching SIGIO signals.
+//export CatchSIGIO
+func CatchSIGIO() {
+	sigioChan = make(chan os.Signal, 1)
+	signal.Notify(sigioChan, syscall.SIGIO)
+}
+
+// ResetSIGIO stops catching SIGIO signals.
+//export ResetSIGIO
+func ResetSIGIO() {
+	signal.Reset(syscall.SIGIO)
+}
+
+// SawSIGIO returns whether we saw a SIGIO within a brief pause.
+//export SawSIGIO
+func SawSIGIO() C.int {
+	select {
+	case <-sigioChan:
+		return 1
+	case <-time.After(100 * time.Millisecond):
+		return 0
+	}
+}
+
+func main() {
+}
diff --git a/misc/cgo/testcarchive/test.bash b/misc/cgo/testcarchive/test.bash
index d561c02..053833a 100755
--- a/misc/cgo/testcarchive/test.bash
+++ b/misc/cgo/testcarchive/test.bash
@@ -30,7 +30,7 @@
 GOPATH=$(pwd) go install -buildmode=c-archive libgo
 $(go env CC) $(go env GOGCCFLAGS) $ccargs -o testp main.c pkg/$(go env GOOS)_$(go env GOARCH)/libgo.a
 if ! $bin arg1 arg2; then
-    echo "FAIL test1"
+    echo "FAIL test1a"
     status=1
 fi
 rm -f libgo.a libgo.h testp
@@ -41,7 +41,7 @@
 GOPATH=$(pwd) go build -buildmode=c-archive src/libgo/libgo.go
 $(go env CC) $(go env GOGCCFLAGS) $ccargs -o testp main.c libgo.a
 if ! $bin arg1 arg2; then
-    echo "FAIL test2"
+    echo "FAIL test1b"
     status=1
 fi
 rm -f libgo.a libgo.h testp
@@ -49,24 +49,32 @@
 GOPATH=$(pwd) go build -buildmode=c-archive -o libgo.a libgo
 $(go env CC) $(go env GOGCCFLAGS) $ccargs -o testp main.c libgo.a
 if ! $bin arg1 arg2; then
-    echo "FAIL test3"
+    echo "FAIL test1c"
     status=1
 fi
 rm -rf libgo.a libgo.h testp pkg
 
 case "$(go env GOOS)/$(go env GOARCH)" in
 "darwin/arm" | "darwin/arm64")
-    echo "Skipping test4; see https://golang.org/issue/13701"
+    echo "Skipping test2; see https://golang.org/issue/13701"
     ;;
 *)
     GOPATH=$(pwd) go build -buildmode=c-archive -o libgo2.a libgo2
     $(go env CC) $(go env GOGCCFLAGS) $ccargs -o testp main2.c libgo2.a
     if ! $bin; then
-        echo "FAIL test4"
+        echo "FAIL test2"
         status=1
     fi
     rm -rf libgo2.a libgo2.h testp pkg
     ;;
 esac
 
+GOPATH=$(pwd) go build -buildmode=c-archive -o libgo3.a libgo3
+$(go env CC) $(go env GOGCCFLAGS) $ccargs -o testp main3.c libgo3.a
+if ! $bin; then
+    echo "FAIL test3"
+    status=1
+fi
+rm -rf libgo3.a libgo3.h testp pkg
+
 exit $status
diff --git a/misc/cgo/testcshared/main5.c b/misc/cgo/testcshared/main5.c
new file mode 100644
index 0000000..50ddb47
--- /dev/null
+++ b/misc/cgo/testcshared/main5.c
@@ -0,0 +1,197 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Test that a signal handler works in non-Go code when using
+// os/signal.Notify.
+// This is a lot like misc/cgo/testcarchive/main3.c.
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <dlfcn.h>
+
+static void die(const char* msg) {
+	perror(msg);
+	exit(EXIT_FAILURE);
+}
+
+static volatile sig_atomic_t sigioSeen;
+
+static void ioHandler(int signo, siginfo_t* info, void* ctxt) {
+	sigioSeen = 1;
+}
+
+int main(int argc, char** argv) {
+	int verbose;
+	struct sigaction sa;
+	void* handle;
+	void (*fn1)(void);
+	int (*sawSIGIO)(void);
+	int i;
+
+	verbose = argc > 2;
+	setvbuf(stdout, NULL, _IONBF, 0);
+
+	if (verbose) {
+		printf("calling sigaction\n");
+	}
+
+	memset(&sa, 0, sizeof sa);
+	sa.sa_sigaction = ioHandler;
+	if (sigemptyset(&sa.sa_mask) < 0) {
+		die("sigemptyset");
+	}
+	sa.sa_flags = SA_SIGINFO;
+	if (sigaction(SIGIO, &sa, NULL) < 0) {
+		die("sigaction");
+	}
+
+	if (verbose) {
+		printf("calling dlopen\n");
+	}
+
+	handle = dlopen(argv[1], RTLD_NOW | RTLD_GLOBAL);
+	if (handle == NULL) {
+		fprintf(stderr, "%s\n", dlerror());
+		exit(EXIT_FAILURE);
+	}
+
+	// At this point there should not be a Go signal handler
+	// installed for SIGIO.
+
+	if (verbose) {
+		printf("raising SIGIO\n");
+	}
+
+	if (raise(SIGIO) < 0) {
+		die("raise");
+	}
+
+	if (verbose) {
+		printf("waiting for sigioSeen\n");
+	}
+
+	// Wait until the signal has been delivered.
+	i = 0;
+	while (!sigioSeen) {
+		if (sched_yield() < 0) {
+			perror("sched_yield");
+		}
+		i++;
+		if (i > 10000) {
+			fprintf(stderr, "looping too long waiting for signal\n");
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	sigioSeen = 0;
+
+	// Tell the Go code to catch SIGIO.
+
+	if (verbose) {
+		printf("calling dlsym\n");
+	}
+
+	fn1 = (void(*)(void))dlsym(handle, "CatchSIGIO");
+	if (fn1 == NULL) {
+		fprintf(stderr, "%s\n", dlerror());
+		exit(EXIT_FAILURE);
+	}
+
+	if (verbose) {
+		printf("calling CatchSIGIO\n");
+	}
+
+	fn1();
+
+	if (verbose) {
+		printf("raising SIGIO\n");
+	}
+
+	if (raise(SIGIO) < 0) {
+		die("raise");
+	}
+
+	if (verbose) {
+		printf("calling dlsym\n");
+	}
+
+	// Check that the Go code saw SIGIO.
+	sawSIGIO = (int (*)(void))dlsym(handle, "SawSIGIO");
+	if (sawSIGIO == NULL) {
+		fprintf(stderr, "%s\n", dlerror());
+		exit(EXIT_FAILURE);
+	}
+
+	if (verbose) {
+		printf("calling SawSIGIO\n");
+	}
+
+	if (!sawSIGIO()) {
+		fprintf(stderr, "Go handler did not see SIGIO\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (sigioSeen != 0) {
+		fprintf(stderr, "C handler saw SIGIO when only Go handler should have\n");
+		exit(EXIT_FAILURE);
+	}
+
+	// Tell the Go code to stop catching SIGIO.
+
+	if (verbose) {
+		printf("calling dlsym\n");
+	}
+
+	fn1 = (void(*)(void))dlsym(handle, "ResetSIGIO");
+	if (fn1 == NULL) {
+		fprintf(stderr, "%s\n", dlerror());
+		exit(EXIT_FAILURE);
+	}
+
+	if (verbose) {
+		printf("calling ResetSIGIO\n");
+	}
+
+	fn1();
+
+	if (verbose) {
+		printf("raising SIGIO\n");
+	}
+
+	if (raise(SIGIO) < 0) {
+		die("raise");
+	}
+
+	if (verbose) {
+		printf("calling SawSIGIO\n");
+	}
+
+	if (sawSIGIO()) {
+		fprintf(stderr, "Go handler saw SIGIO after Reset\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (verbose) {
+		printf("waiting for sigioSeen\n");
+	}
+
+	// Wait until the signal has been delivered.
+	i = 0;
+	while (!sigioSeen) {
+		if (sched_yield() < 0) {
+			perror("sched_yield");
+		}
+		i++;
+		if (i > 10000) {
+			fprintf(stderr, "looping too long waiting for signal\n");
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	printf("PASS\n");
+	return 0;
+}
diff --git a/misc/cgo/testcshared/src/libgo5/libgo5.go b/misc/cgo/testcshared/src/libgo5/libgo5.go
new file mode 100644
index 0000000..94e5d21
--- /dev/null
+++ b/misc/cgo/testcshared/src/libgo5/libgo5.go
@@ -0,0 +1,44 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "C"
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+)
+
+// The channel used to read SIGIO signals.
+var sigioChan chan os.Signal
+
+// CatchSIGIO starts catching SIGIO signals.
+//export CatchSIGIO
+func CatchSIGIO() {
+	sigioChan = make(chan os.Signal, 1)
+	signal.Notify(sigioChan, syscall.SIGIO)
+}
+
+// ResetSIGIO stops catching SIGIO signals.
+//export ResetSIGIO
+func ResetSIGIO() {
+	signal.Reset(syscall.SIGIO)
+}
+
+// SawSIGIO returns whether we saw a SIGIO within a brief pause.
+//export SawSIGIO
+func SawSIGIO() C.int {
+	select {
+	case <-sigioChan:
+		return 1
+	case <-time.After(100 * time.Millisecond):
+		return 0
+	}
+}
+
+func main() {
+}
diff --git a/misc/cgo/testcshared/test.bash b/misc/cgo/testcshared/test.bash
index 162a62d..ac852a00 100755
--- a/misc/cgo/testcshared/test.bash
+++ b/misc/cgo/testcshared/test.bash
@@ -33,8 +33,9 @@
 androidpath=/data/local/tmp/testcshared-$$
 
 function cleanup() {
-	rm -f libgo.$libext libgo2.$libext libgo4.$libext libgo.h libgo4.h
-	rm -f testp testp2 testp3 testp4
+	rm -f libgo.$libext libgo2.$libext libgo4.$libext libgo5.$libext
+	rm -f libgo.h libgo4.h libgo5.h
+	rm -f testp testp2 testp3 testp4 testp5
 	rm -rf pkg "${goroot}/${installdir}"
 
 	if [ "$goos" == "android" ]; then
@@ -161,6 +162,21 @@
     status=1
 fi
 
+# test5: tests signal handlers with os/signal.Notify
+GOPATH=$(pwd) go build -buildmode=c-shared $suffix -o libgo5.$libext libgo5
+binpush libgo5.$libext
+$(go env CC) ${GOGCCFLAGS} -pthread -o testp5 main5.c -ldl
+binpush testp5
+output=$(run ./testp5 ./libgo5.$libext 2>&1)
+if test "$output" != "PASS"; then
+    echo "FAIL test5 got ${output}"
+    if test "$goos" != "android"; then
+	echo "re-running test5 in verbose mode"
+	./testp5 ./libgo5.$libext verbose
+    fi
+    status=1
+fi
+
 if test $status = 0; then
     echo "ok"
 fi
diff --git a/src/os/signal/doc.go b/src/os/signal/doc.go
index 4a6d1d5..b36c16c 100644
--- a/src/os/signal/doc.go
+++ b/src/os/signal/doc.go
@@ -162,13 +162,20 @@
 the existing signal handler instead of the Go signal handler.
 
 Go code built with -buildmode=c-archive or -buildmode=c-shared will
-not install any other signal handlers. TODO: Describe Notify behavior.
+not install any other signal handlers by default. If there is an
+existing signal handler, the Go runtime will turn on the SA_ONSTACK
+flag and otherwise keep the signal handler. If Notify is called for an
+asynchronous signal, a Go signal handler will be installed for that
+signal. If, later, Reset is called for that signal, the original
+handling for that signal will be reinstalled, restoring the non-Go
+signal handler if any.
 
-Go code built otherwise will install a signal handler for the
-asynchronous signals listed above, and save any existing signal
-handler. If a signal is delivered to a non-Go thread, it will act as
-described above, except that if there is an existing non-Go signal
-handler, that handler will be installed before raising the signal.
+Go code built without -buildmode=c-archive or -buildmode=c-shared will
+install a signal handler for the asynchronous signals listed above,
+and save any existing signal handler. If a signal is delivered to a
+non-Go thread, it will act as described above, except that if there is
+an existing non-Go signal handler, that handler will be installed
+before raising the signal.
 
 Windows
 
diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go
index 9549d1f..c357f6e 100644
--- a/src/runtime/runtime2.go
+++ b/src/runtime/runtime2.go
@@ -482,7 +482,6 @@
 	_SigPanic                // if the signal is from the kernel, panic
 	_SigDefault              // if the signal isn't explicitly requested, don't monitor it
 	_SigHandling             // our signal handler is registered
-	_SigIgnored              // the signal was ignored before we registered for it
 	_SigGoExit               // cause all runtime procs to exit (only used on Plan 9).
 	_SigSetStack             // add SA_ONSTACK to libc handler
 	_SigUnblock              // unblocked in minit
diff --git a/src/runtime/signal1_unix.go b/src/runtime/signal1_unix.go
index 3bb3ed8..468d6f6 100644
--- a/src/runtime/signal1_unix.go
+++ b/src/runtime/signal1_unix.go
@@ -49,13 +49,13 @@
 			continue
 		}
 		fwdSig[i] = getsig(i)
+
 		// For some signals, we respect an inherited SIG_IGN handler
 		// rather than insist on installing our own default handler.
 		// Even these signals can be fetched using the os/signal package.
 		switch i {
 		case _SIGHUP, _SIGINT:
-			if getsig(i) == _SIG_IGN {
-				t.flags = _SigNotify | _SigIgnored
+			if fwdSig[i] == _SIG_IGN {
 				continue
 			}
 		}
@@ -90,9 +90,6 @@
 		<-maskUpdatedChan
 		if t.flags&_SigHandling == 0 {
 			t.flags |= _SigHandling
-			if getsig(int32(sig)) == _SIG_IGN {
-				t.flags |= _SigIgnored
-			}
 			setsig(int32(sig), funcPC(sighandler), true)
 		}
 	}
@@ -110,11 +107,7 @@
 		<-maskUpdatedChan
 		if t.flags&_SigHandling != 0 {
 			t.flags &^= _SigHandling
-			if t.flags&_SigIgnored != 0 {
-				setsig(int32(sig), _SIG_IGN, true)
-			} else {
-				setsig(int32(sig), _SIG_DFL, true)
-			}
+			setsig(int32(sig), fwdSig[sig], true)
 		}
 	}
 }