Goto sanos source index
//
// interp.c
//
// Shell command interpreter
//
// Copyright (C) 2012 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include "sh.h"
static int expand_args(struct stkmark *mark, struct job *job, struct args *args, union node *node);
static int expand_glob(struct stkmark *mark, struct args *args, char *pattern) {
glob_t globbuf;
unsigned i;
if (glob(pattern, GLOB_NOCHECK | GLOB_NOESCAPE, NULL, &globbuf) < 0) {
fprintf(stderr, "errror expanding glob pattern %s\n", pattern);
return 1;
}
if (args) {
for (i = 0; i < globbuf.gl_pathc; ++i) {
stputstr(mark, globbuf.gl_pathv[i]);
add_arg(args, ststr(mark));
}
} else {
for (i = 0; i < globbuf.gl_pathc; ++i) {
if (i > 0) stputc(mark, ' ');
stputstr(mark, globbuf.gl_pathv[i]);
}
}
globfree(&globbuf);
return 0;
}
static void expand_field(struct stkmark *mark, char *str, int quoted, struct args *args) {
if (!str) return;
if (quoted || !args) {
stputstr(mark, str);
} else {
while (*str) {
char *start = str;
while (*str && is_space(*str)) str++;
if (!*str) break;
if (str != start || ststrlen(mark) > 0) add_arg(args, ststr(mark));
start = str;
while (*str && !is_space(*str)) str++;
stputbuf(mark, start, str - start);
}
}
}
static char *expand_word(struct job *job, union node *node) {
struct stkmark mark;
int rc;
char *word = NULL;
pushstkmark(NULL, &mark);
rc = expand_args(&mark, job, NULL, node);
if (rc == 0) word = strdup(ststr(&mark));
popstkmark(&mark);
return word;
}
static char *remove_shortest_suffix(char *str, char *pattern) {
char *s;
if (!str || !pattern) return NULL;
s = str + strlen(str);
while (s >= str) {
if (fnmatch(pattern, s, 0) == 0) {
int len = s - str;
char *buffer = malloc(len + 1);
memcpy(buffer, str, len);
buffer[len] = 0;
return buffer;
}
s--;
}
return NULL;
}
static char *remove_longest_suffix(char *str, char *pattern) {
char *s;
if (!str || !pattern) return NULL;
s = str;
while (*s) {
if (fnmatch(pattern, s, 0) == 0) {
int len = s - str;
char *buffer = malloc(len + 1);
memcpy(buffer, str, len);
buffer[len] = 0;
return buffer;
}
s++;
}
return NULL;
}
static char *remove_shortest_prefix(char *str, char *pattern) {
char *s;
if (!str || !pattern) return str;
s = str;
while (*s) {
char c = *s;
*s = 0;
if (fnmatch(pattern, str, 0) == 0) {
*s = c;
return s;
}
*s++ = c;
}
if (fnmatch(pattern, str, 0) == 0) return s;
return str;
}
static char *remove_longest_prefix(char *str, char *pattern) {
char *s;
if (!str || !pattern) return str;
s = str + strlen(str);
while (s >= str) {
char c = *s;
*s = 0;
if (fnmatch(pattern, str, 0) == 0) {
*s = c;
return s;
}
*s-- = c;
}
return str;
}
static int expand_param(struct stkmark *mark, struct job *job, struct args *args, struct nargparam *param) {
char buffer[MAX_INTSTR];
struct job *scope;
char *value = NULL;
int n;
int special;
int quoted;
int varmod;
struct arg *arg;
// Expand special parameters
quoted = (param->flags & S_TABLE) == S_DQUOTED;
special = param->flags & S_SPECIAL;
if (special) {
switch (special) {
case S_ARGC: // $#
scope = get_arg_scope(job);
value = itoa(scope->args.num - 1, buffer, 10);
expand_field(mark, value, quoted, args);
return 0;
case S_ARGV: // $*
case S_ARGVS: // $@
scope = get_arg_scope(job);
arg = scope->args.first;
if (arg) arg = arg->next;
while (arg) {
if (args) {
stputstr(mark, arg->value);
add_arg(args, ststr(mark));
} else {
expand_field(mark, arg->value, quoted, args);
}
arg = arg->next;
}
return 0;
case S_EXITCODE: // $?
value = itoa(job->shell->lastrc, buffer, 10);
expand_field(mark, value, quoted, args);
return 0;
case S_ARG: // $[0-9]
scope = get_arg_scope(job);
arg = scope->args.first;
n = param->num;
while (arg && n > 0) {
n--;
arg = arg->next;
}
if (param->flags & S_STRLEN) {
value = itoa(arg ? strlen(arg->value) : 0, buffer, 10);
expand_field(mark, value, quoted, args);
} else {
if (arg) expand_field(mark, arg->value, quoted, args);
}
return 0;
case S_PID: // $$
value = itoa(job->shell->lastpid, buffer, 10);
expand_field(mark, value, quoted, args);
return 0;
case S_FLAGS: // $-
case S_BGEXCODE: // $!
// Not implemented
return 0;
}
}
// Expand variable
value = get_var(job, param->name);
varmod = param->flags & S_VAR;
if (!varmod) {
if (value) expand_field(mark, value, quoted, args);
} else {
// Expand word
char *word = expand_word(job, param->word);
char *buf = NULL;
int rc = 0;
switch (varmod) {
case S_DEFAULT: // ${parameter:-word}
if (!value) value = word;
break;
case S_ASGNDEF: // ${parameter:=word}
if (!value) {
value = word;
set_var(job, param->name, value);
if (job->parent) set_var(job->parent, param->name, value);
}
break;
case S_ERRNULL: // ${parameter:?[word]}
if (!value) {
if (word && *word) {
fprintf(stderr, "%s: %s\n", param->name, word);
} else {
fprintf(stderr, "%s: variable not set\n", param->name);
}
rc = 1;
}
break;
case S_ALTERNAT: // ${parameter:+word}
if (value || *value) value = word;
break;
case S_RSSFX: // ${parameter%word}
buf = remove_shortest_suffix(value, word);
if (buf) value = buf;
break;
case S_RLSFX: // ${parameter%%word}
buf = remove_longest_suffix(value, word);
if (buf) value = buf;
break;
case S_RSPFX: // ${parameter#word}
value = remove_shortest_prefix(value, word);
break;
case S_RLPFX: // ${parameter##word}
value = remove_longest_prefix(value, word);
break;
}
if (rc == 0 && value) expand_field(mark, value, quoted, args);
free(word);
free(buf);
return rc;
}
return 0;
}
static int expand_command(struct stkmark *mark, struct job *parent, struct args *args, union node *node) {
struct job *job;
FILE *f;
union node *n;
char buf[512];
// Create job for command expansion
job = create_job(parent, 0);
// Redirect output to temporary file
f = tmpfile();
if (!f) {
remove_job(job);
return 1;
}
set_fd(job, 1, fileno(f));
// Interpret command
for (n = node->nargcmd.list; n; n = n->list.next) {
if (interp(job, n) != 0) {
fclose(f);
remove_job(job);
return 1;
}
}
// Add output from command to expansion
fseek(f, 0, SEEK_SET);
while (fgets(buf, sizeof(buf), f)) {
expand_field(mark, buf, (node->nargcmd.flags & S_TABLE) == S_DQUOTED, args);
}
fclose(f);
remove_job(job);
return 0;
}
static int eval_expr(struct job *job, struct expr *expr) {
int l, r, n;
char buffer[MAX_INTSTR];
if (!expr) return 0;
l = (expr->op == '=') ? 0 : eval_expr(job, expr->left);
r = eval_expr(job, expr->right);
switch (expr->op & ~A_ASSIGN) {
case '=':
set_var(job, expr->left->var, itoa(r, buffer, 10));
n = r;
break;
case A_NUM:
n = expr->num;
break;
case A_VAR:
n = atoi(get_var(job, expr->var));
break;
case '+': n = l + r; break;
case '-': n = l - r; break;
case '*': n = l * r; break;
case '/': n = (r == 0) ? 0 : l / r; break;
case '%': n = (r == 0) ? 0 : l % r; break;
case '&': n = l & r; break;
case '|': n = l | r; break;
case '^': n = l ^ r; break;
case A_SHL: n = l << r; break;
case A_SHR: n = l >> r; break;
case '~': n = ~r; break;
case '!': n = !r; break;
case A_AND: n = l && r; break;
case A_OR: n = l || r; break;
case A_EQ: n = (l == r); break;
case '>': n = (l > r); break;
case '<': n = (l > r); break;
case A_NE: n = (l != r); break;
case A_GE: n = (l >= r); break;
case A_LE: n = (l <= r); break;
case ',': n = r; break;
case ':': n = 0; break;
case '?':
n = l ? eval_expr(job, expr->right->left) : eval_expr(job, expr->right->right);
break;
default:
fprintf(stderr, "unknown arithmetic operator (%x)\n", expr->op & ~A_ASSIGN);
return 0;
}
if (expr->op & A_ASSIGN) {
set_var(job, expr->left->var, itoa(n, buffer, 10));
}
return n;
}
static int expand_expr(struct stkmark *mark, struct job *parent, struct args *args, union node *node) {
char buffer[MAX_INTSTR];
int n;
n = eval_expr(parent, node->nargarith.expr);
expand_field(mark, itoa(n, buffer, 10), 0, args);
return 0;
}
static int expand_args(struct stkmark *mark, struct job *job, struct args *args, union node *node) {
union node *n;
int before;
int rc;
switch (node->type) {
case N_SIMPLECMD:
for (n = node->ncmd.args; n; n = n->list.next) {
rc = expand_args(mark, job, args, n);
if (rc != 0) return rc;
}
break;
case N_FOR:
for (n = node->nfor.args; n; n = n->list.next) {
rc = expand_args(mark, job, args, n);
if (rc != 0) return rc;
}
break;
case N_ARG:
if (args) {
before = args->num;
for (n = node->narg.list; n; n = n->list.next) {
rc = expand_args(mark, job, args, n);
if (rc != 0) return rc;
}
if (args->num == before || ststrlen(mark) > 0) add_arg(args, ststr(mark));
} else {
for (n = node->narg.list; n; n = n->list.next) {
rc = expand_args(mark, job, args, n);
if (rc != 0) return rc;
}
}
break;
case N_ARGSTR:
if (node->nargstr.flags & S_GLOB) {
if (expand_glob(mark, args, node->nargstr.text) < 0) return 1;
} else {
stputstr(mark, node->nargstr.text);
}
break;
case N_ARGPARAM:
rc = expand_param(mark, job, args, &node->nargparam);
if (rc != 0) return rc;
break;
case N_ARGCMD:
rc = expand_command(mark, job, args, node);
if (rc != 0) return rc;
break;
case N_ARGARITH:
rc = expand_expr(mark, job, args, node);
if (rc != 0) return rc;
break;
default:
fprintf(stderr, "Unsupported argument type: %d\n", node->type);
return 1;
}
return 0;
}
static int interp_vars(struct stkmark *mark, struct job *job, union node *node) {
struct arg *arg;
union node *n;
int rc;
// Expand variable assignments
struct args args;
init_args(&args);
for (n = node->ncmd.vars; n; n = n->list.next) {
rc = expand_args(mark, job, &args, n);
if (rc != 0) {
delete_args(&args);
return rc;
}
}
// Assign variables to job
arg = args.first;
while (arg) {
char *name = arg->value;
char *value = strchr(arg->value, '=');
if (value) *value++ = 0;
set_var(job, name, value);
arg = arg->next;
}
delete_args(&args);
return 0;
}
static int interp_redir(struct stkmark *mark, struct job *job, union node *node) {
union node *n;
int rc, dir, act, fd, f, flags, oflags;
char *arg;
// Open files for redirection
for (n = node; n; n = n->nredir.next) {
fd = n->nredir.fd;
flags = n->nredir.flags;
dir = flags & R_DIR;
act = flags & R_ACT;
rc = expand_args(mark, job, NULL, n->nredir.list);
if (rc != 0) return rc;
arg = ststr(mark);
if (!arg || fd < 0 || fd >= STD_HANDLES) return 1;
switch (act) {
case R_OPEN:
oflags = 0;
if (dir == R_IN) {
oflags |= O_RDONLY;
} else if (dir == R_OUT) {
oflags |= O_WRONLY;
} else if (dir == (R_IN | R_OUT)) {
oflags |= O_RDWR;
}
if (dir & R_OUT) {
oflags |= (flags & R_APPEND) ? O_APPEND : O_TRUNC;
oflags |= (flags & R_CLOBBER) ? (O_CREAT | O_EXCL) : O_CREAT;
}
f = open(arg, oflags, 0666);
if (f < 0) {
perror(arg);
return 1;
}
set_fd(job, fd, f);
break;
case R_DUP:
f = atoi(arg);
if (f < 0 || f >= STD_HANDLES) return 1;
set_fd(job, fd, dup(get_fd(job, f, 0)));
break;
case R_HERE:
// Not yet implemented
break;
}
}
return 0;
}
static struct job *setup_command(struct job *parent, union node *node) {
struct stkmark mark;
struct job *job;
// Create new job
job = create_job(parent, J_VAR_SCOPE);
// Assign variables
pushstkmark(NULL, &mark);
if (node->ncmd.vars) {
if (interp_vars(&mark, job, node) != 0) {
popstkmark(&mark);
remove_job(job);
return NULL;
}
}
// Perform I/O redirection
if (node->ncmd.rdir) {
if (interp_redir(&mark, job, node->ncmd.rdir) != 0) {
popstkmark(&mark);
remove_job(job);
return NULL;
}
}
// Expand command arguments
if (expand_args(&mark, job, &job->args, node) != 0) {
popstkmark(&mark);
remove_job(job);
return NULL;
}
popstkmark(&mark);
return job;
}
static int interp_simple_command(struct job *parent, union node *node) {
int rc;
struct job *job;
int i;
// If there are no arguments just assign variables to parent job
if (!node->ncmd.args) {
struct stkmark mark;
pushstkmark(NULL, &mark);
rc = interp_vars(&mark, parent, node);
popstkmark(&mark);
return rc;
}
// Setup job for command
job = setup_command(parent, node);
if (job == NULL) return 1;
// Execute job
rc = execute_job(job);
if (rc != 0) {
remove_job(job);
return rc;
}
if (job->handle != -1) resume(job->handle);
for (i = 0; i < STD_HANDLES; i++) {
if (job->fd[i] != -1) {
close(job->fd[i]);
job->fd[i] = -1;
}
}
if (node->ncmd.flags & S_BGND) {
// Detach background job
detach_job(job);
return 0;
} else {
// Wait for command to complete
rc = wait_for_job(job);
remove_job(job);
return rc;
}
}
static int interp_pipeline(struct job *parent, union node *node) {
union node *n;
struct job *job;
int pipefd[2];
int out;
// Create job for pipeline and get stdin and stdout for whole pipeline
job = create_job(parent, 0);
out = get_fd(job, 1, 1);
pipefd[0] = pipefd[1] = -1;
for (n = node->npipe.cmds; n; n = n->list.next) {
int first = (n == node->npipe.cmds);
int last = (n->list.next == NULL);
// Set input to be the output of the previous pipe
if (!first) {
set_fd(job, 0, pipefd[0]);
pipefd[0] = -1;
}
// Create pipe for output
if (last) {
set_fd(job, 1, out);
out = -1;
} else {
pipe(pipefd);
set_fd(job, 1, pipefd[1]);
pipefd[1] = -1;
n->list.flags |= S_BGND;
}
// Interpret pipeline element
if (interp(job, n) != 0) {
if (out != -1) close(out);
if (pipefd[0] != -1) close(pipefd[0]);
if (pipefd[1] != -1) close(pipefd[1]);
remove_job(job);
return 1;
}
}
remove_job(job);
return 0;
}
static int interp_cmdlist(struct job *parent, union node *node, int subshell) {
struct job *job;
union node *n;
// Create new job
job = create_job(parent, subshell ? J_VAR_SCOPE : 0);
// Perform I/O redirection
if (node->ncmd.rdir) {
int rc;
struct stkmark mark;
pushstkmark(NULL, &mark);
rc = interp_redir(&mark, job, node->ngrp.rdir);
popstkmark(&mark);
if (rc != 0) {
remove_job(job);
return rc;
}
}
// Interpret each command in the list
for (n = node->ngrp.cmds; n; n = n->list.next) {
if (interp(job, n) != 0) {
remove_job(job);
return 1;
}
if (job->flags & J_BREAK) break;
}
remove_job(job);
return 0;
}
static int interp_and(struct job *parent, union node *node) {
// Execute first command
if (interp(parent, node->nandor.cmd0) != 0) return 1;
// Execute second command if the exit code if the first command was zero
if (parent->shell->lastrc == 0) {
if (interp(parent, node->nandor.cmd1) != 0) return 1;
}
return 0;
}
static int interp_or(struct job *parent, union node *node) {
// Execute first command
if (interp(parent, node->nandor.cmd0) != 0) return 1;
// Execute second command if the exit code if the first command was non-zero
if (parent->shell->lastrc != 0) {
if (interp(parent, node->nandor.cmd1) != 0) return 1;
}
return 0;
}
static int interp_not(struct job *parent, union node *node) {
// Execute command
if (interp(parent, node->nandor.cmd0) != 0) return 1;
// Negate exit code
parent->shell->lastrc = !parent->shell->lastrc;
return 0;
}
static int interp_if(struct job *parent, union node *node) {
struct job *job;
union node *body;
union node *n;
int rc;
// Create new job
job = create_job(parent, 0);
// Perform I/O redirection
if (node->ncmd.rdir) {
struct stkmark mark;
pushstkmark(NULL, &mark);
rc = interp_redir(&mark, job, node->nif.rdir);
popstkmark(&mark);
if (rc != 0) {
remove_job(job);
return rc;
}
}
// Interpret test condition
if (interp(job, node->nif.test) != 0) {
remove_job(job);
return 1;
}
// Interpret if or else command
body = job->shell->lastrc == 0 ? node->nif.cmd0 : node->nif.cmd1;
for (n = body; n; n = n->list.next) {
rc = interp(job, n);
if (rc != 0) break;
}
remove_job(job);
return rc;
}
static int interp_for(struct job *parent, union node *node) {
struct stkmark mark;
struct job *job;
struct arg *arg;
union node *n;
int rc;
// Create new job
job = create_job(parent, J_LOOP);
// Perform I/O redirection
pushstkmark(NULL, &mark);
if (node->ncmd.rdir) {
rc = interp_redir(&mark, job, node->nfor.rdir);
if (rc != 0) {
popstkmark(&mark);
remove_job(job);
return rc;
}
}
// Expand argument list
if (expand_args(&mark, job, &job->args, node) != 0) {
popstkmark(&mark);
remove_job(job);
return 1;
}
popstkmark(&mark);
// Iterate over the arguments
rc = 0;
for (arg = job->args.first; arg; arg = arg->next) {
// Set iterator variable
set_var(job, node->nfor.varn, arg->value);
// Interpret body
for (n = node->nfor.cmds; n; n = n->list.next) {
rc = interp(job, n);
if (rc != 0) break;
if (job->flags & (J_BREAK | J_CONTINUE)) break;
}
if (job->flags & J_BREAK) break;
job->flags &= ~J_CONTINUE;
}
remove_job(job);
return rc;
}
static int interp_loop(struct job *parent, union node *node, int until) {
struct job *job;
union node *n;
int rc;
// Create new job
job = create_job(parent, J_LOOP);
// Perform I/O redirection
if (node->ncmd.rdir) {
struct stkmark mark;
pushstkmark(NULL, &mark);
rc = interp_redir(&mark, job, node->nloop.rdir);
popstkmark(&mark);
if (rc != 0) {
remove_job(job);
return rc;
}
}
rc = 0;
for (;;) {
// Interpret test condition
rc = interp(job, node->nloop.test);
if (rc != 0) break;
if ((job->shell->lastrc == 0) == until) break;
// Interpret body
for (n = node->nloop.cmds; n; n = n->list.next) {
rc = interp(job, n);
if (rc != 0) break;
if (job->flags & (J_BREAK | J_CONTINUE)) break;
}
if (job->flags & J_BREAK) break;
job->flags &= ~J_CONTINUE;
}
remove_job(job);
return rc;
}
static int interp_case(struct job *parent, union node *node) {
struct stkmark mark;
struct job *job;
union node *n;
char *word;
int rc;
// Create new job
job = create_job(parent, 0);
pushstkmark(NULL, &mark);
// Perform I/O redirection
if (node->ncmd.rdir) {
rc = interp_redir(&mark, job, node->ncase.rdir);
if (rc != 0) {
popstkmark(&mark);
remove_job(job);
return rc;
}
}
// Interpret case word
rc = expand_args(&mark, job, NULL, node->ncase.word);
if (rc != 0) {
popstkmark(&mark);
remove_job(job);
return rc;
}
word = ststr(&mark);
// Match case patterns
for (n = node->ncase.list; n; n = n->list.next) {
// Expand case node patterns
struct args args;
struct arg *arg;
int match;
init_args(&args);
rc = expand_args(&mark, job, &args, n->ncasenode.pats);
if (rc != 0) {
delete_args(&args);
popstkmark(&mark);
remove_job(job);
return rc;
}
// Check for matching pattern
match = 0;
arg = args.first;
while (arg) {
if (fnmatch(arg->value, word, 0) == 0) {
match = 1;
break;
}
arg = arg->next;
}
delete_args(&args);
if (match) {
// Interpret case node
if (interp(job, n->ncasenode.cmds) != 0) {
popstkmark(&mark);
remove_job(job);
return 1;
}
break;
}
}
popstkmark(&mark);
remove_job(job);
return 0;
}
static int interp_function(struct job *parent, union node *node) {
// Add function definition to shell
struct function *func = (struct function *) malloc(sizeof(struct function));
pushstkmark(NULL, &func->mark);
func->def = copy_node(&func->mark, node);
func->next = parent->shell->funcs;
parent->shell->funcs = func;
return 0;
}
int interp(struct job *parent, union node *node) {
switch (node->type) {
case N_SIMPLECMD: return interp_simple_command(parent, node);
case N_PIPELINE: return interp_pipeline(parent, node);
case N_CMDLIST: return interp_cmdlist(parent, node, 0);
case N_SUBSHELL: return interp_cmdlist(parent, node, 1);
case N_AND: return interp_and(parent, node);
case N_OR: return interp_or(parent, node);
case N_NOT: return interp_not(parent, node);
case N_IF: return interp_if(parent, node);
case N_FOR: return interp_for(parent, node);
case N_WHILE: return interp_loop(parent, node, 0);
case N_UNTIL: return interp_loop(parent, node, 1);
case N_CASE: return interp_case(parent, node);
case N_FUNCTION: return interp_function(parent, node);
default:
fprintf(stderr, "Error: not supported (node type %d)\n", node->type);
return 1;
}
}