From 9bc7dc01b780a4cfc69c6c3ff6eb9500b2b252da Mon Sep 17 00:00:00 2001 From: Cormac Shannon <> Date: Sun, 22 Mar 2026 17:04:02 +0000 Subject: [PATCH] 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. --- lcmd.c | 408 +++++++++++++++++++++++------------------------------- lparser.c | 30 ++-- 2 files changed, 189 insertions(+), 249 deletions(-) diff --git a/lcmd.c b/lcmd.c index 08cae4c9..f5fd6fdd 100644 --- a/lcmd.c +++ b/lcmd.c @@ -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) ===== */ /* @@ -617,21 +685,8 @@ static void read_pipes (int fd_out, int fd_err, */ static int exec_user_command (lua_State *L, ParsedArgs *pa) { int i, code = 0; - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH); - if (!lua_istable(L, -1)) { - lua_pop(L, 1); + if (!lookup_lush_func(L, "commands", pa->argv[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++) lua_pushstring(L, pa->argv[i]); 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. ** 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]); execvp(pa[i].argv[0], pa[i].argv); - - /* 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); - } + exec_failed(pa[i].argv[0]); _exit(127); } } @@ -892,14 +920,8 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages, for (i = 0; i < nstages; i++) { int status; waitpid(pids[i], &status, 0); - if (i == nstages - 1) { - if (WIFEXITED(status)) - last_code = WEXITSTATUS(status); - else if (WIFSIGNALED(status)) - last_code = 128 + WTERMSIG(status); - else - last_code = -1; - } + if (i == nstages - 1) + last_code = exit_code_from_status(status); } 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) { int i; - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH); - if (!lua_istable(L, -1)) { - lua_pop(L, 1); + if (!lookup_lush_func(L, "builtins", pa->argv[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++) lua_pushstring(L, pa->argv[i]); 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 ===== */ int lushCmd_command (lua_State *L) { @@ -1017,11 +1117,7 @@ int lushCmd_command (lua_State *L) { char *stages[MAX_PIPELINE_STAGES]; int nstages; ParsedArgs pa; - int out_pipe[2], err_pipe[2]; - pid_t pid; - int status; - DynBuf buf_out, buf_err; - struct sigaction sa_old, sa_new; + DynBuf empty_out, empty_err; /* expand alias before pipeline splitting */ expand_alias(L, &cmd); @@ -1047,13 +1143,9 @@ int lushCmd_command (lua_State *L) { /* empty command: return {code=0, stdout="", stderr=""} */ if (pa.argc == 0) { free_argv(&pa); - lua_createtable(L, 0, 3); - lua_pushinteger(L, 0); - lua_setfield(L, -2, "code"); - lua_pushliteral(L, ""); - lua_setfield(L, -2, "stdout"); - lua_pushliteral(L, ""); - lua_setfield(L, -2, "stderr"); + dynbuf_init(&empty_out); + dynbuf_init(&empty_err); + push_result_table(L, 0, &empty_out, &empty_err); return 1; } @@ -1063,96 +1155,8 @@ int lushCmd_command (lua_State *L) { return 1; } - /* create pipes */ - 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]); + fork_exec_single(L, &pa, 1); 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; } @@ -1188,10 +1192,7 @@ int lushCmd_interactive (lua_State *L) { 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; + DynBuf empty_out, empty_err; /* expand alias before pipeline splitting */ expand_alias(L, &cmd); @@ -1216,9 +1217,9 @@ int lushCmd_interactive (lua_State *L) { if (pa.argc == 0) { free_argv(&pa); - dynbuf_init(&buf_out); - dynbuf_init(&buf_err); - push_result_table(L, 0, &buf_out, &buf_err); + dynbuf_init(&empty_out); + dynbuf_init(&empty_err); + push_result_table(L, 0, &empty_out, &empty_err); lua_setglobal(L, "_"); return 0; } @@ -1230,69 +1231,10 @@ int lushCmd_interactive (lua_State *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); - - 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 */ + fork_exec_single(L, &pa, 0); 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 */ + return 0; } diff --git a/lparser.c b/lparser.c index f27c1642..4dc73ee0 100644 --- a/lparser.c +++ b/lparser.c @@ -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, @@ -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) { /* primaryexp -> NAME | '(' expr ')' | COMMAND | ENVVAR */ switch (ls->t.token) {