Implement prefix-based interactive commands via ! (issue #14)
Add !command syntax that runs commands with inherited terminal (no
capture). The lexer treats ! as a statement-starting token, reading
to end-of-line with the same interpolation/escape/pipe support as
backtick commands. The C function sets _ = {code=N, stdout="", stderr=""}.
This commit is contained in:
215
lcmd.c
215
lcmd.c
@@ -319,14 +319,15 @@ static void push_result_table (lua_State *L, int code,
|
||||
** are captured. Middle stages' stderr goes to the parent's stderr (inherited).
|
||||
** Returns 1 (one result table on the Lua stack).
|
||||
*/
|
||||
static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
||||
static int exec_pipeline (lua_State *L, char **stages, int nstages,
|
||||
int interactive) {
|
||||
ParsedArgs *pa = NULL;
|
||||
int (*inter_pipes)[2] = NULL; /* inter_pipes[i] connects stage i → i+1 */
|
||||
int out_pipe[2], err_pipe[2]; /* capture pipes for last stage */
|
||||
int out_pipe[2] = {-1, -1}, err_pipe[2] = {-1, -1};
|
||||
pid_t *pids = NULL;
|
||||
int i, last_code = -1;
|
||||
DynBuf buf_out, buf_err;
|
||||
struct sigaction sa_old, sa_new;
|
||||
struct sigaction sa_old_pipe, sa_old_int, sa_old_quit, sa_new;
|
||||
|
||||
/* parse all stages */
|
||||
pa = (ParsedArgs *)calloc((size_t)nstages, sizeof(ParsedArgs));
|
||||
@@ -364,45 +365,57 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
||||
}
|
||||
}
|
||||
|
||||
/* capture pipes for last stage */
|
||||
if (pipe(out_pipe) != 0) {
|
||||
if (inter_pipes) {
|
||||
for (i = 0; i < nstages - 1; i++) { close(inter_pipes[i][0]); close(inter_pipes[i][1]); }
|
||||
free(inter_pipes);
|
||||
/* capture pipes for last stage (only in non-interactive mode) */
|
||||
if (!interactive) {
|
||||
if (pipe(out_pipe) != 0) {
|
||||
if (inter_pipes) {
|
||||
for (i = 0; i < nstages - 1; i++) { close(inter_pipes[i][0]); close(inter_pipes[i][1]); }
|
||||
free(inter_pipes);
|
||||
}
|
||||
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
||||
free(pa);
|
||||
return luaL_error(L, "pipe() failed: %s", strerror(errno));
|
||||
}
|
||||
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
||||
free(pa);
|
||||
return luaL_error(L, "pipe() failed: %s", strerror(errno));
|
||||
}
|
||||
if (pipe(err_pipe) != 0) {
|
||||
close(out_pipe[0]); close(out_pipe[1]);
|
||||
if (inter_pipes) {
|
||||
for (i = 0; i < nstages - 1; i++) { close(inter_pipes[i][0]); close(inter_pipes[i][1]); }
|
||||
free(inter_pipes);
|
||||
if (pipe(err_pipe) != 0) {
|
||||
close(out_pipe[0]); close(out_pipe[1]);
|
||||
if (inter_pipes) {
|
||||
for (i = 0; i < nstages - 1; i++) { close(inter_pipes[i][0]); close(inter_pipes[i][1]); }
|
||||
free(inter_pipes);
|
||||
}
|
||||
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
||||
free(pa);
|
||||
return luaL_error(L, "pipe() failed: %s", strerror(errno));
|
||||
}
|
||||
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
||||
free(pa);
|
||||
return luaL_error(L, "pipe() failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/* ignore SIGPIPE in parent */
|
||||
/* ignore signals in parent */
|
||||
memset(&sa_new, 0, sizeof(sa_new));
|
||||
sa_new.sa_handler = SIG_IGN;
|
||||
sigemptyset(&sa_new.sa_mask);
|
||||
sigaction(SIGPIPE, &sa_new, &sa_old);
|
||||
sigaction(SIGPIPE, &sa_new, &sa_old_pipe);
|
||||
if (interactive) {
|
||||
sigaction(SIGINT, &sa_new, &sa_old_int);
|
||||
sigaction(SIGQUIT, &sa_new, &sa_old_quit);
|
||||
}
|
||||
|
||||
/* allocate pid array */
|
||||
pids = (pid_t *)calloc((size_t)nstages, sizeof(pid_t));
|
||||
if (pids == NULL) {
|
||||
close(out_pipe[0]); close(out_pipe[1]);
|
||||
close(err_pipe[0]); close(err_pipe[1]);
|
||||
if (!interactive) {
|
||||
close(out_pipe[0]); close(out_pipe[1]);
|
||||
close(err_pipe[0]); close(err_pipe[1]);
|
||||
}
|
||||
if (inter_pipes) {
|
||||
for (i = 0; i < nstages - 1; i++) { close(inter_pipes[i][0]); close(inter_pipes[i][1]); }
|
||||
free(inter_pipes);
|
||||
}
|
||||
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
||||
free(pa);
|
||||
sigaction(SIGPIPE, &sa_old, NULL);
|
||||
sigaction(SIGPIPE, &sa_old_pipe, NULL);
|
||||
if (interactive) {
|
||||
sigaction(SIGINT, &sa_old_int, NULL);
|
||||
sigaction(SIGQUIT, &sa_old_quit, NULL);
|
||||
}
|
||||
return luaL_error(L, "out of memory");
|
||||
}
|
||||
|
||||
@@ -415,15 +428,21 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
||||
kill(pids[j], SIGTERM);
|
||||
waitpid(pids[j], NULL, 0);
|
||||
}
|
||||
close(out_pipe[0]); close(out_pipe[1]);
|
||||
close(err_pipe[0]); close(err_pipe[1]);
|
||||
if (!interactive) {
|
||||
close(out_pipe[0]); close(out_pipe[1]);
|
||||
close(err_pipe[0]); close(err_pipe[1]);
|
||||
}
|
||||
if (inter_pipes) {
|
||||
for (int j = 0; j < nstages - 1; j++) { close(inter_pipes[j][0]); close(inter_pipes[j][1]); }
|
||||
free(inter_pipes);
|
||||
}
|
||||
for (int j = 0; j < nstages; j++) free_argv(&pa[j]);
|
||||
free(pa); free(pids);
|
||||
sigaction(SIGPIPE, &sa_old, NULL);
|
||||
sigaction(SIGPIPE, &sa_old_pipe, NULL);
|
||||
if (interactive) {
|
||||
sigaction(SIGINT, &sa_old_int, NULL);
|
||||
sigaction(SIGQUIT, &sa_old_quit, NULL);
|
||||
}
|
||||
return luaL_error(L, "fork() failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
@@ -431,9 +450,13 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
||||
/* === child process for stage i === */
|
||||
int j;
|
||||
|
||||
/* restore default SIGPIPE */
|
||||
/* restore default signals */
|
||||
sa_new.sa_handler = SIG_DFL;
|
||||
sigaction(SIGPIPE, &sa_new, NULL);
|
||||
if (interactive) {
|
||||
sigaction(SIGINT, &sa_new, NULL);
|
||||
sigaction(SIGQUIT, &sa_new, NULL);
|
||||
}
|
||||
|
||||
/* set up stdin */
|
||||
if (i > 0) {
|
||||
@@ -443,11 +466,12 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
||||
/* set up stdout */
|
||||
if (i < nstages - 1) {
|
||||
dup2(inter_pipes[i][1], STDOUT_FILENO);
|
||||
} else {
|
||||
} else if (!interactive) {
|
||||
/* last stage: capture stdout and stderr */
|
||||
dup2(out_pipe[1], STDOUT_FILENO);
|
||||
dup2(err_pipe[1], STDERR_FILENO);
|
||||
}
|
||||
/* else: interactive last stage inherits terminal */
|
||||
|
||||
/* close all pipe fds in child */
|
||||
if (inter_pipes) {
|
||||
@@ -456,8 +480,10 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
||||
close(inter_pipes[j][1]);
|
||||
}
|
||||
}
|
||||
close(out_pipe[0]); close(out_pipe[1]);
|
||||
close(err_pipe[0]); close(err_pipe[1]);
|
||||
if (!interactive) {
|
||||
close(out_pipe[0]); close(out_pipe[1]);
|
||||
close(err_pipe[0]); close(err_pipe[1]);
|
||||
}
|
||||
|
||||
execvp(pa[i].argv[0], pa[i].argv);
|
||||
|
||||
@@ -483,17 +509,20 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
||||
}
|
||||
free(inter_pipes);
|
||||
}
|
||||
close(out_pipe[1]);
|
||||
close(err_pipe[1]);
|
||||
|
||||
/* free parsed args (children have already exec'd) */
|
||||
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
||||
free(pa);
|
||||
|
||||
/* read captured output from last stage */
|
||||
dynbuf_init(&buf_out);
|
||||
dynbuf_init(&buf_err);
|
||||
read_pipes(out_pipe[0], err_pipe[0], &buf_out, &buf_err);
|
||||
|
||||
if (!interactive) {
|
||||
close(out_pipe[1]);
|
||||
close(err_pipe[1]);
|
||||
/* read captured output from last stage */
|
||||
read_pipes(out_pipe[0], err_pipe[0], &buf_out, &buf_err);
|
||||
}
|
||||
|
||||
/* wait for all children, keep last stage's exit code */
|
||||
for (i = 0; i < nstages; i++) {
|
||||
@@ -510,8 +539,12 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
||||
}
|
||||
free(pids);
|
||||
|
||||
/* restore SIGPIPE */
|
||||
sigaction(SIGPIPE, &sa_old, NULL);
|
||||
/* restore signals */
|
||||
sigaction(SIGPIPE, &sa_old_pipe, NULL);
|
||||
if (interactive) {
|
||||
sigaction(SIGINT, &sa_old_int, NULL);
|
||||
sigaction(SIGQUIT, &sa_old_quit, NULL);
|
||||
}
|
||||
|
||||
/* push result table */
|
||||
push_result_table(L, last_code, &buf_out, &buf_err);
|
||||
@@ -541,7 +574,7 @@ int luaB_command (lua_State *L) {
|
||||
|
||||
/* multi-stage pipeline: delegate to exec_pipeline */
|
||||
if (nstages > 1) {
|
||||
int result = exec_pipeline(L, stages, nstages);
|
||||
int result = exec_pipeline(L, stages, nstages, 0);
|
||||
free(stages[0]); /* free backing buffer */
|
||||
return result;
|
||||
}
|
||||
@@ -657,3 +690,107 @@ int luaB_command (lua_State *L) {
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* ===== luaB_interactive ===== */
|
||||
|
||||
int luaB_interactive (lua_State *L) {
|
||||
const char *cmd = luaL_checkstring(L, 1);
|
||||
char *stages[MAX_PIPELINE_STAGES];
|
||||
int nstages;
|
||||
ParsedArgs pa;
|
||||
pid_t pid;
|
||||
int status, code;
|
||||
struct sigaction sa_old_pipe, sa_old_int, sa_old_quit, sa_new;
|
||||
DynBuf buf_out, buf_err;
|
||||
|
||||
/* try to split into pipeline stages */
|
||||
if (split_pipeline(cmd, stages, &nstages) != 0)
|
||||
return luaL_error(L, "invalid pipeline syntax in command");
|
||||
|
||||
/* multi-stage pipeline: delegate to exec_pipeline with interactive=1 */
|
||||
if (nstages > 1) {
|
||||
exec_pipeline(L, stages, nstages, 1);
|
||||
free(stages[0]);
|
||||
lua_setglobal(L, "_");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* single stage */
|
||||
free(stages[0]);
|
||||
|
||||
if (parse_argv(cmd, &pa) != 0)
|
||||
return luaL_error(L, "unterminated quote in command");
|
||||
|
||||
if (pa.argc == 0) {
|
||||
free_argv(&pa);
|
||||
dynbuf_init(&buf_out);
|
||||
dynbuf_init(&buf_err);
|
||||
push_result_table(L, 0, &buf_out, &buf_err);
|
||||
lua_setglobal(L, "_");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ignore SIGINT, SIGQUIT, SIGPIPE in parent */
|
||||
memset(&sa_new, 0, sizeof(sa_new));
|
||||
sa_new.sa_handler = SIG_IGN;
|
||||
sigemptyset(&sa_new.sa_mask);
|
||||
sigaction(SIGPIPE, &sa_new, &sa_old_pipe);
|
||||
sigaction(SIGINT, &sa_new, &sa_old_int);
|
||||
sigaction(SIGQUIT, &sa_new, &sa_old_quit);
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
free_argv(&pa);
|
||||
sigaction(SIGPIPE, &sa_old_pipe, NULL);
|
||||
sigaction(SIGINT, &sa_old_int, NULL);
|
||||
sigaction(SIGQUIT, &sa_old_quit, NULL);
|
||||
return luaL_error(L, "fork() failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
/* child: restore signal defaults, inherit terminal */
|
||||
sa_new.sa_handler = SIG_DFL;
|
||||
sigaction(SIGPIPE, &sa_new, NULL);
|
||||
sigaction(SIGINT, &sa_new, NULL);
|
||||
sigaction(SIGQUIT, &sa_new, NULL);
|
||||
|
||||
execvp(pa.argv[0], pa.argv);
|
||||
|
||||
/* exec failed */
|
||||
{
|
||||
const char *err = strerror(errno);
|
||||
size_t namelen = strlen(pa.argv[0]);
|
||||
size_t errlen = strlen(err);
|
||||
(void)write(STDERR_FILENO, pa.argv[0], namelen);
|
||||
(void)write(STDERR_FILENO, ": ", 2);
|
||||
(void)write(STDERR_FILENO, err, errlen);
|
||||
(void)write(STDERR_FILENO, "\n", 1);
|
||||
}
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
/* parent */
|
||||
free_argv(&pa);
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
/* restore signals */
|
||||
sigaction(SIGPIPE, &sa_old_pipe, NULL);
|
||||
sigaction(SIGINT, &sa_old_int, NULL);
|
||||
sigaction(SIGQUIT, &sa_old_quit, NULL);
|
||||
|
||||
if (WIFEXITED(status))
|
||||
code = WEXITSTATUS(status);
|
||||
else if (WIFSIGNALED(status))
|
||||
code = 128 + WTERMSIG(status);
|
||||
else
|
||||
code = -1;
|
||||
|
||||
/* build {code=N, stdout="", stderr=""} and set as global _ */
|
||||
dynbuf_init(&buf_out);
|
||||
dynbuf_init(&buf_err);
|
||||
push_result_table(L, code, &buf_out, &buf_err);
|
||||
lua_setglobal(L, "_");
|
||||
|
||||
return 0; /* void — no Lua return values */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user