MINOR: debug: replace popen() with pipe+fork() in "debug dev exec"

popen() is annoying because it doesn't catch stderr. The command was
implemented using it just by pure laziness, let's just redo it a bit
cleaner using normal syscalls. Note that this command is only enabled
when built with -DDEBUG_DEV.
diff --git a/src/debug.c b/src/debug.c
index fe2fe31..41fa7ae 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -10,10 +10,13 @@
  *
  */
 
+#include <fcntl.h>
 #include <signal.h>
 #include <time.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
 
 #include <common/buf.h>
 #include <common/config.h>
@@ -310,8 +313,9 @@
 #if defined(DEBUG_DEV)
 static int debug_parse_cli_exec(char **args, char *payload, struct appctx *appctx, void *private)
 {
-	FILE *f;
+	int pipefd[2];
 	int arg;
+	int pid;
 
 	if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
 		return 1;
@@ -324,14 +328,41 @@
 		chunk_strcat(&trash, args[arg]);
 	}
 
-	f = popen(trash.area, "re");
-	if (!f)
-		return cli_err(appctx, "Failed to execute command.\n");
+	thread_isolate();
+	if (pipe(pipefd) < 0)
+		goto fail_pipe;
 
+	if (fcntl(pipefd[0], F_SETFD, fcntl(pipefd[0], F_GETFD, FD_CLOEXEC) | FD_CLOEXEC) == -1)
+		goto fail_fcntl;
+
+	if (fcntl(pipefd[1], F_SETFD, fcntl(pipefd[1], F_GETFD, FD_CLOEXEC) | FD_CLOEXEC) == -1)
+		goto fail_fcntl;
+
+	pid = fork();
+
+	if (pid < 0)
+		goto fail_fork;
+	else if (pid == 0) {
+		/* child */
+		char *cmd[4] = { "/bin/sh", "-c", 0, 0 };
+
+		close(0);
+		dup2(pipefd[1], 1);
+		dup2(pipefd[1], 2);
+
+		cmd[2] = trash.area;
+		execvp(cmd[0], cmd);
+		printf("execvp() failed\n");
+		exit(1);
+	}
+
+	/* parent */
+	thread_release();
+	close(pipefd[1]);
 	chunk_reset(&trash);
 	while (1) {
-		size_t ret = fread(trash.area + trash.data, 1, trash.size - 20 - trash.data, f);
-		if (!ret)
+		size_t ret = read(pipefd[0], trash.area + trash.data, trash.size - 20 - trash.data);
+		if (ret <= 0)
 			break;
 		trash.data += ret;
 		if (trash.data + 20 == trash.size) {
@@ -339,10 +370,18 @@
 			break;
 		}
 	}
-
-	fclose(f);
+	close(pipefd[0]);
+	waitpid(pid, NULL, WNOHANG);
 	trash.area[trash.data] = 0;
 	return cli_msg(appctx, LOG_INFO, trash.area);
+
+ fail_fork:
+ fail_fcntl:
+	close(pipefd[0]);
+	close(pipefd[1]);
+ fail_pipe:
+	thread_release();
+	return cli_err(appctx, "Failed to execute command.\n");
 }
 #endif