BUG/MINOR: listeners: fix suspend/resume of inherited FDs

FDs inherited from a parent process do not deal well with suspend/resume
since commit 59b5da487 ("BUG/MEDIUM: listener: never suspend inherited
sockets") introduced in 2.3. The problem is that we now report that they
cannot be suspended at all, and they return a failure. As such, if a new
process fails to bind and sends SIGTTOU to the previous process, that
one will notice the failure and instantly switch to soft-stop, leaving
no chance to the new process to give up later and signal its failure.

What we need to do, however, is to stop receiving new connections from
such inherited FDs, which just means that the FD must be unsubscribed
from the poller (and resubscribed later if finally it has to stay).
With this a new process can start on the already bound FD without
problem thanks to the absence of polling, and when the old process
stops the new process will be alone on it.

This may be backported as far as 2.4.

(cherry picked from commit 64763342aa0d620e38e29387441ecd78e10c660b)
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit 0f6663b9acce9a9fbeafbac3a56f17fbab0b741c)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit dad6068c35b0a94521c29535808add46837c5d65)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit 036e11d0d0f7f9ac9069cce172967901b37dd5a6)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 93efcdc..c423b88 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -754,22 +754,24 @@
 }
 
 /* Suspend a receiver. Returns < 0 in case of failure, 0 if the receiver
- * was totally stopped, or > 0 if correctly suspended.
+ * was totally stopped, or > 0 if correctly suspended. Note that inherited FDs
+ * are neither suspended nor resumed, we only enable/disable polling on them.
  */
 static int tcp_suspend_receiver(struct receiver *rx)
 {
 	const struct sockaddr sa = { .sa_family = AF_UNSPEC };
 	int ret;
 
-	/* we never do that with a shared FD otherwise we'd break it in the
+	/* We never disconnect a shared FD otherwise we'd break it in the
 	 * parent process and any possible subsequent worker inheriting it.
+	 * Thus we just stop receiving from it.
 	 */
 	if (rx->flags & RX_F_INHERITED)
-		return -1;
+		goto done;
 
 	if (connect(rx->fd, &sa, sizeof(sa)) < 0)
 		goto check_already_done;
-
+ done:
 	fd_stop_recv(rx->fd);
 	return 1;
 
@@ -790,7 +792,8 @@
 }
 
 /* Resume a receiver. Returns < 0 in case of failure, 0 if the receiver
- * was totally stopped, or > 0 if correctly suspended.
+ * was totally stopped, or > 0 if correctly resumed. Note that inherited FDs
+ * are neither suspended nor resumed, we only enable/disable polling on them.
  */
 static int tcp_resume_receiver(struct receiver *rx)
 {
@@ -799,7 +802,7 @@
 	if (rx->fd < 0)
 		return 0;
 
-	if (listen(rx->fd, listener_backlog(l)) == 0) {
+	if ((rx->flags & RX_F_INHERITED) || listen(rx->fd, listener_backlog(l)) == 0) {
 		fd_want_recv(l->rx.fd);
 		return 1;
 	}
diff --git a/src/proto_udp.c b/src/proto_udp.c
index f07170f..55ef649 100644
--- a/src/proto_udp.c
+++ b/src/proto_udp.c
@@ -175,7 +175,9 @@
  * suspend the receiver, we want it to stop receiving traffic, which means that
  * the socket must be unhashed from the kernel's socket table. The simple way
  * to do this is to connect to any address that is reachable and will not be
- * used by regular traffic, and a great one is reconnecting to self.
+ * used by regular traffic, and a great one is reconnecting to self. Note that
+ * inherited FDs are neither suspended nor resumed, we only enable/disable
+ * polling on them.
  */
 int udp_suspend_receiver(struct receiver *rx)
 {
@@ -189,14 +191,14 @@
 	 * parent process and any possible subsequent worker inheriting it.
 	 */
 	if (rx->flags & RX_F_INHERITED)
-		return -1;
+		goto done;
 
 	if (getsockname(rx->fd, (struct sockaddr *)&ss, &len) < 0)
 		return -1;
 
 	if (connect(rx->fd, (struct sockaddr *)&ss, len) < 0)
 		return -1;
-
+ done:
 	/* not necessary but may make debugging clearer */
 	fd_stop_recv(rx->fd);
 	return 1;
@@ -206,7 +208,8 @@
  * was totally stopped, or > 0 if correctly suspended.
  * The principle is to reverse the change above, we'll break the connection by
  * connecting to AF_UNSPEC. The association breaks and the socket starts to
- * receive from everywhere again.
+ * receive from everywhere again. Note that inherited FDs are neither suspended
+ * nor resumed, we only enable/disable polling on them.
  */
 int udp_resume_receiver(struct receiver *rx)
 {
@@ -215,7 +218,7 @@
 	if (rx->fd < 0)
 		return 0;
 
-	if (connect(rx->fd, &sa, sizeof(sa)) < 0)
+	if (!(rx->flags & RX_F_INHERITED) && connect(rx->fd, &sa, sizeof(sa)) < 0)
 		return -1;
 
 	fd_want_recv(rx->fd);