MINOR: listener: introduce listener_backlog() to report the backlog value

In an attempt to try to provide automatic maxconn settings, we need to
decorrelate a listner's backlog and maxconn so that these values can be
independent. This introduces a listener_backlog() function which retrieves
the backlog value from the listener's backlog, the frontend's, the
listener's maxconn, the frontend's or falls back to 1024. This
corresponds to what was done in cfgparse.c to force a value there except
the last fallback which was not set since the frontend's maxconn is always
known.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index dd86493..85b687e 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -10885,7 +10885,7 @@
        bind :443 ssl crt pub.pem alpn h2,http/1.1
 
 backlog <backlog>
-  Sets the socket's backlog to this value. If unspecified, the frontend's
+  Sets the socket's backlog to this value. If unspecified or 0, the frontend's
   backlog is used instead, which generally defaults to the maxconn value.
 
 curves <curves>
diff --git a/include/proto/listener.h b/include/proto/listener.h
index 8ec41af..24a01b2 100644
--- a/include/proto/listener.h
+++ b/include/proto/listener.h
@@ -109,6 +109,12 @@
  */
 void listener_accept(int fd);
 
+/* Returns a suitable value for a listener's backlog. It uses the listener's,
+ * otherwise the frontend's backlog, otherwise the listener's maxconn,
+ * otherwise the frontend's maxconn, otherwise 1024.
+ */
+int listener_backlog(const struct listener *l);
+
 /* Notify the listener that a connection initiated from it was released. This
  * is used to keep the connection count consistent and to possibly re-open
  * listening when it was limited.
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 21b9af5..4c8b48b 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -650,7 +650,6 @@
 			l = LIST_ELEM(bind_conf->listeners.n, typeof(l), by_bind);
 			l->maxaccept = 1;
 			l->maxconn = curpeers->peers_fe->maxconn;
-			l->backlog = curpeers->peers_fe->backlog;
 			l->accept = session_accept_fd;
 			l->analysers |=  curpeers->peers_fe->fe_req_ana;
 			l->default_target = curpeers->peers_fe->default_target;
@@ -854,7 +853,6 @@
 		l = LIST_ELEM(bind_conf->listeners.n, typeof(l), by_bind);
 		l->maxaccept = 1;
 		l->maxconn = curpeers->peers_fe->maxconn;
-		l->backlog = curpeers->peers_fe->backlog;
 		l->accept = session_accept_fd;
 		l->analysers |=  curpeers->peers_fe->fe_req_ana;
 		l->default_target = curpeers->peers_fe->default_target;
@@ -3743,8 +3741,6 @@
 				listener->options |= LI_O_NOLINGER;
 			if (!listener->maxconn)
 				listener->maxconn = curproxy->maxconn;
-			if (!listener->backlog)
-				listener->backlog = curproxy->backlog;
 			if (!listener->maxaccept)
 				listener->maxaccept = global.tune.maxaccept ? global.tune.maxaccept : 64;
 
diff --git a/src/cli.c b/src/cli.c
index 0cace77..9b95718 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -298,7 +298,6 @@
 
 		list_for_each_entry(l, &bind_conf->listeners, by_bind) {
 			l->maxconn = global.stats_fe->maxconn;
-			l->backlog = global.stats_fe->backlog;
 			l->accept = session_accept_fd;
 			l->default_target = global.stats_fe->default_target;
 			l->options |= LI_O_UNLIMITED; /* don't make the peers subject to global limits */
@@ -2522,7 +2521,6 @@
 
 	list_for_each_entry(l, &bind_conf->listeners, by_bind) {
 		l->maxconn = 10;
-		l->backlog = 10;
 		l->accept = session_accept_fd;
 		l->default_target = mworker_proxy->default_target;
 		/* don't make the peers subject to global limits and don't close it in the master */
@@ -2592,7 +2590,6 @@
 
 	list_for_each_entry(l, &bind_conf->listeners, by_bind) {
 		l->maxconn = global.stats_fe->maxconn;
-		l->backlog = global.stats_fe->backlog;
 		l->accept = session_accept_fd;
 		l->default_target = global.stats_fe->default_target;
 		l->options |= (LI_O_UNLIMITED | LI_O_NOSTOP);
diff --git a/src/listener.c b/src/listener.c
index 3ae20e2..4e22e50 100644
--- a/src/listener.c
+++ b/src/listener.c
@@ -350,7 +350,7 @@
 
 	if (l->proto->sock_prot == IPPROTO_TCP &&
 	    l->state == LI_PAUSED &&
-	    listen(l->fd, l->backlog ? l->backlog : l->maxconn) != 0) {
+	    listen(l->fd, listener_backlog(l)) != 0) {
 		ret = 0;
 		goto end;
 	}
@@ -568,6 +568,27 @@
 	HA_SPIN_UNLOCK(LISTENER_LOCK, &listener->lock);
 }
 
+/* Returns a suitable value for a listener's backlog. It uses the listener's,
+ * otherwise the frontend's backlog, otherwise the listener's maxconn,
+ * otherwise the frontend's maxconn, otherwise 1024.
+ */
+int listener_backlog(const struct listener *l)
+{
+	if (l->backlog)
+		return l->backlog;
+
+	if (l->bind_conf->frontend->backlog)
+		return l->bind_conf->frontend->backlog;
+
+	if (l->maxconn)
+		return l->maxconn;
+
+	if (l->bind_conf->frontend->maxconn)
+		return l->bind_conf->frontend->maxconn;
+
+	return 1024;
+}
+
 /* This function is called on a read event from a listening socket, corresponding
  * to an accept. It tries to accept as many connections as possible, and for each
  * calls the listener's accept handler (generally the frontend's accept handler).
@@ -1101,7 +1122,7 @@
 
 	val = atol(args[cur_arg + 1]);
 	if (val <= 0) {
-		memprintf(err, "'%s' : invalid value %d, must be > 0", args[cur_arg], val);
+		memprintf(err, "'%s' : invalid value %d, must be >= 0", args[cur_arg], val);
 		return ERR_ALERT | ERR_FATAL;
 	}
 
@@ -1125,7 +1146,7 @@
 	}
 
 	val = atol(args[cur_arg + 1]);
-	if (val <= 0) {
+	if (val < 0) {
 		memprintf(err, "'%s' : invalid value %d, must be > 0", args[cur_arg], val);
 		return ERR_ALERT | ERR_FATAL;
 	}
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 28b7750..f1a0e4c 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -1017,7 +1017,7 @@
 #if defined(TCP_FASTOPEN)
 	if (listener->options & LI_O_TCP_FO) {
 		/* TFO needs a queue length, let's use the configured backlog */
-		int qlen = listener->backlog ? listener->backlog : listener->maxconn;
+		int qlen = listener_backlog(listener);
 		if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen)) == -1) {
 			msg = "cannot enable TCP_FASTOPEN";
 			err |= ERR_WARN;
@@ -1058,7 +1058,7 @@
 		ready = 0;
 
 	if (!(ext && ready) && /* only listen if not already done by external process */
-	    listen(fd, listener->backlog ? listener->backlog : listener->maxconn) == -1) {
+	    listen(fd, listener_backlog(listener)) == -1) {
 		err |= ERR_RETRYABLE | ERR_ALERT;
 		msg = "cannot listen to socket";
 		goto tcp_close_return;
@@ -1150,7 +1150,7 @@
 	if (shutdown(l->fd, SHUT_WR) != 0)
 		return -1; /* Solaris dies here */
 
-	if (listen(l->fd, l->backlog ? l->backlog : l->maxconn) != 0)
+	if (listen(l->fd, listener_backlog(l)) != 0)
 		return -1; /* OpenBSD dies here */
 
 	if (shutdown(l->fd, SHUT_RD) != 0)
diff --git a/src/proto_uxst.c b/src/proto_uxst.c
index d454d4c..980a226 100644
--- a/src/proto_uxst.c
+++ b/src/proto_uxst.c
@@ -315,7 +315,7 @@
 		ready = 0;
 
 	if (!(ext && ready) && /* only listen if not already done by external process */
-	    listen(fd, listener->backlog ? listener->backlog : listener->maxconn) < 0) {
+	    listen(fd, listener_backlog(listener)) < 0) {
 		err |= ERR_FATAL | ERR_ALERT;
 		msg = "cannot listen to UNIX socket";
 		goto err_unlink_temp;