vis-master/675-non-block_subproc.patch

419 lines
14 KiB
Diff

---
Makefile | 1
lua/vis.lua | 2
vis-lua.c | 82 +++++++++++++++++++++++++
vis-lua.h | 4 -
vis-subprocess.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
vis-subprocess.h | 23 +++++++
vis.c | 5 +
7 files changed, 291 insertions(+), 2 deletions(-)
--- a/Makefile
+++ b/Makefile
@@ -26,6 +26,7 @@ SRC = array.c \
vis-prompt.c \
vis-registers.c \
vis-text-objects.c \
+ vis-subprocess.c \
$(REGEX_SRC)
ELF = vis vis-menu vis-digraph
--- a/lua/vis.lua
+++ b/lua/vis.lua
@@ -152,6 +152,7 @@ local events = {
WIN_OPEN = "Event::WIN_OPEN", -- see @{win_open}
WIN_STATUS = "Event::WIN_STATUS", -- see @{win_status}
TERM_CSI = "Event::TERM_CSI", -- see @{term_csi}
+ PROCESS_RESPONSE = "Event::PROCESS_RESPONSE", -- see @{process_response}
}
events.file_close = function(...) events.emit(events.FILE_CLOSE, ...) end
@@ -167,6 +168,7 @@ events.win_highlight = function(...) eve
events.win_open = function(...) events.emit(events.WIN_OPEN, ...) end
events.win_status = function(...) events.emit(events.WIN_STATUS, ...) end
events.term_csi = function(...) events.emit(events.TERM_CSI, ...) end
+events.process_response = function(...) events.emit(events.PROCESS_RESPONSE, ...) end
local handlers = {}
--- a/vis-lua.c
+++ b/vis-lua.c
@@ -23,6 +23,7 @@
#include "vis-lua.h"
#include "vis-core.h"
+#include "vis-subprocess.h"
#include "text-motions.h"
#include "util.h"
@@ -52,6 +53,13 @@
#define debug(...) do { } while (0)
#endif
+typedef struct {
+ /* Lua stream structure for the process input stream */
+ FILE *f;
+ lua_CFunction closef;
+ Process *handler;
+} ProcessStream;
+
static void window_status_update(Vis *vis, Win *win) {
char left_parts[4][255] = { "", "", "", "" };
char right_parts[4][32] = { "", "", "", "" };
@@ -162,6 +170,9 @@ void vis_lua_win_close(Vis *vis, Win *wi
void vis_lua_win_highlight(Vis *vis, Win *win) { }
void vis_lua_win_status(Vis *vis, Win *win) { window_status_update(vis, win); }
void vis_lua_term_csi(Vis *vis, const long *csi) { }
+void vis_lua_process_response(Vis *vis, const char *name,
+ char *buffer, size_t len, ResponseType rtype) { }
+
#else
@@ -1368,6 +1379,47 @@ static int redraw(lua_State *L) {
return 0;
}
/***
+ * Closes a stream returned by @{Vis.communicate}.
+ *
+ * @function close
+ * @tparam io.file inputfd the stream to be closed
+ * @treturn bool the same with @{io.close}
+ */
+static int close_subprocess(lua_State *L) {
+ luaL_Stream *file = luaL_checkudata(L, -1, "FILE*");
+ int result = fclose(file->f);
+ if (result == 0) {
+ file->f = NULL;
+ file->closef = NULL;
+ }
+ return luaL_fileresult(L, result == 0, NULL);
+}
+/***
+ * Open new process and return its input handler.
+ * When the process will quit or will output anything to stdout or stderr,
+ * the @{process_response} event will be fired.
+ *
+ * The editor core won't be blocked while the external process is running.
+ *
+ * @function communicate
+ * @tparam string name the name of subprocess (to distinguish processes in the @{process_response} event)
+ * @tparam string command the command to execute
+ * @return the file handle to write data to the process, in case of error the return values are equivalent to @{io.open} error values.
+ */
+static int communicate_func(lua_State *L) {
+ Vis *vis = obj_ref_check(L, 1, "vis");
+ const char *name = luaL_checkstring(L, 2);
+ const char *cmd = luaL_checkstring(L, 3);
+ ProcessStream *inputfd = (ProcessStream *)lua_newuserdata(L, sizeof(ProcessStream));
+ luaL_setmetatable(L, LUA_FILEHANDLE);
+ inputfd->handler = vis_process_communicate(vis, name, cmd, (void **)(&(inputfd->closef)));
+ if (inputfd->handler) {
+ inputfd->f = fdopen(inputfd->handler->inpfd, "w");
+ inputfd->closef = &close_subprocess;
+ }
+ return inputfd->f ? 1 : luaL_fileresult(L, inputfd->f != NULL, name);
+}
+/***
* Currently active window.
* @tfield Window win
* @see windows
@@ -1524,6 +1576,7 @@ static const struct luaL_Reg vis_lua[] =
{ "exit", exit_func },
{ "pipe", pipe_func },
{ "redraw", redraw },
+ { "communicate", communicate_func },
{ "__index", vis_index },
{ "__newindex", vis_newindex },
{ NULL, NULL },
@@ -3135,5 +3188,34 @@ void vis_lua_term_csi(Vis *vis, const lo
}
lua_pop(L, 1);
}
+/***
+ * The response received from the process started via @{Vis:communicate}.
+ * @function process_response
+ * @tparam string name the name of process given to @{Vis:communicate}
+ * @tparam string response_type can be "STDOUT" or "STDERR" if new output was received in corresponding channel, "SIGNAL" if the process was terminated by a signal or "EXIT" when the process terminated normally
+ * @tparam string|int buffer the available content sent by process; it becomes the exit code number if response\_type is "EXIT", or the signal number if response\_type is "SIGNAL"
+ */
+void vis_lua_process_response(Vis *vis, const char *name,
+ char *buffer, size_t len, ResponseType rtype) {
+ lua_State *L = vis->lua;
+ if (!L)
+ return;
+ vis_lua_event_get(L, "process_response");
+ if (lua_isfunction(L, -1)) {
+ lua_pushstring(L, name);
+ if (rtype == EXIT || rtype == SIGNAL)
+ lua_pushinteger(L, len);
+ else
+ lua_pushlstring(L, buffer, len);
+ switch (rtype){
+ case STDOUT: lua_pushstring(L, "STDOUT"); break;
+ case STDERR: lua_pushstring(L, "STDERR"); break;
+ case SIGNAL: lua_pushstring(L, "SIGNAL"); break;
+ case EXIT: lua_pushstring(L, "EXIT"); break;
+ }
+ pcall(vis, L, 3, 0);
+ }
+ lua_pop(L, 1);
+}
#endif
--- a/vis-lua.h
+++ b/vis-lua.h
@@ -7,10 +7,11 @@
#include <lauxlib.h>
#else
typedef struct lua_State lua_State;
+typedef void* lua_CFunction;
#endif
#include "vis.h"
-
+#include "vis-subprocess.h"
/* add a directory to consider when loading lua files */
bool vis_lua_path_add(Vis*, const char *path);
/* get semicolon separated list of paths to load lua files
@@ -38,5 +39,6 @@ void vis_lua_win_close(Vis*, Win*);
void vis_lua_win_highlight(Vis*, Win*);
void vis_lua_win_status(Vis*, Win*);
void vis_lua_term_csi(Vis*, const long *);
+void vis_lua_process_response(Vis *, const char *, char *, size_t, ResponseType);
#endif
--- /dev/null
+++ b/vis-subprocess.c
@@ -0,0 +1,176 @@
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/wait.h>
+#include "vis-lua.h"
+#include "vis-subprocess.h"
+
+/* Maximum amount of data what can be read from IPC pipe per event */
+#define MAXBUFFER 1024
+
+/* Pool of information about currently running subprocesses */
+static Process *process_pool;
+
+Process *new_in_pool() {
+ /* Adds new empty process information structure to the process pool and
+ * returns it */
+ Process *newprocess = (Process *)malloc(sizeof(Process));
+ if (!newprocess) return NULL;
+ newprocess->next = process_pool;
+ process_pool = newprocess;
+ return newprocess;
+}
+
+void destroy(Process **pointer) {
+ /* Removes the subprocess information from the pool, sets invalidator to NULL
+ * and frees resources. */
+ Process *target = *pointer;
+ if (target->outfd != -1) close(target->outfd);
+ if (target->errfd != -1) close(target->errfd);
+ if (target->inpfd != -1) close(target->inpfd);
+ /* marking stream as closed for lua */
+ if (target->invalidator) *(target->invalidator) = NULL;
+ if (target->name) free(target->name);
+ *pointer = target->next;
+ free(target);
+}
+
+Process *vis_process_communicate(Vis *vis, const char *name,
+ const char *command, void **invalidator) {
+ /* Starts new subprocess by passing the `command` to the shell and
+ * returns the subprocess information structure, containing file descriptors
+ * of the process.
+ * Also stores the subprocess information to the internal pool to track
+ * its status and responses.
+ * `name` - the string than should contain an unique name of the subprocess.
+ * This name will be passed to the PROCESS_RESPONSE event handler
+ * to distinguish running subprocesses.
+ * `invalidator` - a pointer to the pointer which shows that the subprocess
+ * is invalid when set to NULL. When subprocess dies, it is being set to NULL.
+ * If the pointer is set to NULL by an external code, the subprocess will be
+ * killed on the next main loop iteration. */
+ int pin[2], pout[2], perr[2];
+ pid_t pid = (pid_t)-1;
+ if (pipe(perr) == -1) goto closeerr;
+ if (pipe(pout) == -1) goto closeouterr;
+ if (pipe(pin) == -1) goto closeall;
+ pid = fork();
+ if (pid == -1)
+ vis_info_show(vis, "fork failed: %s", strerror(errno));
+ else if (pid == 0){ /* child process */
+ sigset_t sigterm_mask;
+ sigemptyset(&sigterm_mask);
+ sigaddset(&sigterm_mask, SIGTERM);
+ if (sigprocmask(SIG_UNBLOCK, &sigterm_mask, NULL) == -1) {
+ fprintf(stderr, "failed to reset signal mask");
+ exit(EXIT_FAILURE);
+ }
+ dup2(pin[0], STDIN_FILENO);
+ dup2(pout[1], STDOUT_FILENO);
+ dup2(perr[1], STDERR_FILENO);
+ }
+ else { /* main process */
+ Process *new = new_in_pool();
+ if (!new) {
+ vis_info_show(vis, "Can not create process: %s", strerror(errno));
+ goto closeall;
+ }
+ new->name = strdup(name);
+ if (!new->name) {
+ vis_info_show(vis, "Can not copy process name: %s", strerror(errno));
+ /* pop top element (which is `new`) from the pool */
+ destroy(&process_pool);
+ goto closeall;
+ }
+ new->outfd = pout[0];
+ new->errfd = perr[0];
+ new->inpfd = pin[1];
+ new->pid = pid;
+ new->invalidator = invalidator;
+ close(pin[0]);
+ close(pout[1]);
+ close(perr[1]);
+ return new;
+ }
+closeall:
+ close(pin[0]);
+ close(pin[1]);
+closeouterr:
+ close(pout[0]);
+ close(pout[1]);
+closeerr:
+ close(perr[0]);
+ close(perr[1]);
+ if (pid == 0) { /* start command in child process */
+ execlp(vis->shell, vis->shell, "-c", command, (char*)NULL);
+ fprintf(stderr, "exec failed: %s(%d)\n", strerror(errno), errno);
+ exit(1);
+ }
+ else
+ vis_info_show(vis, "process creation failed: %s", strerror(errno));
+ return NULL;
+}
+
+int vis_process_before_tick(fd_set *readfds) {
+ /* Adds file descriptors of currently running subprocesses to the `readfds`
+ * to track their readiness and returns maximum file descriptor value
+ * to pass it to the `pselect` call */
+ Process **pointer = &process_pool;
+ int maxfd = 0;
+ while (*pointer) {
+ Process *current = *pointer;
+ if (current->outfd != -1) {
+ FD_SET(current->outfd, readfds);
+ maxfd = maxfd < current->outfd ? current->outfd : maxfd;
+ }
+ if (current->errfd != -1) {
+ FD_SET(current->errfd, readfds);
+ maxfd = maxfd < current->errfd ? current->errfd : maxfd;
+ }
+ pointer = &current->next;
+ }
+ return maxfd;
+}
+
+void read_and_fire(Vis* vis, int fd, const char *name, ResponseType rtype) {
+ /* Reads data from the given subprocess file descriptor `fd` and fires
+ * the PROCESS_RESPONSE event in Lua with given subprocess `name`,
+ * `rtype` and the read data as arguments. */
+ static char buffer[MAXBUFFER];
+ size_t obtained = read(fd, &buffer, MAXBUFFER-1);
+ if (obtained > 0)
+ vis_lua_process_response(vis, name, buffer, obtained, rtype);
+}
+
+void vis_process_tick(Vis *vis, fd_set *readfds) {
+ /* Checks if `readfds` contains file discriptors of subprocesses from
+ * the pool. If so, reads the data from them and fires corresponding events.
+ * Also checks if subprocesses from pool is dead or need to be killed then
+ * raises event or kills it if necessary. */
+ Process **pointer = &process_pool;
+ while (*pointer) {
+ Process *current = *pointer;
+ if (current->outfd != -1 && FD_ISSET(current->outfd, readfds))
+ read_and_fire(vis, current->outfd, current->name, STDOUT);
+ if (current->errfd != -1 && FD_ISSET(current->errfd, readfds))
+ read_and_fire(vis, current->errfd, current->name, STDERR);
+ int status;
+ pid_t wpid = waitpid(current->pid, &status, WNOHANG);
+ if (wpid == -1) vis_message_show(vis, strerror(errno));
+ else if (wpid == current->pid) goto just_destroy;
+ else if(!*(current->invalidator)) goto kill_and_destroy;
+ pointer = &current->next;
+ continue;
+kill_and_destroy:
+ kill(current->pid, SIGTERM);
+ waitpid(current->pid, &status, 0);
+just_destroy:
+ if (WIFSIGNALED(status))
+ vis_lua_process_response(vis, current->name, NULL, WTERMSIG(status), SIGNAL);
+ else
+ vis_lua_process_response(vis, current->name, NULL, WEXITSTATUS(status), EXIT);
+ destroy(pointer);
+ }
+}
--- /dev/null
+++ b/vis-subprocess.h
@@ -0,0 +1,23 @@
+#ifndef VIS_SUBPROCESS_H
+#define VIS_SUBPROCESS_H
+#include "vis-core.h"
+#include <sys/select.h>
+
+struct Process {
+ char *name;
+ int outfd;
+ int errfd;
+ int inpfd;
+ pid_t pid;
+ void **invalidator;
+ struct Process *next;
+};
+
+typedef struct Process Process;
+typedef enum { STDOUT, STDERR, SIGNAL, EXIT } ResponseType;
+
+Process *vis_process_communicate(Vis *, const char *command, const char *name,
+ void **invalidator);
+int vis_process_before_tick(fd_set *);
+void vis_process_tick(Vis *, fd_set *);
+#endif
--- a/vis.c
+++ b/vis.c
@@ -28,6 +28,7 @@
#include "vis-core.h"
#include "sam.h"
#include "ui.h"
+#include "vis-subprocess.h"
static void macro_replay(Vis *vis, const Macro *macro);
@@ -1412,7 +1413,8 @@ int vis_run(Vis *vis) {
vis_update(vis);
idle.tv_sec = vis->mode->idle_timeout;
- int r = pselect(1, &fds, NULL, NULL, timeout, &emptyset);
+ int r = pselect(vis_process_before_tick(&fds) + 1, &fds, NULL, NULL,
+ timeout, &emptyset);
if (r == -1 && errno == EINTR)
continue;
@@ -1420,6 +1422,7 @@ int vis_run(Vis *vis) {
/* TODO save all pending changes to a ~suffixed file */
vis_die(vis, "Error in mainloop: %s\n", strerror(errno));
}
+ vis_process_tick(vis, &fds);
if (!FD_ISSET(STDIN_FILENO, &fds)) {
if (vis->mode->idle)