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

From: Silvan Jegen <s.jegen_AT_gmail.com>
Date: Sat, 9 May 2015 18:12:54 +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.
---
v2 Changes:
 - Made the cmd_substitute function work when called by either 's' or 'substitute'.
The rest of the patch is unchanged from v1.
Please note that the error handling in this RFC patch is still very
rudimentary. If we go down this route we would have to make it at least
somewhat more robust.
 vis.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 178 insertions(+), 1 deletion(-)
diff --git a/vis.c b/vis.c
index cd2fb5b..4e116e4 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,176 @@ 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
+	char *arg = (char *)argv[0];
+	char *termchk = arg;
+
+	// Insert the s for sed to recognize the argument.
+	while (*arg) arg++;
+	*arg = 's';
+	char *sep = arg+1;
+
+	while (*termchk) termchk++;
+	char *end = termchk-1;
+	if(*end != *sep && *end != 'i' && *end != 'g') {
+	 editor_info_show(vis, "Sed command not properly terminated.");
+	 return false;
+	}
+
+	char * const sedcmd[] = {"sed", arg, (char *)NULL};
+	exec_external_cmd(range, opt, sedcmd);
+
 	return true;
 }
 
-- 
2.4.0
Received on Sat May 09 2015 - 18:12:54 CEST

This archive was generated by hypermail 2.3.0 : Sat May 09 2015 - 18:24:13 CEST