Refactor: extract lookup_lush_func, reorder helpers, eliminate forward decl

Extract lookup_lush_func() in lcmd.c to deduplicate the registry lookup
pattern shared by try_builtin and exec_user_command. Move exec_user_command
after exec_failed to group helpers together. Move codeenvget definition in
lparser.c to replace its forward declaration.
This commit is contained in:
Cormac Shannon
2026-03-22 17:04:02 +00:00
parent 973dd90d65
commit 9bc7dc01b7
2 changed files with 189 additions and 249 deletions

408
lcmd.c
View File

@@ -607,6 +607,74 @@ static void read_pipes (int fd_out, int fd_err,
} }
/* ===== pipeline execution ===== */
/*
** Build result table {code=N, stdout=S, stderr=S} on the Lua stack.
*/
static void push_result_table (lua_State *L, int code,
DynBuf *buf_out, DynBuf *buf_err) {
lua_createtable(L, 0, 3);
lua_pushinteger(L, code);
lua_setfield(L, -2, "code");
lua_pushlstring(L, buf_out->data ? buf_out->data : "", buf_out->len);
lua_setfield(L, -2, "stdout");
lua_pushlstring(L, buf_err->data ? buf_err->data : "", buf_err->len);
lua_setfield(L, -2, "stderr");
}
/*
** Extract exit code from wait status.
*/
static int exit_code_from_status (int status) {
if (WIFEXITED(status))
return WEXITSTATUS(status);
else if (WIFSIGNALED(status))
return 128 + WTERMSIG(status);
return -1;
}
/*
** Write "name: strerror(errno)\n" to stderr after exec failure.
** Only called in forked child processes.
*/
static void exec_failed (const char *name) {
const char *err = strerror(errno);
(void)write(STDERR_FILENO, name, strlen(name));
(void)write(STDERR_FILENO, ": ", 2);
(void)write(STDERR_FILENO, err, strlen(err));
(void)write(STDERR_FILENO, "\n", 1);
}
/*
** Look up a function in lush[table][key].
** On success, pushes the function and returns 1.
** On failure, cleans the stack and returns 0.
*/
static int lookup_lush_func (lua_State *L, const char *table,
const char *key) {
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH);
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return 0;
}
if (lua_getfield(L, -1, table) != LUA_TTABLE) {
lua_pop(L, 2);
return 0;
}
if (lua_getfield(L, -1, key) != LUA_TFUNCTION) {
lua_pop(L, 3);
return 0;
}
lua_remove(L, -2); /* remove subtable */
lua_remove(L, -2); /* remove lush table */
return 1;
}
/* ===== user command dispatch (runs in forked child) ===== */ /* ===== user command dispatch (runs in forked child) ===== */
/* /*
@@ -617,21 +685,8 @@ static void read_pipes (int fd_out, int fd_err,
*/ */
static int exec_user_command (lua_State *L, ParsedArgs *pa) { static int exec_user_command (lua_State *L, ParsedArgs *pa) {
int i, code = 0; int i, code = 0;
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH); if (!lookup_lush_func(L, "commands", pa->argv[0]))
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return 0; return 0;
}
if (lua_getfield(L, -1, "commands") != LUA_TTABLE) {
lua_pop(L, 2);
return 0;
}
if (lua_getfield(L, -1, pa->argv[0]) != LUA_TFUNCTION) {
lua_pop(L, 3);
return 0;
}
lua_remove(L, -2); /* remove commands table */
lua_remove(L, -2); /* remove lush table */
for (i = 0; i < pa->argc; i++) for (i = 0; i < pa->argc; i++)
lua_pushstring(L, pa->argv[i]); lua_pushstring(L, pa->argv[i]);
if (lua_pcall(L, pa->argc, 1, 0) != LUA_OK) { if (lua_pcall(L, pa->argc, 1, 0) != LUA_OK) {
@@ -658,23 +713,6 @@ static int exec_user_command (lua_State *L, ParsedArgs *pa) {
} }
/* ===== pipeline execution ===== */
/*
** Build result table {code=N, stdout=S, stderr=S} on the Lua stack.
*/
static void push_result_table (lua_State *L, int code,
DynBuf *buf_out, DynBuf *buf_err) {
lua_createtable(L, 0, 3);
lua_pushinteger(L, code);
lua_setfield(L, -2, "code");
lua_pushlstring(L, buf_out->data ? buf_out->data : "", buf_out->len);
lua_setfield(L, -2, "stdout");
lua_pushlstring(L, buf_err->data ? buf_err->data : "", buf_err->len);
lua_setfield(L, -2, "stderr");
}
/* /*
** Execute a multi-stage pipeline. ** Execute a multi-stage pipeline.
** stages[0..nstages-1] are command strings. Each is parsed with parse_argv(). ** stages[0..nstages-1] are command strings. Each is parsed with parse_argv().
@@ -850,17 +888,7 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages,
exec_user_command(L, &pa[i]); exec_user_command(L, &pa[i]);
execvp(pa[i].argv[0], pa[i].argv); execvp(pa[i].argv[0], pa[i].argv);
exec_failed(pa[i].argv[0]);
/* exec failed */
{
const char *err = strerror(errno);
size_t namelen = strlen(pa[i].argv[0]);
size_t errlen = strlen(err);
(void)write(STDERR_FILENO, pa[i].argv[0], namelen);
(void)write(STDERR_FILENO, ": ", 2);
(void)write(STDERR_FILENO, err, errlen);
(void)write(STDERR_FILENO, "\n", 1);
}
_exit(127); _exit(127);
} }
} }
@@ -892,14 +920,8 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages,
for (i = 0; i < nstages; i++) { for (i = 0; i < nstages; i++) {
int status; int status;
waitpid(pids[i], &status, 0); waitpid(pids[i], &status, 0);
if (i == nstages - 1) { if (i == nstages - 1)
if (WIFEXITED(status)) last_code = exit_code_from_status(status);
last_code = WEXITSTATUS(status);
else if (WIFSIGNALED(status))
last_code = 128 + WTERMSIG(status);
else
last_code = -1;
}
} }
free(pids); free(pids);
@@ -927,21 +949,8 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages,
*/ */
static int try_builtin (lua_State *L, ParsedArgs *pa) { static int try_builtin (lua_State *L, ParsedArgs *pa) {
int i; int i;
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH); if (!lookup_lush_func(L, "builtins", pa->argv[0]))
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return 0; return 0;
}
if (lua_getfield(L, -1, "builtins") != LUA_TTABLE) {
lua_pop(L, 2);
return 0;
}
if (lua_getfield(L, -1, pa->argv[0]) != LUA_TFUNCTION) {
lua_pop(L, 3);
return 0;
}
lua_remove(L, -2); /* remove builtins table */
lua_remove(L, -2); /* remove lush table */
for (i = 0; i < pa->argc; i++) for (i = 0; i < pa->argc; i++)
lua_pushstring(L, pa->argv[i]); lua_pushstring(L, pa->argv[i]);
lua_call(L, pa->argc, 1); lua_call(L, pa->argc, 1);
@@ -1010,6 +1019,97 @@ static int expand_alias (lua_State *L, const char **cmd) {
} }
/* ===== single-command fork/exec/wait ===== */
/*
** Fork, exec a single command, wait, and push a result table.
** If capture is true, stdout/stderr are captured via pipes.
** If capture is false, the child inherits the terminal.
*/
static void fork_exec_single (lua_State *L, ParsedArgs *pa, int capture) {
int out_pipe[2], err_pipe[2];
pid_t pid;
int status;
DynBuf buf_out, buf_err;
struct sigaction sa_old_pipe, sa_old_int, sa_old_quit, sa_new;
if (capture) {
if (pipe(out_pipe) != 0)
luaL_error(L, "pipe() failed: %s", strerror(errno));
if (pipe(err_pipe) != 0) {
close(out_pipe[0]); close(out_pipe[1]);
luaL_error(L, "pipe() failed: %s", strerror(errno));
}
}
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);
if (!capture) {
sigaction(SIGINT, &sa_new, &sa_old_int);
sigaction(SIGQUIT, &sa_new, &sa_old_quit);
}
pid = fork();
if (pid < 0) {
if (capture) {
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
}
sigaction(SIGPIPE, &sa_old_pipe, NULL);
if (!capture) {
sigaction(SIGINT, &sa_old_int, NULL);
sigaction(SIGQUIT, &sa_old_quit, NULL);
}
luaL_error(L, "fork() failed: %s", strerror(errno));
}
if (pid == 0) {
/* child */
sa_new.sa_handler = SIG_DFL;
sigaction(SIGPIPE, &sa_new, NULL);
if (!capture) {
sigaction(SIGINT, &sa_new, NULL);
sigaction(SIGQUIT, &sa_new, NULL);
}
if (capture) {
close(out_pipe[0]);
close(err_pipe[0]);
dup2(out_pipe[1], STDOUT_FILENO);
dup2(err_pipe[1], STDERR_FILENO);
close(out_pipe[1]);
close(err_pipe[1]);
}
exec_user_command(L, pa);
execvp(pa->argv[0], pa->argv);
exec_failed(pa->argv[0]);
_exit(127);
}
/* parent */
dynbuf_init(&buf_out);
dynbuf_init(&buf_err);
if (capture) {
close(out_pipe[1]);
close(err_pipe[1]);
read_pipes(out_pipe[0], err_pipe[0], &buf_out, &buf_err);
}
waitpid(pid, &status, 0);
sigaction(SIGPIPE, &sa_old_pipe, NULL);
if (!capture) {
sigaction(SIGINT, &sa_old_int, NULL);
sigaction(SIGQUIT, &sa_old_quit, NULL);
}
push_result_table(L, exit_code_from_status(status), &buf_out, &buf_err);
dynbuf_free(&buf_out);
dynbuf_free(&buf_err);
}
/* ===== lushCmd_command ===== */ /* ===== lushCmd_command ===== */
int lushCmd_command (lua_State *L) { int lushCmd_command (lua_State *L) {
@@ -1017,11 +1117,7 @@ int lushCmd_command (lua_State *L) {
char *stages[MAX_PIPELINE_STAGES]; char *stages[MAX_PIPELINE_STAGES];
int nstages; int nstages;
ParsedArgs pa; ParsedArgs pa;
int out_pipe[2], err_pipe[2]; DynBuf empty_out, empty_err;
pid_t pid;
int status;
DynBuf buf_out, buf_err;
struct sigaction sa_old, sa_new;
/* expand alias before pipeline splitting */ /* expand alias before pipeline splitting */
expand_alias(L, &cmd); expand_alias(L, &cmd);
@@ -1047,13 +1143,9 @@ int lushCmd_command (lua_State *L) {
/* empty command: return {code=0, stdout="", stderr=""} */ /* empty command: return {code=0, stdout="", stderr=""} */
if (pa.argc == 0) { if (pa.argc == 0) {
free_argv(&pa); free_argv(&pa);
lua_createtable(L, 0, 3); dynbuf_init(&empty_out);
lua_pushinteger(L, 0); dynbuf_init(&empty_err);
lua_setfield(L, -2, "code"); push_result_table(L, 0, &empty_out, &empty_err);
lua_pushliteral(L, "");
lua_setfield(L, -2, "stdout");
lua_pushliteral(L, "");
lua_setfield(L, -2, "stderr");
return 1; return 1;
} }
@@ -1063,96 +1155,8 @@ int lushCmd_command (lua_State *L) {
return 1; return 1;
} }
/* create pipes */ fork_exec_single(L, &pa, 1);
if (pipe(out_pipe) != 0) {
free_argv(&pa);
return luaL_error(L, "pipe() failed: %s", strerror(errno));
}
if (pipe(err_pipe) != 0) {
close(out_pipe[0]); close(out_pipe[1]);
free_argv(&pa);
return luaL_error(L, "pipe() failed: %s", strerror(errno));
}
/* ignore SIGPIPE so parent doesn't crash if child exits early */
memset(&sa_new, 0, sizeof(sa_new));
sa_new.sa_handler = SIG_IGN;
sigemptyset(&sa_new.sa_mask);
sigaction(SIGPIPE, &sa_new, &sa_old);
pid = fork();
if (pid < 0) {
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
free_argv(&pa);
sigaction(SIGPIPE, &sa_old, NULL);
return luaL_error(L, "fork() failed: %s", strerror(errno));
}
if (pid == 0) {
/* child */
close(out_pipe[0]);
close(err_pipe[0]);
dup2(out_pipe[1], STDOUT_FILENO);
dup2(err_pipe[1], STDERR_FILENO);
close(out_pipe[1]);
close(err_pipe[1]);
/* restore default SIGPIPE for child */
sa_new.sa_handler = SIG_DFL;
sigaction(SIGPIPE, &sa_new, NULL);
exec_user_command(L, &pa);
execvp(pa.argv[0], pa.argv);
/* exec failed — write error to stderr and exit */
{
const char *err = strerror(errno);
size_t namelen = strlen(pa.argv[0]);
size_t errlen = strlen(err);
/* write "cmd: error\n" */
(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 */
close(out_pipe[1]);
close(err_pipe[1]);
free_argv(&pa); free_argv(&pa);
dynbuf_init(&buf_out);
dynbuf_init(&buf_err);
read_pipes(out_pipe[0], err_pipe[0], &buf_out, &buf_err);
waitpid(pid, &status, 0);
/* restore old SIGPIPE handler */
sigaction(SIGPIPE, &sa_old, NULL);
/* build result table */
lua_createtable(L, 0, 3);
if (WIFEXITED(status))
lua_pushinteger(L, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
lua_pushinteger(L, 128 + WTERMSIG(status));
else
lua_pushinteger(L, -1);
lua_setfield(L, -2, "code");
lua_pushlstring(L, buf_out.data ? buf_out.data : "", buf_out.len);
lua_setfield(L, -2, "stdout");
lua_pushlstring(L, buf_err.data ? buf_err.data : "", buf_err.len);
lua_setfield(L, -2, "stderr");
dynbuf_free(&buf_out);
dynbuf_free(&buf_err);
return 1; return 1;
} }
@@ -1188,10 +1192,7 @@ int lushCmd_interactive (lua_State *L) {
char *stages[MAX_PIPELINE_STAGES]; char *stages[MAX_PIPELINE_STAGES];
int nstages; int nstages;
ParsedArgs pa; ParsedArgs pa;
pid_t pid; DynBuf empty_out, empty_err;
int status, code;
struct sigaction sa_old_pipe, sa_old_int, sa_old_quit, sa_new;
DynBuf buf_out, buf_err;
/* expand alias before pipeline splitting */ /* expand alias before pipeline splitting */
expand_alias(L, &cmd); expand_alias(L, &cmd);
@@ -1216,9 +1217,9 @@ int lushCmd_interactive (lua_State *L) {
if (pa.argc == 0) { if (pa.argc == 0) {
free_argv(&pa); free_argv(&pa);
dynbuf_init(&buf_out); dynbuf_init(&empty_out);
dynbuf_init(&buf_err); dynbuf_init(&empty_err);
push_result_table(L, 0, &buf_out, &buf_err); push_result_table(L, 0, &empty_out, &empty_err);
lua_setglobal(L, "_"); lua_setglobal(L, "_");
return 0; return 0;
} }
@@ -1230,69 +1231,10 @@ int lushCmd_interactive (lua_State *L) {
return 0; return 0;
} }
/* ignore SIGINT, SIGQUIT, SIGPIPE in parent */ fork_exec_single(L, &pa, 0);
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);
exec_user_command(L, &pa);
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); 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, "_"); lua_setglobal(L, "_");
return 0;
return 0; /* void — no Lua return values */
} }

View File

@@ -553,7 +553,20 @@ static void singlevar (LexState *ls, expdesc *var) {
} }
static void codeenvget (LexState *ls, expdesc *v, TString *name); static void codeenvget (LexState *ls, expdesc *v, TString *name) {
FuncState *fs = ls->fs;
int base, line;
expdesc func, arg;
line = ls->linenumber;
codelushfunc(fs, LUSH_OP_GETENV, &func);
base = func.u.info;
codestring(&arg, name);
luaK_exp2nextreg(fs, &arg);
init_exp(v, VCALL, luaK_codeABC(fs, OP_CALL, base, 2, 2));
luaK_fixline(fs, line);
fs->freereg = cast_byte(base + 1);
}
/* /*
** Parse a single interpolation fragment in a command: ${expr}, $NAME, ** Parse a single interpolation fragment in a command: ${expr}, $NAME,
@@ -1323,21 +1336,6 @@ static void commandexp (LexState *ls, expdesc *v) {
} }
static void codeenvget (LexState *ls, expdesc *v, TString *name) {
FuncState *fs = ls->fs;
int base, line;
expdesc func, arg;
line = ls->linenumber;
codelushfunc(fs, LUSH_OP_GETENV, &func);
base = func.u.info;
codestring(&arg, name);
luaK_exp2nextreg(fs, &arg);
init_exp(v, VCALL, luaK_codeABC(fs, OP_CALL, base, 2, 2));
luaK_fixline(fs, line);
fs->freereg = cast_byte(base + 1);
}
static void primaryexp (LexState *ls, expdesc *v) { static void primaryexp (LexState *ls, expdesc *v) {
/* primaryexp -> NAME | '(' expr ')' | COMMAND | ENVVAR */ /* primaryexp -> NAME | '(' expr ')' | COMMAND | ENVVAR */
switch (ls->t.token) { switch (ls->t.token) {