os/signal: add Stop, be careful about SIGHUP

Fixes #4268.
Fixes #4491.

R=golang-dev, nightlyone, fullung, r
CC=golang-dev
https://golang.org/cl/7546048
diff --git a/src/pkg/os/signal/sig.s b/src/pkg/os/signal/sig.s
index d1984cf..7d0c92b 100644
--- a/src/pkg/os/signal/sig.s
+++ b/src/pkg/os/signal/sig.s
@@ -8,6 +8,9 @@
 #define JMP B
 #endif
 
+TEXT ·signal_disable(SB),7,$0
+	JMP runtime·signal_disable(SB)
+
 TEXT ·signal_enable(SB),7,$0
 	JMP runtime·signal_enable(SB)
 
diff --git a/src/pkg/os/signal/signal.go b/src/pkg/os/signal/signal.go
index 0861f59..3004275 100644
--- a/src/pkg/os/signal/signal.go
+++ b/src/pkg/os/signal/signal.go
@@ -14,13 +14,20 @@
 
 var handlers struct {
 	sync.Mutex
-	list []handler
+	m   map[chan<- os.Signal]*handler
+	ref [numSig]int64
 }
 
 type handler struct {
-	c   chan<- os.Signal
-	sig os.Signal
-	all bool
+	mask [(numSig + 31) / 32]uint32
+}
+
+func (h *handler) want(sig int) bool {
+	return (h.mask[sig/32]>>uint(sig&31))&1 != 0
+}
+
+func (h *handler) set(sig int) {
+	h.mask[sig/32] |= 1 << uint(sig&31)
 }
 
 // Notify causes package signal to relay incoming signals to c.
@@ -32,6 +39,13 @@
 // signal rate.  For a channel used for notification of just one signal value,
 // a buffer of size 1 is sufficient.
 //
+// It is allowed to call Notify multiple times with the same channel:
+// each call expands the set of signals sent to that channel.
+// The only way to remove signals from the set is to call Stop.
+//
+// It is allowed to call Notify multiple times with different channels
+// and the same signals: each channel receives copies of incoming
+// signals independently.
 func Notify(c chan<- os.Signal, sig ...os.Signal) {
 	if c == nil {
 		panic("os/signal: Notify using nil channel")
@@ -39,32 +53,77 @@
 
 	handlers.Lock()
 	defer handlers.Unlock()
+
+	h := handlers.m[c]
+	if h == nil {
+		if handlers.m == nil {
+			handlers.m = make(map[chan<- os.Signal]*handler)
+		}
+		h = new(handler)
+		handlers.m[c] = h
+	}
+
+	add := func(n int) {
+		if n < 0 {
+			return
+		}
+		if !h.want(n) {
+			h.set(n)
+			if handlers.ref[n] == 0 {
+				enableSignal(n)
+			}
+			handlers.ref[n]++
+		}
+	}
+
 	if len(sig) == 0 {
-		enableSignal(nil)
-		handlers.list = append(handlers.list, handler{c: c, all: true})
+		for n := 0; n < numSig; n++ {
+			add(n)
+		}
 	} else {
 		for _, s := range sig {
-			// We use nil as a special wildcard value for enableSignal,
-			// so filter it out of the list of arguments.  This is safe because
-			// we will never get an incoming nil signal, so discarding the
-			// registration cannot affect the observed behavior.
-			if s != nil {
-				enableSignal(s)
-				handlers.list = append(handlers.list, handler{c: c, sig: s})
+			add(signum(s))
+		}
+	}
+}
+
+// Stop causes package signal to stop relaying incoming signals to c.
+// It undoes the effect of all prior calls to Notify using c.
+// When Stop returns, it is guaranteed that c will receive no more signals.
+func Stop(c chan<- os.Signal) {
+	handlers.Lock()
+	defer handlers.Unlock()
+
+	h := handlers.m[c]
+	if h == nil {
+		return
+	}
+	delete(handlers.m, c)
+
+	for n := 0; n < numSig; n++ {
+		if h.want(n) {
+			handlers.ref[n]--
+			if handlers.ref[n] == 0 {
+				disableSignal(n)
 			}
 		}
 	}
 }
 
 func process(sig os.Signal) {
+	n := signum(sig)
+	if n < 0 {
+		return
+	}
+
 	handlers.Lock()
 	defer handlers.Unlock()
 
-	for _, h := range handlers.list {
-		if h.all || h.sig == sig {
+	for c, h := range handlers.m {
+		if h.want(n) {
 			// send but do not block for it
 			select {
-			case h.c <- sig:
+			case c <- sig:
 			default:
 			}
 		}
diff --git a/src/pkg/os/signal/signal_stub.go b/src/pkg/os/signal/signal_stub.go
index fc227cf..d0a6935 100644
--- a/src/pkg/os/signal/signal_stub.go
+++ b/src/pkg/os/signal/signal_stub.go
@@ -8,4 +8,10 @@
 
 import "os"
 
-func enableSignal(sig os.Signal) {}
+const numSig = 0
+
+func signum(sig os.Signal) int { return -1 }
+
+func disableSignal(int) {}
+
+func enableSignal(int) {}
diff --git a/src/pkg/os/signal/signal_test.go b/src/pkg/os/signal/signal_test.go
index 93e5ab9..d138333 100644
--- a/src/pkg/os/signal/signal_test.go
+++ b/src/pkg/os/signal/signal_test.go
@@ -7,15 +7,17 @@
 package signal
 
 import (
+	"flag"
+	"io/ioutil"
 	"os"
+	"os/exec"
 	"runtime"
+	"strconv"
 	"syscall"
 	"testing"
 	"time"
 )
 
-const sighup = syscall.SIGHUP
-
 func waitSig(t *testing.T, c <-chan os.Signal, sig os.Signal) {
 	select {
 	case s := <-c:
@@ -27,15 +29,17 @@
 	}
 }
 
+// Test that basic signal handling works.
 func TestSignal(t *testing.T) {
 	// Ask for SIGHUP
 	c := make(chan os.Signal, 1)
-	Notify(c, sighup)
+	Notify(c, syscall.SIGHUP)
+	defer Stop(c)
 
 	t.Logf("sighup...")
 	// Send this process a SIGHUP
-	syscall.Kill(syscall.Getpid(), sighup)
-	waitSig(t, c, sighup)
+	syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
+	waitSig(t, c, syscall.SIGHUP)
 
 	// Ask for everything we can get.
 	c1 := make(chan os.Signal, 1)
@@ -71,6 +75,7 @@
 	go func() {
 		sig := make(chan os.Signal, 1)
 		Notify(sig, syscall.SIGUSR1)
+		defer Stop(sig)
 	Loop:
 		for {
 			select {
@@ -103,3 +108,101 @@
 	// Sleep for a while to reduce the possibility of the failure.
 	time.Sleep(10 * time.Millisecond)
 }
+
+var sendUncaughtSighup = flag.Int("send_uncaught_sighup", 0, "send uncaught SIGHUP during TestStop")
+
+// Test that Stop cancels the channel's registrations.
+func TestStop(t *testing.T) {
+	sigs := []syscall.Signal{
+		syscall.SIGWINCH,
+		syscall.SIGHUP,
+	}
+
+	for _, sig := range sigs {
+		// Send the signal.
+		// If it's SIGWINCH, we should not see it.
+		// If it's SIGHUP, maybe we'll die. Let the flag tell us what to do.
+		if sig != syscall.SIGHUP || *sendUncaughtSighup == 1 {
+			syscall.Kill(syscall.Getpid(), sig)
+		}
+		time.Sleep(10 * time.Millisecond)
+
+		// Ask for signal
+		c := make(chan os.Signal, 1)
+		Notify(c, sig)
+		defer Stop(c)
+
+		// Send this process that signal
+		syscall.Kill(syscall.Getpid(), sig)
+		waitSig(t, c, sig)
+
+		Stop(c)
+		select {
+		case s := <-c:
+			t.Fatalf("unexpected signal %v", s)
+		case <-time.After(10 * time.Millisecond):
+			// nothing to read - good
+		}
+
+		// Send the signal.
+		// If it's SIGWINCH, we should not see it.
+		// If it's SIGHUP, maybe we'll die. Let the flag tell us what to do.
+		if sig != syscall.SIGHUP || *sendUncaughtSighup == 2 {
+			syscall.Kill(syscall.Getpid(), sig)
+		}
+
+		select {
+		case s := <-c:
+			t.Fatalf("unexpected signal %v", s)
+		case <-time.After(10 * time.Millisecond):
+			// nothing to read - good
+		}
+	}
+}
+
+// Test that when run under nohup, an uncaught SIGHUP does not kill the program,
+// but a
+func TestNohup(t *testing.T) {
+	// Ugly: ask for SIGHUP so that child will not have no-hup set
+	// even if test is running under nohup environment.
+	// We have no intention of reading from c.
+	c := make(chan os.Signal, 1)
+	Notify(c, syscall.SIGHUP)
+
+	// When run without nohup, the test should crash on an uncaught SIGHUP.
+	// When run under nohup, the test should ignore uncaught SIGHUPs,
+	// because the runtime is not supposed to be listening for them.
+	// Either way, TestStop should still be able to catch them when it wants them
+	// and then when it stops wanting them, the original behavior should resume.
+	//
+	// send_uncaught_sighup=1 sends the SIGHUP before starting to listen for SIGHUPs.
+	// send_uncaught_sighup=2 sends the SIGHUP after no longer listening for SIGHUPs.
+	//
+	// Both should fail without nohup and succeed with nohup.
+
+	for i := 1; i <= 2; i++ {
+		out, err := exec.Command(os.Args[0], "-test.run=TestStop", "-send_uncaught_sighup="+strconv.Itoa(i)).CombinedOutput()
+		if err == nil {
+			t.Fatalf("ran test with -send_uncaught_sighup=%d and it succeeded: expected failure.\nOutput:\n%s", i, out)
+		}
+	}
+
+	Stop(c)
+
+	// Again, this time with nohup, assuming we can find it.
+	_, err := os.Stat("/usr/bin/nohup")
+	if err != nil {
+		t.Skip("cannot find nohup; skipping second half of test")
+	}
+
+	for i := 1; i <= 2; i++ {
+		os.Remove("nohup.out")
+		out, err := exec.Command("/usr/bin/nohup", os.Args[0], "-test.run=TestStop", "-send_uncaught_sighup="+strconv.Itoa(i)).CombinedOutput()
+
+		data, _ := ioutil.ReadFile("nohup.out")
+		os.Remove("nohup.out")
+		if err != nil {
+			t.Fatalf("ran test with -send_uncaught_sighup=%d under nohup and it failed: expected success.\nError: %v\nOutput:\n%s%s", i, err, out, data)
+		}
+	}
+}
diff --git a/src/pkg/os/signal/signal_unix.go b/src/pkg/os/signal/signal_unix.go
index 20ee5f2..6b4c8ab 100644
--- a/src/pkg/os/signal/signal_unix.go
+++ b/src/pkg/os/signal/signal_unix.go
@@ -12,6 +12,7 @@
 )
 
 // In assembly.
+func signal_disable(uint32)
 func signal_enable(uint32)
 func signal_recv() uint32
 
@@ -26,13 +27,27 @@
 	go loop()
 }
 
-func enableSignal(sig os.Signal) {
+const (
+	numSig = 65 // max across all systems
+)
+
+func signum(sig os.Signal) int {
 	switch sig := sig.(type) {
-	case nil:
-		signal_enable(^uint32(0))
 	case syscall.Signal:
-		signal_enable(uint32(sig))
+		i := int(sig)
+		if i < 0 || i >= numSig {
+			return -1
+		}
+		return i
 	default:
-		// Can ignore: this signal (whatever it is) will never come in.
+		return -1
 	}
 }
+
+func enableSignal(sig int) {
+	signal_enable(uint32(sig))
+}
+
+func disableSignal(sig int) {
+	signal_disable(uint32(sig))
+}
diff --git a/src/pkg/runtime/os_plan9_386.c b/src/pkg/runtime/os_plan9_386.c
index 17bc117..3d8b43a 100644
--- a/src/pkg/runtime/os_plan9_386.c
+++ b/src/pkg/runtime/os_plan9_386.c
@@ -112,6 +112,12 @@
 }
 
 void
+runtime·sigdisable(uint32 sig)
+{
+	USED(sig);
+}
+
+void
 runtime·resetcpuprofiler(int32 hz)
 {
 	// TODO: Enable profiling interrupts.
diff --git a/src/pkg/runtime/os_plan9_amd64.c b/src/pkg/runtime/os_plan9_amd64.c
index e4f946a..acdf65d 100644
--- a/src/pkg/runtime/os_plan9_amd64.c
+++ b/src/pkg/runtime/os_plan9_amd64.c
@@ -119,6 +119,12 @@
 }
 
 void
+runtime·sigdisable(uint32 sig)
+{
+	USED(sig);
+}
+
+void
 runtime·resetcpuprofiler(int32 hz)
 {
 	// TODO: Enable profiling interrupts.
diff --git a/src/pkg/runtime/os_windows_386.c b/src/pkg/runtime/os_windows_386.c
index d76d5bf..fc75eb3 100644
--- a/src/pkg/runtime/os_windows_386.c
+++ b/src/pkg/runtime/os_windows_386.c
@@ -91,6 +91,12 @@
 }
 
 void
+runtime·sigdisable(uint32 sig)
+{
+	USED(sig);
+}
+
+void
 runtime·dosigprof(Context *r, G *gp)
 {
 	runtime·sigprof((uint8*)r->Eip, (uint8*)r->Esp, nil, gp);
diff --git a/src/pkg/runtime/os_windows_amd64.c b/src/pkg/runtime/os_windows_amd64.c
index 3729aa5..7ed3346 100644
--- a/src/pkg/runtime/os_windows_amd64.c
+++ b/src/pkg/runtime/os_windows_amd64.c
@@ -98,6 +98,12 @@
 }
 
 void
+runtime·sigdisable(uint32 sig)
+{
+	USED(sig);
+}
+
+void
 runtime·dosigprof(Context *r, G *gp)
 {
 	runtime·sigprof((uint8*)r->Rip, (uint8*)r->Rsp, nil, gp);
diff --git a/src/pkg/runtime/runtime.h b/src/pkg/runtime/runtime.h
index e2c35e1..e0da57b 100644
--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -384,6 +384,8 @@
 	SigThrow = 1<<2,	// if signal.Notify doesn't take it, exit loudly
 	SigPanic = 1<<3,	// if the signal is from the kernel, panic
 	SigDefault = 1<<4,	// if the signal isn't explicitly requested, don't monitor it
+	SigHandling = 1<<5,	// our signal handler is registered
+	SigIgnored = 1<<6,	// the signal was ignored before we registered for it
 };
 
 // NOTE(rsc): keep in sync with extern.go:/type.Func.
@@ -696,6 +698,7 @@
 String	runtime·gostringw(uint16*);
 void	runtime·initsig(void);
 void	runtime·sigenable(uint32 sig);
+void	runtime·sigdisable(uint32 sig);
 int32	runtime·gotraceback(void);
 void	runtime·goroutineheader(G*);
 void	runtime·traceback(uint8 *pc, uint8 *sp, uint8 *lr, G* gp);
diff --git a/src/pkg/runtime/signal_unix.c b/src/pkg/runtime/signal_unix.c
index f3542ac..5d0bcbd 100644
--- a/src/pkg/runtime/signal_unix.c
+++ b/src/pkg/runtime/signal_unix.c
@@ -22,6 +22,20 @@
 		t = &runtime·sigtab[i];
 		if((t->flags == 0) || (t->flags & SigDefault))
 			continue;
+
+		// 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:
+		case SIGINT:
+			if(runtime·getsig(i) == SIG_IGN) {
+				t->flags = SigNotify | SigIgnored;
+				continue;
+			}
+		}
+
+		t->flags |= SigHandling;
 		runtime·setsig(i, runtime·sighandler, true);
 	}
 }
@@ -29,18 +43,35 @@
 void
 runtime·sigenable(uint32 sig)
 {
-	int32 i;
 	SigTab *t;
 
-	for(i = 0; i<NSIG; i++) {
-		// ~0 means all signals.
-		if(~sig == 0 || i == sig) {
-			t = &runtime·sigtab[i];
-			if(t->flags & SigDefault) {
-				runtime·setsig(i, runtime·sighandler, true);
-				t->flags &= ~SigDefault;  // make this idempotent
-			}
-		}
+	if(sig >= NSIG)
+		return;
+
+	t = &runtime·sigtab[sig];
+	if((t->flags & SigNotify) && !(t->flags & SigHandling)) {
+		t->flags |= SigHandling;
+		if(runtime·getsig(sig) == SIG_IGN)
+			t->flags |= SigIgnored;
+		runtime·setsig(sig, runtime·sighandler, true);
+	}
+}
+
+void
+runtime·sigdisable(uint32 sig)
+{
+	SigTab *t;
+
+	if(sig >= NSIG)
+		return;
+
+	t = &runtime·sigtab[sig];
+	if((t->flags & SigNotify) && (t->flags & SigHandling)) {
+		t->flags &= ~SigHandling;
+		if(t->flags & SigIgnored)
+			runtime·setsig(sig, SIG_IGN, true);
+		else
+			runtime·setsig(sig, SIG_DFL, true);
 	}
 }
 
diff --git a/src/pkg/runtime/sigqueue.goc b/src/pkg/runtime/sigqueue.goc
index ab5f312..7e08368 100644
--- a/src/pkg/runtime/sigqueue.goc
+++ b/src/pkg/runtime/sigqueue.goc
@@ -133,8 +133,6 @@
 
 // Must only be called from a single goroutine at a time.
 func signal_enable(s uint32) {
-	int32 i;
-
 	if(!sig.inuse) {
 		// The first call to signal_enable is for us
 		// to use for initialization.  It does not pass
@@ -144,16 +142,16 @@
 		return;
 	}
 	
-	if(~s == 0) {
-		// Special case: want everything.
-		for(i=0; i<nelem(sig.wanted); i++)
-			sig.wanted[i] = ~(uint32)0;
-		runtime·sigenable(s);
-		return;
-	}
-
 	if(s >= nelem(sig.wanted)*32)
 		return;
 	sig.wanted[s/32] |= 1U<<(s&31);
 	runtime·sigenable(s);
 }
+
+// Must only be called from a single goroutine at a time.
+func signal_disable(s uint32) {
+	if(s >= nelem(sig.wanted)*32)
+		return;
+	sig.wanted[s/32] &= ~(1U<<(s&31));
+	runtime·sigdisable(s);
+}