Files
lush/lbuiltin.c
Cormac Shannon f88b17959f Hide shell functions from _G using upvalues on the main chunk
Store __command, __interactive, __getenv, __setenv as upvalues
populated by lua_load() from the registry, keeping them invisible
to user code while accessible to the parser's codegen.
2026-03-12 22:46:56 +00:00

164 lines
3.8 KiB
C

/*
** $Id: lbuiltin.c $
** Shell builtins (cd, exec, umask)
** See Copyright Notice in lua.h
*/
#define lbuiltin_c
#define LUA_LIB
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "lua.h"
#include "lauxlib.h"
#include "lbuiltin.h"
static void push_builtin_result (lua_State *L, int code,
const char *out, const char *err) {
lua_createtable(L, 0, 3);
lua_pushinteger(L, code);
lua_setfield(L, -2, "code");
lua_pushstring(L, out ? out : "");
lua_setfield(L, -2, "stdout");
lua_pushstring(L, err ? err : "");
lua_setfield(L, -2, "stderr");
}
/*
** builtin_cd(cmd, [dir])
** Change working directory. No arg → $HOME, "-" → $OLDPWD.
** Updates $PWD and $OLDPWD.
*/
static int builtin_cd (lua_State *L) {
const char *dir;
char oldpwd[4096];
char newpwd[4096];
if (lua_gettop(L) < 2 || lua_isnil(L, 2)) {
/* no dir argument: use $HOME */
dir = getenv("HOME");
if (dir == NULL) {
push_builtin_result(L, 1, NULL, "cd: HOME not set");
return 1;
}
}
else {
dir = luaL_checkstring(L, 2);
if (strcmp(dir, "-") == 0) {
dir = getenv("OLDPWD");
if (dir == NULL) {
push_builtin_result(L, 1, NULL, "cd: OLDPWD not set");
return 1;
}
}
}
/* save current directory */
if (getcwd(oldpwd, sizeof(oldpwd)) == NULL)
oldpwd[0] = '\0';
if (chdir(dir) != 0) {
char errbuf[4200];
snprintf(errbuf, sizeof(errbuf), "cd: %s: %s", dir, strerror(errno));
push_builtin_result(L, 1, NULL, errbuf);
return 1;
}
/* get resolved path */
if (getcwd(newpwd, sizeof(newpwd)) == NULL)
newpwd[0] = '\0';
setenv("OLDPWD", oldpwd, 1);
setenv("PWD", newpwd, 1);
push_builtin_result(L, 0, NULL, NULL);
return 1;
}
/*
** builtin_exec(cmd, program, [args...])
** Replace the shell process via execvp(2).
*/
static int builtin_exec (lua_State *L) {
int nargs = lua_gettop(L);
int i;
char **argv;
char errbuf[4200];
if (nargs < 2) {
push_builtin_result(L, 1, NULL, "exec: command required");
return 1;
}
/* build argv from args 2..nargs (skip "exec" at arg 1) */
argv = (char **)malloc((size_t)(nargs) * sizeof(char *));
if (argv == NULL)
return luaL_error(L, "out of memory");
for (i = 2; i <= nargs; i++)
argv[i - 2] = (char *)luaL_checkstring(L, i);
argv[nargs - 1] = NULL;
execvp(argv[0], argv);
/* execvp only returns on failure */
snprintf(errbuf, sizeof(errbuf), "exec: %s: %s", argv[0], strerror(errno));
free(argv);
push_builtin_result(L, 1, NULL, errbuf);
return 1;
}
/*
** builtin_umask(cmd, [mode])
** No arg: query and print current umask. With arg: set umask from octal string.
*/
static int builtin_umask (lua_State *L) {
if (lua_gettop(L) < 2 || lua_isnil(L, 2)) {
/* query mode */
mode_t old = umask(0);
char buf[16];
umask(old); /* restore */
snprintf(buf, sizeof(buf), "%04o\n", (unsigned)old);
push_builtin_result(L, 0, buf, NULL);
return 1;
}
else {
const char *modestr = luaL_checkstring(L, 2);
char *endp;
unsigned long val;
errno = 0;
val = strtoul(modestr, &endp, 8);
if (errno != 0 || *endp != '\0' || endp == modestr || val > 0777) {
push_builtin_result(L, 1, NULL, "umask: invalid mode");
return 1;
}
umask((mode_t)val);
push_builtin_result(L, 0, NULL, NULL);
return 1;
}
}
void luaopen_builtins (lua_State *L) {
lua_createtable(L, 0, 3);
lua_pushcfunction(L, builtin_cd);
lua_setfield(L, -2, "cd");
lua_pushcfunction(L, builtin_exec);
lua_setfield(L, -2, "exec");
lua_pushcfunction(L, builtin_umask);
lua_setfield(L, -2, "umask");
/* Store as __lush.builtins in registry */
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH);
lua_insert(L, -2);
lua_setfield(L, -2, "builtins");
lua_pop(L, 1);
}