BUG/MEDIUM: stats: show servers state may show an empty or incomplete result

It was reported that the unix socket command "show servers state" returned an
empty response while "show servers state <backend>" worked.
In fact, both cases can reproduce the issue. It happens when the response can't
fit in one buffer.

The fix consists in processing the response in several steps, as it is done in
some others commands, by restarting where it was stopped after the buffer is
sent to the client.

This fix should be backported to haproxy 1.6
diff --git a/include/types/applet.h b/include/types/applet.h
index 562575f..bf801f4 100644
--- a/include/types/applet.h
+++ b/include/types/applet.h
@@ -131,7 +131,9 @@
 			struct dns_resolvers *ptr;
 		} resolvers;
 		struct {
-			struct proxy *backend;
+			int iid;		/* if >= 0, ID of the proxy to filter on */
+			struct proxy *px;	/* current proxy being dumped, NULL = not started yet. */
+			struct server *sv;	/* current server being dumped, NULL = not started yet. */
 		} server_state;
 		struct {
 			char **var;
diff --git a/src/dumpstats.c b/src/dumpstats.c
index bfd5b5c..a6d021b 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -303,7 +303,7 @@
 #endif
 static void cli_release_handler(struct appctx *appctx);
 
-static void dump_servers_state(struct proxy *backend, struct chunk *buf);
+static int dump_servers_state(struct stream_interface *si, struct chunk *buf);
 
 /*
  * cli_io_handler()
@@ -1379,18 +1379,21 @@
 			appctx->st0 = STAT_CLI_O_INFO; // stats_dump_info_to_buffer
 		}
 		else if (strcmp(args[1], "servers") == 0 && strcmp(args[2], "state") == 0) {
-			appctx->ctx.server_state.backend = NULL;
+			appctx->ctx.server_state.iid = 0;
+			appctx->ctx.server_state.px = NULL;
+			appctx->ctx.server_state.sv = NULL;
 
 			/* check if a backend name has been provided */
 			if (*args[3]) {
 				/* read server state from local file */
-				appctx->ctx.server_state.backend = proxy_be_by_name(args[3]);
+				appctx->ctx.server_state.px = proxy_be_by_name(args[3]);
 
-				if (appctx->ctx.server_state.backend == NULL) {
+				if (!appctx->ctx.server_state.px) {
 					appctx->ctx.cli.msg = "Can't find backend.\n";
 					appctx->st0 = STAT_CLI_PRINT;
 					return 1;
 				}
+				appctx->ctx.server_state.iid = appctx->ctx.server_state.px->uuid;
 			}
 			appctx->st2 = STAT_ST_INIT;
 			appctx->st0 = STAT_CLI_O_SERVERS_STATE; // stats_dump_servers_state_to_buffer
@@ -3057,20 +3060,21 @@
  * By default, we only export to the last known server state file format.
  * These information can be used at next startup to recover same level of server state.
  */
-static void dump_servers_state(struct proxy *backend, struct chunk *buf)
+static int dump_servers_state(struct stream_interface *si, struct chunk *buf)
 {
+	struct appctx *appctx = __objt_appctx(si->end);
 	struct server *srv;
 	char srv_addr[INET6_ADDRSTRLEN + 1];
 	time_t srv_time_since_last_change;
 	int bk_f_forced_id, srv_f_forced_id;
 
-	/* we don't want to report any state if the backend is not enabled on this process */
-	if (backend->bind_proc && !(backend->bind_proc & (1UL << (relative_pid - 1))))
-		return;
 
-	srv = backend->srv;
+	/* we don't want to report any state if the backend is not enabled on this process */
+	if (appctx->ctx.server_state.px->bind_proc && !(appctx->ctx.server_state.px->bind_proc & (1UL << (relative_pid - 1))))
+		return 1;
 
-	while (srv) {
+	for (; appctx->ctx.server_state.sv != NULL; appctx->ctx.server_state.sv = srv->next) {
+		srv = appctx->ctx.server_state.sv;
 		srv_addr[0] = '\0';
 		srv_time_since_last_change = 0;
 		bk_f_forced_id = 0;
@@ -3087,7 +3091,7 @@
 				break;
 		}
 		srv_time_since_last_change = now.tv_sec - srv->last_change;
-		bk_f_forced_id = backend->options & PR_O_FORCED_ID ? 1 : 0;
+		bk_f_forced_id = appctx->ctx.server_state.px->options & PR_O_FORCED_ID ? 1 : 0;
 		srv_f_forced_id = srv->flags & SRV_F_FORCED_ID ? 1 : 0;
 
 		chunk_appendf(buf,
@@ -3097,14 +3101,17 @@
 				"%d %d %d %d %d "
 				"%d %d"
 				"\n",
-				backend->uuid, backend->id,
+				appctx->ctx.server_state.px->uuid, appctx->ctx.server_state.px->id,
 				srv->puid, srv->id, srv_addr,
 				srv->state, srv->admin, srv->uweight, srv->iweight, (long int)srv_time_since_last_change,
 				srv->check.status, srv->check.result, srv->check.health, srv->check.state, srv->agent.state,
 				bk_f_forced_id, srv_f_forced_id);
-
-		srv = srv->next;
+		if (bi_putchk(si_ic(si), &trash) == -1) {
+			si_applet_cant_put(si);
+			return 0;
+		}
 	}
+	return 1;
 }
 
 /* Parses backend list and simply report backend names */
@@ -3147,24 +3154,32 @@
 
 	chunk_reset(&trash);
 
-	chunk_printf(&trash, "%d\n# %s\n", SRV_STATE_FILE_VERSION, SRV_STATE_FILE_FIELD_NAMES);
-
-	if (appctx->ctx.server_state.backend) {
-		dump_servers_state(appctx->ctx.server_state.backend, &trash);
-	}
-	else {
-		for (curproxy = proxy; curproxy != NULL; curproxy = curproxy->next) {
-			/* servers are only in backends */
-			if (!(curproxy->cap & PR_CAP_BE))
-				continue;
-
-			dump_servers_state(curproxy, &trash);
+	if (!appctx->ctx.server_state.px) {
+		chunk_printf(&trash, "%d\n# %s\n", SRV_STATE_FILE_VERSION, SRV_STATE_FILE_FIELD_NAMES);
+		if (bi_putchk(si_ic(si), &trash) == -1) {
+			si_applet_cant_put(si);
+			return 0;
 		}
+		appctx->ctx.server_state.px = proxy;
 	}
 
-	if (bi_putchk(si_ic(si), &trash) == -1) {
-		si_applet_cant_put(si);
-		return 0;
+	for (; appctx->ctx.server_state.px != NULL; appctx->ctx.server_state.px = curproxy->next) {
+		curproxy = appctx->ctx.server_state.px;
+		if (!appctx->ctx.server_state.sv)
+			appctx->ctx.server_state.sv = appctx->ctx.server_state.px->srv;
+		/* servers are only in backends */
+		if (curproxy->cap & PR_CAP_BE) {
+			if (!dump_servers_state(si, &trash))
+				return 0;
+
+			if (bi_putchk(si_ic(si), &trash) == -1) {
+				si_applet_cant_put(si);
+				return 0;
+			}
+		}
+		/* only the selected proxy is dumped */
+		if (appctx->ctx.server_state.iid)
+			break;
 	}
 
 	return 1;