[dev] [vis][RFC][PATCH 2/2] Implement the execution of external commands and cmd_substitute

From: Silvan Jegen <s.jegen_AT_gmail.com>
Date: Tue, 5 May 2015 18:25:30 +0200

We use the fork/exec pattern to execute an external command. Two functions
using this approach are implemented in this patch.

1. exec_external_cmd

uses the pipe function to write to the stdin of the executed command
and read from its stdout.

The data being sent to the external command's stdin comes directly from
the byte contents of a Filerange and the data being read from its stdout
is being used to replace the Filerange's contents.

The cmd_substitute function is implemented subsequently through calling
the 'sed' program using the functionality implemented by this function.

This function should also be used when implementing the :! filter command
in the future.

2. exec_external_cmd_without_range

calls the external command, ignores its stdout and returns as soon as
the forked process exits.

Signed-off-by: Silvan Jegen <s.jegen_AT_gmail.com>
---
The code for the read/write loops communicating through a pipe with the
external process is very ugly. There must be a better way to implement
the pipe communication but I could not figure it out.
This approach uses a unnecessary amount of memory too since it keeps a lot
of the input data for the external process as well as its output data
in memory at the same time. When testing it on a text file 160MB in size
it seemed to work ok though.
Using a call to 'sed' to implement cmd_substitute seems like a good
way to do it since the exec_external_cmd function can be used for other
purposes as well.
 vis.c | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 181 insertions(+), 1 deletion(-)
diff --git a/vis.c b/vis.c
index 201d88a..e2da4cd 100644
--- a/vis.c
+++ b/vis.c
_AT_@ -30,6 +30,7 @@
 #include <sys/stat.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
+#include <sys/wait.h>
 
 #include "ui-curses.h"
 #include "editor.h"
_AT_@ -413,6 +414,14 @@ static bool cmd_write(Filerange*, enum CmdOpt, const char *argv[]);
  * associate the new name with the buffer. further :w commands
  * without arguments will write to the new filename */
 static bool cmd_saveas(Filerange*, enum CmdOpt, const char *argv[]);
+/* Run external commands by forking a child in which to start them. The
+ * bytes from range will be sent to the stdin of the external program and
+ * the stdout will replace the bytes in the range after the program has
+ * finished */
+static bool exec_external_cmd(Filerange *range, enum CmdOpt opt, char * const argv[]);
+/* Run external commands by forking a child in which to start them. Vis
+ * will wait for the program to return before continuing */
+static bool exec_external_cmd_without_range(enum CmdOpt opt, char * const argv[]);
 
 static void action_reset(Action *a);
 static void switchmode_to(Mode *new_mode);
_AT_@ -1561,8 +1570,179 @@ static bool cmd_read(Filerange *range, enum CmdOpt opt, const char *argv[]) {
 	return true;
 }
 
+static bool exec_external_cmd(Filerange *range, enum CmdOpt opt, char * const argv[]) {
+	pid_t pid = 0;
+	int outputpipe[2], inputpipe[2], pret, readcount;
+	char *inbuf;
+	char tmpbuf[BUFSIZ];
+	size_t totalread, inputlen, curpos;
+
+	if (!range)
+		return exec_external_cmd_without_range(opt, argv);
+
+	Buffer *outbuf = calloc(1, sizeof(Buffer));
+
+	pret = pipe(outputpipe);
+	if (pret < 0) {
+		editor_info_show(vis, "Could not make the output pipe for the external command.");
+		return false;
+	}
+	pret = pipe(inputpipe);
+	if (pret < 0) {
+		editor_info_show(vis, "Could not make the input pipe for the external command.");
+		return false;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		editor_info_show(vis, "Could not start the external command.");
+		return false;
+	}
+
+	if (pid == 0) {
+		close(outputpipe[0]);
+		dup2(outputpipe[1], STDOUT_FILENO);
+
+		close(inputpipe[1]);
+		dup2(inputpipe[0], STDIN_FILENO);
+
+		execvp(argv[0], argv);
+	}
+
+	close(inputpipe[0]);
+
+	Text *text = vis->win->file->text;
+	inputlen = range->end-range->start;
+
+	inbuf = malloc(inputlen);
+	if (!inbuf) {
+		editor_info_show(vis, "Could not allocate input buffer for external program.");
+		return false;
+	}
+
+	// Save cursor position in order to restore it after replacing
+	// the filtered text.
+	View *view = vis->win->view;
+	CursorPos curspos = view_cursor_getpos(view);
+	text_bytes_get(text, range->start, inputlen, inbuf);
+
+	int wrote = 0;
+	int totalwritten = 0;
+	int towrite = inputlen;
+
+	readcount = totalread = 0;
+
+	close(outputpipe[1]);
+	if (!buffer_alloc(outbuf, BUFSIZ)) {
+		editor_info_show(vis, "Could not allocate output buffer for external program.");
+		return false;
+	}
+
+	int fret = fcntl(outputpipe[0], F_SETFL, O_NONBLOCK);
+	if (fret < 0) {
+		editor_info_show(vis, "Could not set output pipe to nonblocking.");
+		close(inputpipe[1]);
+		close(outputpipe[0]);
+		return false;
+	}
+
+	fret = fcntl(inputpipe[1], F_SETFL, O_NONBLOCK);
+	if (fret < 0) {
+		editor_info_show(vis, "Could not set input pipe to nonblocking.");
+		close(inputpipe[1]);
+		close(outputpipe[0]);
+		return false;
+	}
+
+	// TODO: This is fugly and should be replaced with a better
+	// version...
+	// In order not to starve the external command or getting stuck when
+	// reading from its stdout we write and read from it in parallel and
+	// asynchronously. As soon as we have written all the text data to
+	// it, we restart another reading loop until the output pipe has
+	// seen an EOF (when read returns zero).
+	while (towrite > totalwritten) {
+		wrote = write(inputpipe[1], inbuf+totalwritten, towrite-totalwritten);
+		if (wrote > 0)
+		    totalwritten += wrote;
+
+		readcount = read(outputpipe[0], tmpbuf, sizeof(tmpbuf));
+		if (readcount < 0)
+		    continue;
+		totalread += readcount;
+		buffer_append(outbuf, tmpbuf, readcount);
+	}
+	close(inputpipe[1]);
+	free(inbuf);
+
+	while (readcount != 0) {
+		readcount = read(outputpipe[0], tmpbuf, sizeof(tmpbuf));
+		if (readcount < 0)
+		    continue;
+
+		totalread += readcount;
+		buffer_append(outbuf, tmpbuf, readcount);
+	}
+	close(outputpipe[0]);
+
+
+	editor_delete(vis, range->start, inputlen);
+	editor_insert(vis, range->start, outbuf->data, totalread);
+
+	buffer_free(outbuf);
+	free(outbuf);
+
+	curpos = text_pos_by_lineno(text, curspos.line);
+	view_cursor_to(view, curpos);
+
+	return true;
+}
+
+static bool exec_external_cmd_without_range(enum CmdOpt opt, char * const argv[]) {
+	pid_t pid = 0;
+	int status;
+
+	pid = fork();
+	if (pid < 0) {
+		editor_info_show(vis, "Could not fork to start the external command.");
+		return false;
+	}
+
+	if (pid == 0) {
+		int null = open("/dev/null", O_WRONLY);
+		if (null < 0)
+			return false;
+		dup2(null, STDOUT_FILENO);
+		execvp(argv[0], argv);
+	}
+
+	// Wait for the child to return.
+	waitpid(pid, &status, 0);
+	return status;
+}
+
 static bool cmd_substitute(Filerange *range, enum CmdOpt opt, const char *argv[]) {
-	// TODO
+	// Add back the slash we removed before to get the command
+	// recognized.
+	char *cmd;
+	cmd = (char *)argv[0];
+	*(++cmd) = '/';
+
+	while (*cmd) cmd++;
+	switch(*(cmd-1)) {
+	case '/':
+	case 'i':
+	case 'g':
+	 break;
+
+	default:
+	 editor_info_show(vis, "Sed command not properly terminated.");
+	 return false;
+	}
+
+	char * const sedcmd[] = {"sed", (char *)argv[0], (char *)NULL};
+	exec_external_cmd(range, opt, sedcmd);
+
 	return true;
 }
 
-- 
2.3.7
Received on Tue May 05 2015 - 18:25:30 CEST

This archive was generated by hypermail 2.3.0 : Tue May 05 2015 - 18:36:28 CEST