Aliases rewrite the command string before pipeline splitting, so alias values may contain pipes (e.g. lush.aliases.foo = "echo hi |"). Expansion happens once per dispatch to prevent recursion.
1301 lines
34 KiB
C
1301 lines
34 KiB
C
/*
|
|
** $Id: lcmd.c $
|
|
** Command execution for backtick syntax
|
|
** See Copyright Notice in lua.h
|
|
*/
|
|
|
|
#define lcmd_c
|
|
#define LUA_LIB
|
|
|
|
#include "lprefix.h"
|
|
|
|
#include <errno.h>
|
|
#include <glob.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/select.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include "lua.h"
|
|
#include "lauxlib.h"
|
|
#include "lcmd.h"
|
|
#include "lbuiltin.h"
|
|
#include "lstring.h"
|
|
|
|
|
|
/* ===== argv parser ===== */
|
|
|
|
#define PA_QUOTED 1 /* suppress glob/tilde expansion for this token */
|
|
|
|
typedef struct {
|
|
char **argv;
|
|
char *buf;
|
|
int argc;
|
|
int *flags; /* per-token flags (PA_QUOTED etc.) */
|
|
} ParsedArgs;
|
|
|
|
|
|
static void free_argv (ParsedArgs *pa) {
|
|
free(pa->buf);
|
|
free(pa->argv);
|
|
free(pa->flags);
|
|
}
|
|
|
|
|
|
/* ===== glob/tilde/brace expansion ===== */
|
|
|
|
/*
|
|
** Expand ~ at the start of a token to the home directory.
|
|
** Returns a malloc'd string (caller must free), or NULL on failure.
|
|
** If no tilde prefix, returns a strdup of the original.
|
|
*/
|
|
static char *expand_tilde (const char *token) {
|
|
const char *home;
|
|
size_t homelen, restlen;
|
|
char *result;
|
|
if (token[0] != '~')
|
|
return strdup(token);
|
|
/* only handle plain ~ and ~/path (not ~user for now) */
|
|
if (token[1] != '\0' && token[1] != '/')
|
|
return strdup(token);
|
|
home = getenv("HOME");
|
|
if (home == NULL)
|
|
return strdup(token);
|
|
homelen = strlen(home);
|
|
restlen = strlen(token + 1); /* everything after ~ */
|
|
result = (char *)malloc(homelen + restlen + 1);
|
|
if (result == NULL) return NULL;
|
|
memcpy(result, home, homelen);
|
|
memcpy(result + homelen, token + 1, restlen + 1);
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
** Brace expansion: expand {a,b,c} patterns into multiple strings.
|
|
** Handles prefix{a,b,c}suffix → prefixasuffix prefixbsuffix prefixcsuffix.
|
|
** Does NOT handle nested braces (kept literal).
|
|
** Writes results into out[] (caller provides), returns count.
|
|
** out[] entries are malloc'd strings. max_out limits array size.
|
|
** Returns 0 if no braces found (token unchanged).
|
|
*/
|
|
#define MAX_BRACE_RESULTS 256
|
|
|
|
static int expand_braces (const char *token, char **out, int max_out) {
|
|
const char *open, *close, *p;
|
|
size_t prefix_len, suffix_len;
|
|
int depth, ncommas, count;
|
|
const char *alts[MAX_BRACE_RESULTS];
|
|
size_t altlens[MAX_BRACE_RESULTS];
|
|
int nalts = 0;
|
|
|
|
/* find first '{' */
|
|
open = strchr(token, '{');
|
|
if (open == NULL) return 0;
|
|
|
|
/* find matching '}' at depth 0, counting commas */
|
|
depth = 1; ncommas = 0;
|
|
for (p = open + 1; *p != '\0' && depth > 0; p++) {
|
|
if (*p == '{') depth++;
|
|
else if (*p == '}') depth--;
|
|
else if (*p == ',' && depth == 1) ncommas++;
|
|
}
|
|
if (depth != 0 || ncommas == 0)
|
|
return 0; /* no valid brace expression */
|
|
|
|
close = p - 1; /* points to '}' */
|
|
prefix_len = (size_t)(open - token);
|
|
suffix_len = strlen(close + 1);
|
|
|
|
/* split alternatives on ',' at depth 0 */
|
|
p = open + 1;
|
|
alts[0] = p;
|
|
for (; p < close; p++) {
|
|
if (*p == '{') depth++;
|
|
else if (*p == '}') depth--;
|
|
else if (*p == ',' && depth == 0) {
|
|
altlens[nalts] = (size_t)(p - alts[nalts]);
|
|
nalts++;
|
|
if (nalts >= MAX_BRACE_RESULTS) return 0;
|
|
alts[nalts] = p + 1;
|
|
}
|
|
}
|
|
altlens[nalts] = (size_t)(close - alts[nalts]);
|
|
nalts++;
|
|
|
|
/* generate prefix + alt + suffix for each alternative */
|
|
count = 0;
|
|
for (int i = 0; i < nalts && count < max_out; i++) {
|
|
size_t total = prefix_len + altlens[i] + suffix_len + 1;
|
|
char *s = (char *)malloc(total);
|
|
if (s == NULL) {
|
|
for (int j = 0; j < count; j++) free(out[j]);
|
|
return -1;
|
|
}
|
|
memcpy(s, token, prefix_len);
|
|
memcpy(s + prefix_len, alts[i], altlens[i]);
|
|
memcpy(s + prefix_len + altlens[i], close + 1, suffix_len + 1);
|
|
out[count++] = s;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
/*
|
|
** Growing list of tokens used during expansion.
|
|
** Each item is a separately malloc'd string.
|
|
*/
|
|
typedef struct {
|
|
char **argv;
|
|
int *flags;
|
|
int argc;
|
|
int capacity;
|
|
} TokenList;
|
|
|
|
|
|
static int tklist_init (TokenList *tl, int capacity) {
|
|
tl->argv = (char **)malloc((size_t)capacity * sizeof(char *));
|
|
tl->flags = (int *)calloc((size_t)capacity, sizeof(int));
|
|
tl->argc = 0;
|
|
tl->capacity = capacity;
|
|
if (tl->argv == NULL || tl->flags == NULL) {
|
|
free(tl->argv); free(tl->flags);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void tklist_free (TokenList *tl) {
|
|
int i;
|
|
for (i = 0; i < tl->argc; i++)
|
|
free(tl->argv[i]);
|
|
free(tl->argv);
|
|
free(tl->flags);
|
|
}
|
|
|
|
|
|
/*
|
|
** Add a token to the list. Takes ownership of 's' on success.
|
|
** Returns 0 on success, -1 on allocation failure (caller still owns 's').
|
|
*/
|
|
static int tklist_add (TokenList *tl, char *s, int flag) {
|
|
if (tl->argc >= tl->capacity) {
|
|
int newcap = tl->capacity * 2;
|
|
char **a = (char **)realloc(tl->argv, (size_t)newcap * sizeof(char *));
|
|
int *f = (int *)realloc(tl->flags, (size_t)newcap * sizeof(int));
|
|
if (a == NULL || f == NULL) {
|
|
/* if one succeeded, keep the original pointer valid */
|
|
if (a) tl->argv = a;
|
|
if (f) tl->flags = f;
|
|
return -1;
|
|
}
|
|
tl->argv = a;
|
|
tl->flags = f;
|
|
tl->capacity = newcap;
|
|
}
|
|
tl->argv[tl->argc] = s;
|
|
tl->flags[tl->argc] = flag;
|
|
tl->argc++;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
** Glob a single token and append results to 'tl'.
|
|
** If the token has no metacharacters, it is added as-is (takes ownership).
|
|
** Otherwise, glob results are added and 'token' is freed.
|
|
** Returns 0 on success, -1 on failure (token is freed either way).
|
|
*/
|
|
static int glob_token (TokenList *tl, char *token) {
|
|
const char *s;
|
|
int has_meta = 0;
|
|
for (s = token; *s != '\0'; s++) {
|
|
if (*s == '*' || *s == '?' || *s == '[') { has_meta = 1; break; }
|
|
}
|
|
if (!has_meta)
|
|
return tklist_add(tl, token, 0); /* no glob needed */
|
|
{
|
|
glob_t g;
|
|
int ret = glob(token, GLOB_NOCHECK, NULL, &g);
|
|
free(token);
|
|
if (ret != 0)
|
|
return 0; /* GLOB_NOCHECK means this shouldn't happen, but be safe */
|
|
{
|
|
size_t k;
|
|
for (k = 0; k < g.gl_pathc; k++) {
|
|
char *dup = strdup(g.gl_pathv[k]);
|
|
if (dup == NULL || tklist_add(tl, dup, 0) != 0) {
|
|
free(dup);
|
|
globfree(&g);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
globfree(&g);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
** Expand unquoted tokens: tilde → brace → glob.
|
|
** Quoted tokens (PA_QUOTED) are passed through unchanged.
|
|
** Replaces pa->argv, pa->buf, and pa->flags in place.
|
|
** Returns 0 on success, -1 on allocation failure.
|
|
*/
|
|
static int expand_argv (ParsedArgs *pa) {
|
|
int i;
|
|
TokenList tl;
|
|
size_t total_buflen = 0;
|
|
char *new_buf;
|
|
size_t bp = 0;
|
|
|
|
if (tklist_init(&tl, pa->argc + 16) != 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < pa->argc; i++) {
|
|
if (pa->flags[i] & PA_QUOTED) {
|
|
char *dup = strdup(pa->argv[i]);
|
|
if (dup == NULL || tklist_add(&tl, dup, PA_QUOTED) != 0) {
|
|
free(dup); goto fail;
|
|
}
|
|
} else {
|
|
char *tilded;
|
|
char *brace_results[MAX_BRACE_RESULTS];
|
|
int nbrace;
|
|
|
|
/* step 1: tilde expansion */
|
|
tilded = expand_tilde(pa->argv[i]);
|
|
if (tilded == NULL) goto fail;
|
|
|
|
/* step 2: brace expansion */
|
|
nbrace = expand_braces(tilded, brace_results, MAX_BRACE_RESULTS);
|
|
if (nbrace < 0) { free(tilded); goto fail; }
|
|
|
|
if (nbrace == 0) {
|
|
/* no braces — glob the tilde-expanded token */
|
|
if (glob_token(&tl, tilded) != 0) goto fail;
|
|
} else {
|
|
int j;
|
|
free(tilded);
|
|
/* glob each brace alternative */
|
|
for (j = 0; j < nbrace; j++) {
|
|
if (glob_token(&tl, brace_results[j]) != 0) {
|
|
/* free remaining brace results */
|
|
for (j++; j < nbrace; j++) free(brace_results[j]);
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* pack all tokens into a single contiguous buffer */
|
|
for (i = 0; i < tl.argc; i++)
|
|
total_buflen += strlen(tl.argv[i]) + 1;
|
|
|
|
new_buf = (char *)malloc(total_buflen > 0 ? total_buflen : 1);
|
|
if (new_buf == NULL) goto fail;
|
|
|
|
for (i = 0; i < tl.argc; i++) {
|
|
char *old = tl.argv[i];
|
|
size_t slen = strlen(old);
|
|
memcpy(new_buf + bp, old, slen + 1);
|
|
tl.argv[i] = new_buf + bp;
|
|
bp += slen + 1;
|
|
free(old);
|
|
}
|
|
|
|
/* replace old arrays */
|
|
free(pa->argv);
|
|
free(pa->buf);
|
|
free(pa->flags);
|
|
pa->argv = (char **)realloc(tl.argv, ((size_t)tl.argc + 1) * sizeof(char *));
|
|
if (pa->argv == NULL) pa->argv = tl.argv;
|
|
pa->argv[tl.argc] = NULL;
|
|
pa->buf = new_buf;
|
|
pa->flags = tl.flags;
|
|
pa->argc = tl.argc;
|
|
return 0;
|
|
|
|
fail:
|
|
tklist_free(&tl);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*
|
|
** Tokenize command string into argv array.
|
|
** Handles: whitespace splitting, single quotes (literal),
|
|
** double quotes (with \", \\, \n, \t, \$ escapes), backslash escaping.
|
|
** Returns 0 on success, -1 on error (unterminated quote).
|
|
*/
|
|
static int parse_argv (const char *cmd, ParsedArgs *result) {
|
|
size_t len = strlen(cmd);
|
|
char *buf = (char *)malloc(len + 1);
|
|
int capacity = 8;
|
|
char **argv = (char **)malloc((size_t)capacity * sizeof(char *));
|
|
int *flags = (int *)calloc((size_t)capacity, sizeof(int));
|
|
int argc = 0;
|
|
size_t bp = 0; /* position in buf */
|
|
const char *p = cmd;
|
|
|
|
if (buf == NULL || argv == NULL || flags == NULL) {
|
|
free(buf);
|
|
free(argv);
|
|
free(flags);
|
|
return -1;
|
|
}
|
|
|
|
while (*p != '\0') {
|
|
int quoted = 0;
|
|
|
|
/* skip whitespace */
|
|
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
|
|
p++;
|
|
if (*p == '\0')
|
|
break;
|
|
|
|
/* start of a token */
|
|
if (argc >= capacity - 1) {
|
|
capacity *= 2;
|
|
argv = (char **)realloc(argv, (size_t)capacity * sizeof(char *));
|
|
flags = (int *)realloc(flags, (size_t)capacity * sizeof(int));
|
|
if (argv == NULL || flags == NULL) {
|
|
free(buf); free(argv); free(flags);
|
|
return -1;
|
|
}
|
|
}
|
|
argv[argc] = buf + bp;
|
|
|
|
while (*p != '\0') {
|
|
if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
|
|
break;
|
|
|
|
if (*p == '\'') {
|
|
/* single quote: literal until closing quote */
|
|
quoted = 1;
|
|
p++;
|
|
while (*p != '\0' && *p != '\'')
|
|
buf[bp++] = *p++;
|
|
if (*p == '\'') p++;
|
|
else { free(buf); free(argv); free(flags); return -1; }
|
|
}
|
|
else if (*p == '"') {
|
|
/* double quote: with escape sequences */
|
|
quoted = 1;
|
|
p++;
|
|
while (*p != '\0' && *p != '"') {
|
|
if (*p == '\\' && p[1] != '\0') {
|
|
p++;
|
|
switch (*p) {
|
|
case '"': buf[bp++] = '"'; break;
|
|
case '\\': buf[bp++] = '\\'; break;
|
|
case 'n': buf[bp++] = '\n'; break;
|
|
case 't': buf[bp++] = '\t'; break;
|
|
case '$': buf[bp++] = '$'; break;
|
|
default: buf[bp++] = '\\'; buf[bp++] = *p; break;
|
|
}
|
|
p++;
|
|
}
|
|
else {
|
|
buf[bp++] = *p++;
|
|
}
|
|
}
|
|
if (*p == '"') p++;
|
|
else { free(buf); free(argv); free(flags); return -1; }
|
|
}
|
|
else if (*p == '\\' && p[1] != '\0') {
|
|
/* backslash escape outside quotes */
|
|
quoted = 1;
|
|
p++;
|
|
buf[bp++] = *p++;
|
|
}
|
|
else {
|
|
buf[bp++] = *p++;
|
|
}
|
|
}
|
|
|
|
buf[bp++] = '\0';
|
|
flags[argc] = quoted ? PA_QUOTED : 0;
|
|
argc++;
|
|
}
|
|
|
|
argv[argc] = NULL;
|
|
result->argv = argv;
|
|
result->buf = buf;
|
|
result->argc = argc;
|
|
result->flags = flags;
|
|
|
|
/* expand globs and tildes on unquoted tokens */
|
|
if (expand_argv(result) != 0) {
|
|
free(buf);
|
|
free(argv);
|
|
free(flags);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ===== pipeline splitter ===== */
|
|
|
|
#define MAX_PIPELINE_STAGES 64
|
|
|
|
/*
|
|
** Split a command string on unquoted '|' into pipeline stages.
|
|
** Reuses the same quote-tracking logic as parse_argv().
|
|
** Returns 0 on success, -1 on error (empty stage, leading/trailing pipe).
|
|
** Writes stage pointers into stages[] and count into *nstages.
|
|
** Caller must free stages[0] (the backing buffer).
|
|
*/
|
|
static int split_pipeline (const char *cmd, char **stages, int *nstages) {
|
|
size_t len = strlen(cmd);
|
|
char *buf = (char *)malloc(len + 1);
|
|
int count = 0;
|
|
const char *p = cmd;
|
|
size_t bp = 0;
|
|
|
|
if (buf == NULL) return -1;
|
|
|
|
stages[count++] = buf;
|
|
|
|
while (*p != '\0') {
|
|
if (*p == '\'' ) {
|
|
/* single-quoted string: copy through including quotes */
|
|
buf[bp++] = *p++;
|
|
while (*p != '\0' && *p != '\'')
|
|
buf[bp++] = *p++;
|
|
if (*p == '\'') buf[bp++] = *p++;
|
|
}
|
|
else if (*p == '"') {
|
|
/* double-quoted string: copy through, respecting backslash */
|
|
buf[bp++] = *p++;
|
|
while (*p != '\0' && *p != '"') {
|
|
if (*p == '\\' && p[1] != '\0') {
|
|
buf[bp++] = *p++;
|
|
buf[bp++] = *p++;
|
|
} else {
|
|
buf[bp++] = *p++;
|
|
}
|
|
}
|
|
if (*p == '"') buf[bp++] = *p++;
|
|
}
|
|
else if (*p == '\\' && p[1] != '\0') {
|
|
/* backslash escape: copy both chars */
|
|
buf[bp++] = *p++;
|
|
buf[bp++] = *p++;
|
|
}
|
|
else if (*p == '|') {
|
|
/* unquoted pipe — split here */
|
|
buf[bp++] = '\0';
|
|
if (count >= MAX_PIPELINE_STAGES) { free(buf); return -1; }
|
|
stages[count++] = buf + bp;
|
|
p++;
|
|
}
|
|
else {
|
|
buf[bp++] = *p++;
|
|
}
|
|
}
|
|
buf[bp] = '\0';
|
|
|
|
/* validate: no empty stages (only for multi-stage pipelines) */
|
|
if (count > 1) {
|
|
for (int i = 0; i < count; i++) {
|
|
const char *s = stages[i];
|
|
/* skip whitespace */
|
|
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
|
|
if (*s == '\0') { free(buf); return -1; }
|
|
}
|
|
}
|
|
|
|
*nstages = count;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ===== pipe reader ===== */
|
|
|
|
#define READ_CHUNK 4096
|
|
|
|
typedef struct {
|
|
char *data;
|
|
size_t len;
|
|
size_t cap;
|
|
} DynBuf;
|
|
|
|
|
|
static void dynbuf_init (DynBuf *b) {
|
|
b->data = NULL;
|
|
b->len = 0;
|
|
b->cap = 0;
|
|
}
|
|
|
|
|
|
static void dynbuf_free (DynBuf *b) {
|
|
free(b->data);
|
|
b->data = NULL;
|
|
b->len = b->cap = 0;
|
|
}
|
|
|
|
|
|
static int dynbuf_grow (DynBuf *b, size_t need) {
|
|
if (b->len + need > b->cap) {
|
|
size_t newcap = (b->cap == 0) ? READ_CHUNK : b->cap;
|
|
char *p;
|
|
while (newcap < b->len + need)
|
|
newcap *= 2;
|
|
p = (char *)realloc(b->data, newcap);
|
|
if (p == NULL) return -1;
|
|
b->data = p;
|
|
b->cap = newcap;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
** Read from two fds simultaneously into DynBufs using select().
|
|
** Avoids deadlock when child fills one pipe buffer.
|
|
*/
|
|
static void read_pipes (int fd_out, int fd_err,
|
|
DynBuf *buf_out, DynBuf *buf_err) {
|
|
int nfds = (fd_out > fd_err ? fd_out : fd_err) + 1;
|
|
int open_out = 1, open_err = 1;
|
|
|
|
while (open_out || open_err) {
|
|
fd_set rfds;
|
|
int ret;
|
|
|
|
FD_ZERO(&rfds);
|
|
if (open_out) FD_SET(fd_out, &rfds);
|
|
if (open_err) FD_SET(fd_err, &rfds);
|
|
|
|
ret = select(nfds, &rfds, NULL, NULL, NULL);
|
|
if (ret < 0) {
|
|
if (errno == EINTR) continue;
|
|
break; /* unexpected error */
|
|
}
|
|
|
|
if (open_out && FD_ISSET(fd_out, &rfds)) {
|
|
ssize_t n;
|
|
if (dynbuf_grow(buf_out, READ_CHUNK) != 0) break;
|
|
n = read(fd_out, buf_out->data + buf_out->len, READ_CHUNK);
|
|
if (n > 0)
|
|
buf_out->len += (size_t)n;
|
|
else {
|
|
close(fd_out);
|
|
open_out = 0;
|
|
}
|
|
}
|
|
|
|
if (open_err && FD_ISSET(fd_err, &rfds)) {
|
|
ssize_t n;
|
|
if (dynbuf_grow(buf_err, READ_CHUNK) != 0) break;
|
|
n = read(fd_err, buf_err->data + buf_err->len, READ_CHUNK);
|
|
if (n > 0)
|
|
buf_err->len += (size_t)n;
|
|
else {
|
|
close(fd_err);
|
|
open_err = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* ===== 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().
|
|
** Inter-stage pipes connect stdout→stdin. Only the last stage's stdout/stderr
|
|
** 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,
|
|
int interactive) {
|
|
ParsedArgs *pa = NULL;
|
|
int (*inter_pipes)[2] = NULL; /* inter_pipes[i] connects stage i → i+1 */
|
|
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_pipe, sa_old_int, sa_old_quit, sa_new;
|
|
|
|
/* parse all stages */
|
|
pa = (ParsedArgs *)calloc((size_t)nstages, sizeof(ParsedArgs));
|
|
if (pa == NULL) return luaL_error(L, "out of memory");
|
|
|
|
for (i = 0; i < nstages; i++) {
|
|
if (parse_argv(stages[i], &pa[i]) != 0) {
|
|
for (int j = 0; j < i; j++) free_argv(&pa[j]);
|
|
free(pa);
|
|
return luaL_error(L, "unterminated quote in pipeline stage %d", i + 1);
|
|
}
|
|
if (pa[i].argc == 0) {
|
|
for (int j = 0; j <= i; j++) free_argv(&pa[j]);
|
|
free(pa);
|
|
return luaL_error(L, "empty command in pipeline stage %d", i + 1);
|
|
}
|
|
}
|
|
|
|
/* allocate inter-stage pipes (nstages-1 of them) */
|
|
if (nstages > 1) {
|
|
inter_pipes = (int (*)[2])calloc((size_t)(nstages - 1), sizeof(int [2]));
|
|
if (inter_pipes == NULL) {
|
|
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
|
free(pa);
|
|
return luaL_error(L, "out of memory");
|
|
}
|
|
for (i = 0; i < nstages - 1; i++) {
|
|
if (pipe(inter_pipes[i]) != 0) {
|
|
for (int j = 0; j < i; 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);
|
|
return luaL_error(L, "pipe() failed: %s", strerror(errno));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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));
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
|
|
/* 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_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) {
|
|
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_pipe, NULL);
|
|
if (interactive) {
|
|
sigaction(SIGINT, &sa_old_int, NULL);
|
|
sigaction(SIGQUIT, &sa_old_quit, NULL);
|
|
}
|
|
return luaL_error(L, "out of memory");
|
|
}
|
|
|
|
/* fork all stages */
|
|
for (i = 0; i < nstages; i++) {
|
|
pids[i] = fork();
|
|
if (pids[i] < 0) {
|
|
/* fork failed — kill already-forked children */
|
|
for (int j = 0; j < i; j++) {
|
|
kill(pids[j], SIGTERM);
|
|
waitpid(pids[j], NULL, 0);
|
|
}
|
|
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_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));
|
|
}
|
|
|
|
if (pids[i] == 0) {
|
|
/* === child process for stage i === */
|
|
int j;
|
|
|
|
/* 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) {
|
|
dup2(inter_pipes[i - 1][0], STDIN_FILENO);
|
|
}
|
|
|
|
/* set up stdout */
|
|
if (i < nstages - 1) {
|
|
dup2(inter_pipes[i][1], STDOUT_FILENO);
|
|
} 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) {
|
|
for (j = 0; j < nstages - 1; j++) {
|
|
close(inter_pipes[j][0]);
|
|
close(inter_pipes[j][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);
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
/* === parent: close all write ends === */
|
|
if (inter_pipes) {
|
|
for (i = 0; i < nstages - 1; i++) {
|
|
close(inter_pipes[i][0]);
|
|
close(inter_pipes[i][1]);
|
|
}
|
|
free(inter_pipes);
|
|
}
|
|
|
|
/* free parsed args (children have already exec'd) */
|
|
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
|
free(pa);
|
|
|
|
dynbuf_init(&buf_out);
|
|
dynbuf_init(&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++) {
|
|
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;
|
|
}
|
|
}
|
|
free(pids);
|
|
|
|
/* 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);
|
|
dynbuf_free(&buf_out);
|
|
dynbuf_free(&buf_err);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* ===== builtin dispatch ===== */
|
|
|
|
/*
|
|
** Check if argv[0] is a shell builtin. If so, call it and push the
|
|
** result table. Returns 1 if handled, 0 if not a builtin.
|
|
*/
|
|
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);
|
|
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);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* ===== alias expansion ===== */
|
|
|
|
/*
|
|
** Extract the first whitespace-delimited word from cmd, look it up in
|
|
** lush.aliases. If found, replace the command string on the Lua stack
|
|
** (position 1) with alias_value + remaining_text, and update *cmd.
|
|
** Expansion happens on the raw string, before pipeline splitting,
|
|
** so alias values may contain pipes.
|
|
** Returns 1 if expanded, 0 if no alias.
|
|
*/
|
|
static int expand_alias (lua_State *L, const char **cmd) {
|
|
const char *s = *cmd;
|
|
const char *rest;
|
|
size_t wordlen;
|
|
const char *alias;
|
|
size_t alias_len, rest_len, total;
|
|
char *expanded;
|
|
|
|
/* skip leading whitespace */
|
|
while (*s == ' ' || *s == '\t') s++;
|
|
if (*s == '\0') return 0;
|
|
|
|
/* find end of first word */
|
|
rest = s;
|
|
while (*rest && *rest != ' ' && *rest != '\t') rest++;
|
|
wordlen = (size_t)(rest - s);
|
|
|
|
/* look up in lush.aliases */
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH);
|
|
lua_getfield(L, -1, "aliases");
|
|
lua_pushlstring(L, s, wordlen);
|
|
lua_gettable(L, -2);
|
|
|
|
if (!lua_isstring(L, -1)) {
|
|
lua_pop(L, 3);
|
|
return 0;
|
|
}
|
|
|
|
alias = lua_tolstring(L, -1, &alias_len);
|
|
rest_len = strlen(rest); /* includes leading space if any */
|
|
|
|
total = alias_len + rest_len;
|
|
expanded = (char *)malloc(total + 1);
|
|
if (expanded == NULL) {
|
|
lua_pop(L, 3);
|
|
return 0;
|
|
}
|
|
memcpy(expanded, alias, alias_len);
|
|
memcpy(expanded + alias_len, rest, rest_len);
|
|
expanded[total] = '\0';
|
|
lua_pop(L, 3);
|
|
|
|
/* replace command on Lua stack */
|
|
lua_pushstring(L, expanded);
|
|
lua_replace(L, 1);
|
|
free(expanded);
|
|
*cmd = lua_tostring(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* ===== lushCmd_command ===== */
|
|
|
|
int lushCmd_command (lua_State *L) {
|
|
const char *cmd = luaL_checkstring(L, 1);
|
|
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;
|
|
|
|
/* expand alias before pipeline splitting */
|
|
expand_alias(L, &cmd);
|
|
|
|
/* 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 */
|
|
if (nstages > 1) {
|
|
int result = exec_pipeline(L, stages, nstages, 0);
|
|
free(stages[0]); /* free backing buffer */
|
|
return result;
|
|
}
|
|
|
|
/* single stage: free pipeline buffer and use original path */
|
|
free(stages[0]);
|
|
|
|
/* parse command into argv */
|
|
if (parse_argv(cmd, &pa) != 0)
|
|
return luaL_error(L, "unterminated quote in command");
|
|
|
|
/* 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");
|
|
return 1;
|
|
}
|
|
|
|
/* check for shell builtin */
|
|
if (try_builtin(L, &pa)) {
|
|
free_argv(&pa);
|
|
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);
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/* ===== lushCmd_subcmd ===== */
|
|
|
|
/*
|
|
** Subcommand substitution: run a command, return stdout with trailing
|
|
** newlines stripped. Used for $(cmd) inside command strings.
|
|
*/
|
|
int lushCmd_subcmd (lua_State *L) {
|
|
size_t len;
|
|
const char *s;
|
|
/* run the command — pushes {code, stdout, stderr} table */
|
|
lushCmd_command(L);
|
|
/* extract .stdout from result table */
|
|
lua_getfield(L, -1, "stdout");
|
|
lua_remove(L, -2); /* remove the table, keep stdout string */
|
|
/* strip trailing newlines */
|
|
s = lua_tolstring(L, -1, &len);
|
|
while (len > 0 && s[len - 1] == '\n')
|
|
len--;
|
|
lua_pushlstring(L, s, len);
|
|
lua_remove(L, -2); /* remove original stdout string */
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* ===== lushCmd_interactive ===== */
|
|
|
|
int lushCmd_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;
|
|
|
|
/* expand alias before pipeline splitting */
|
|
expand_alias(L, &cmd);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* check for shell builtin */
|
|
if (try_builtin(L, &pa)) {
|
|
free_argv(&pa);
|
|
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 */
|
|
}
|
|
|
|
|
|
/* ===== lushCmd_getenv ===== */
|
|
|
|
int lushCmd_getenv (lua_State *L) {
|
|
const char *name = luaL_checkstring(L, 1);
|
|
const char *val = getenv(name);
|
|
if (val == NULL)
|
|
lua_pushnil(L);
|
|
else
|
|
lua_pushstring(L, val);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* ===== lushCmd_setenv ===== */
|
|
|
|
int lushCmd_setenv (lua_State *L) {
|
|
const char *name = luaL_checkstring(L, 1);
|
|
if (lua_isnoneornil(L, 2))
|
|
unsetenv(name);
|
|
else {
|
|
const char *val = luaL_tolstring(L, 2, NULL);
|
|
setenv(name, val, 1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ===== lush standard library ===== */
|
|
|
|
TString *lushname[LUSH_OP_COUNT];
|
|
|
|
static const luaL_Reg lushlib[] = {
|
|
{"command", lushCmd_command},
|
|
{"interactive", lushCmd_interactive},
|
|
{"getenv", lushCmd_getenv},
|
|
{"setenv", lushCmd_setenv},
|
|
{"subcmd", lushCmd_subcmd},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
LUAMOD_API int luaopen_lush (lua_State *L) {
|
|
luaL_newlib(L, lushlib);
|
|
/* create aliases subtable */
|
|
lua_createtable(L, 0, 4);
|
|
lua_setfield(L, -2, "aliases");
|
|
/* intern function name strings for OP_LUSH VM access */
|
|
lushname[LUSH_OP_COMMAND] = luaS_new(L, "command");
|
|
lushname[LUSH_OP_INTERACTIVE] = luaS_new(L, "interactive");
|
|
lushname[LUSH_OP_GETENV] = luaS_new(L, "getenv");
|
|
lushname[LUSH_OP_SETENV] = luaS_new(L, "setenv");
|
|
lushname[LUSH_OP_SUBCMD] = luaS_new(L, "subcmd");
|
|
/* store in registry for OP_LUSH access */
|
|
lua_pushvalue(L, -1);
|
|
lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH);
|
|
return 1;
|
|
}
|