Implement shell builtins: cd, exec, umask (issue #15)
Add builtin dispatch in lcmd.c that checks __builtins table before fork(), so cd/exec/umask operate on the shell process itself.
This commit is contained in:
159
lbuiltin.c
Normal file
159
lbuiltin.c
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
** $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");
|
||||
lua_setglobal(L, "__builtins");
|
||||
}
|
||||
Reference in New Issue
Block a user