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 <[email protected]> --- 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 @@ -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" @@ -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); @@ -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
