Goto sanos source index
//
// job.c
//
// Shell jobs
//
// 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 char *aliases[] = {
"[", "test",
"cd", "chdir",
NULL, NULL,
};
void run_atexit_handlers();
struct crtbase *init_crtbase();
void term_crtbase();
static int hash(char *str) {
int h = 0;
while (*str) h = 5 * h + *str++;
return h;
}
static void copy_vars(struct job *job) {
struct var **vptr;
struct var *var;
// Find parent scope for deferred variables
struct job *scope = get_var_scope(job, 1);
// Initialize variables from parent scope
vptr = &job->vars;
var = scope->vars;
while (var) {
struct var *v = (struct var *) malloc(sizeof(struct var));
v->name = strdup(var->name);
v->value = strdup(var->value);
v->hash = var->hash;
v->next = NULL;
*vptr = v;
vptr = &v->next;
var = var->next;
}
job->flags &= ~J_DEFERRED_VARS;
}
static void assign_var(struct var **vars, char *name, char *value) {
int h = hash(name);
struct var **vptr = vars;
struct var *v = *vptr;
while (v) {
if (v->hash == h && strcmp(v->name, name) == 0) {
int len = strlen(value);
v->value = (char *) realloc(v->value, len + 1);
memcpy(v->value, value, len);
v->value[len] = 0;
return;
}
vptr = &v->next;
v = *vptr;
}
v = *vptr = (struct var *) malloc(sizeof(struct var));
v->name = strdup(name);
v->value = strdup(value);
v->hash = h;
v->next = NULL;
}
static void remove_var(struct var **vars, char *name) {
int h = hash(name);
struct var **vptr = vars;
struct var *v = *vptr;
while (v) {
if (v->hash == h && strcmp(v->name, name) == 0) {
*vptr = v->next;
free(v->name);
free(v->value);
free(v);
return;
}
vptr = &v->next;
v = *vptr;
}
}
struct job *get_var_scope(struct job *job, int defer) {
while (job && !(job->flags & J_VAR_SCOPE)) job = job->parent;
if (defer) {
while (job && (job->flags & J_DEFERRED_VARS)) job = job->parent;
}
return job;
}
struct job *get_arg_scope(struct job *job) {
while (job && !(job->flags & J_ARG_SCOPE)) job = job->parent;
return job;
}
void set_var(struct job *job, char *name, char *value) {
// Get variable scope
struct job *scope = get_var_scope(job, 0);
// Performed defered initialization of variables
if (scope->flags & J_DEFERRED_VARS) copy_vars(scope);
if (value) {
// Assign new value to variable.
assign_var(&scope->vars, name, value);
} else {
// Remove variable
remove_var(&scope->vars, name);
}
}
char *get_var(struct job *job, char *name) {
int h;
struct var *var;
// Find scope for variables
struct job *scope = get_var_scope(job, 1);
// Find variable in scope
h = hash(name);
var = scope->vars;
while (var) {
if (var->hash == h && strcmp(var->name, name) == 0) return var->value;
var = var->next;
}
return NULL;
}
static void delete_vars(struct job *job) {
struct var *var = job->vars;
while (var) {
struct var *next = var->next;
free(var->name);
free(var->value);
free(var);
var = next;
}
job->vars = NULL;
}
void init_args(struct args *args) {
args->first = args->last = NULL;
args->num = 0;
}
void delete_args(struct args *args) {
struct arg *arg = args->first;
while (arg) {
struct arg *next = arg->next;
free(arg->value);
free(arg);
arg = next;
}
args->first = args->last = NULL;
args->num = 0;
}
struct arg *add_arg(struct args *args, char *value) {
struct arg *arg = (struct arg *) malloc(sizeof(struct arg));
arg->value = strdup(value ? value : "");
arg->next = NULL;
if (args->last) args->last->next = arg;
if (!args->first) args->first = arg;
args->last = arg;
args->num++;
return arg;
}
char *get_command_line(struct args *args) {
struct arg *arg;
char *cmdline;
char *cmd;
int cmdlen;
// Compute command line size
arg = args->first;
cmdlen = args->num;
while (arg) {
int escape = 0;
char *p = arg->value;
if (!*p) {
escape = 1;
} else {
while (*p) {
cmdlen++;
if (*p == ' ') escape = 1;
if (*p == '"') {
cmdlen++;
escape = 1;
}
p++;
}
}
if (escape) cmdlen += 2;
arg = arg->next;
}
// Build command line
cmdline = malloc(cmdlen);
cmd = cmdline;
arg = args->first;
while (arg) {
int escape = 0;
char *p = arg->value;
if (!*p) {
escape = 1;
} else {
while (*p) {
if (*p == ' ' || *p == '"') escape = 1;
p++;
}
}
if (arg != args->first) *cmd++ = ' ';
if (escape) *cmd++ = '"';
p = arg->value;
while (*p) {
if (*p == '"') *cmd++ = '"';
*cmd++ = *p++;
}
if (escape) *cmd++ = '"';
arg = arg->next;
}
*cmd = 0;
return cmdline;
}
struct job *create_job(struct job *parent, int flags) {
struct job *job;
int i;
// Allocate job
job = (struct job *) malloc(sizeof(struct job));
memset(job, 0, sizeof(struct job));
job->flags = flags | J_DEFERRED_VARS;
job->parent = parent;
job->shell = parent->shell;
job->handle = -1;
// Insert job in job list for shell
if (job->shell->jobs) {
struct job *prev = job->shell->jobs;
while (prev->next != NULL) prev = prev->next;
prev->next = job;
} else {
job->shell->jobs = job;
}
// Defer initialization of I/O handles.
for (i = 0; i < STD_HANDLES; i++) job->fd[i] = -1;
return job;
}
void detach_job(struct job *job) {
int i;
// Check if job has already been detached
if (!job->parent) return;
// Detach variables
if ((job->flags & J_VAR_SCOPE) && (job->flags & J_DEFERRED_VARS)) {
copy_vars(job);
}
job->flags &= ~J_DEFERRED_VARS;
// Detach I/O handles
for (i = 0; i < STD_HANDLES; i++) {
if (job->fd[i] != -1) {
close(job->fd[i]);
job->fd[i] = -1;
}
}
// Detach from parent
job->parent = NULL;
}
void remove_job(struct job *job) {
int i;
// Delete arguments
delete_args(&job->args);
// Delete variables
delete_vars(job);
// Close I/O handles
for (i = 0; i < STD_HANDLES; i++) {
if (job->fd[i] != -1) close(job->fd[i]);
}
// Close process handle
if (job->handle != -1) close(job->handle);
// Remove job from joblist
if (job->shell->jobs == job) {
job->shell->jobs = job->next;
} else {
struct job *j = job->shell->jobs;
struct job *prev = NULL;
while (j != job) {
prev = j;
j = j->next;
}
if (j == job) prev->next = j->next;
}
// Deallocate job
free(job);
}
void init_shell(struct shell *shell, int argc, char *argv[], char *env[]) {
int i;
char *name;
char *value;
struct job *top;
// Create top job
top = (struct job *) malloc(sizeof(struct job));
memset(top, 0, sizeof(struct job));
top->flags = J_VAR_SCOPE | J_ARG_SCOPE;
top->shell = shell;
top->handle = -1;
for (i = 0; i < STD_HANDLES; i++) top->fd[i] = -1;
// Initialize shell
shell->top = shell->jobs = top;
shell->funcs = NULL;
shell->done = 0;
shell->debug = 0;
shell->term = gettib()->proc->term;
// Setup command line parameters
for (i = 0; i < argc; i++) {
add_arg(&top->args, argv[i]);
}
// Copy environment variables
for (i = 0; env[i]; i++) {
name = strdup(env[i]);
value = strchr(name, '=');
if (*value == '=') {
*value++ = 0;
} else {
value = "";
}
assign_var(&top->vars, name, value);
free(name);
}
// Set up shell-level standard I/O handles
shell->fd[0] = fdin;
shell->fd[1] = fdout;
shell->fd[2] = fderr;
}
void clear_shell(struct shell *shell) {
// Remove all jobs
while (shell->jobs) remove_job(shell->jobs);
// Delete function definitions
while (shell->funcs) {
struct function *f = shell->funcs;
shell->funcs = f->next;
popstkmark(&f->mark);
free(f);
}
// Clear job list.
shell->jobs = shell->top = NULL;
}
int get_fd(struct job *job, int h, int own) {
// Try to get I/O handle from job
struct shell *shell = job->shell;
int fd = job->fd[h];
if (fd != -1) {
if (own) job->fd[h] = -1;
return fd;
}
// Try to get I/O handle from ancestors
job = job->parent;
while (job) {
fd = job->fd[h];
if (fd != -1) {
if (own) fd = dup(fd);
return fd;
}
job = job->parent;
}
// Return shell-level I/O handle
fd = shell->fd[h];
if (own) fd = dup(fd);
return fd;
}
void set_fd(struct job *job, int h, int fd) {
if (job->fd[h] != -1) close(job->fd[h]);
job->fd[h] = fd;
}
static char **get_env(struct job *job) {
struct var *v;
char **env;
int n;
// Find scope for variables
struct job *scope = get_var_scope(job, 1);
// Build environment string for scope
if (!scope->vars) return NULL;
for (n = 0, v = scope->vars; v; n++, v = v->next);
env = (char **) malloc((n + 1) * sizeof(char *));
if (!env) return NULL;
env[n] = NULL;
for (n = 0, v = scope->vars; v; n++, v = v->next) {
int nlen = strlen(v->name);
int vlen = strlen(v->value);
int len = nlen + vlen + 1;
char *buf = env[n] = (char *) malloc(len + 1);
if (buf) {
memcpy(buf, v->name, nlen);
buf[nlen] = '=';
memcpy(buf + nlen + 1, v->value, vlen);
buf[len] = 0;
}
}
return env;
}
static void free_env(char **env) {
int n;
if (!env) return;
for (n = 0; env[n]; n++) free(env[n]);
free(env);
}
int wait_for_job(struct job *job) {
if (job->handle != -1) {
while (1) {
job->exitcode = waitone(job->handle, INFINITE);
if (job->exitcode >= 0 || errno != EINTR) break;
}
job->shell->lastrc = job->exitcode;
}
return 0;
}
static int run_external_command(struct job *job) {
char *cmdline;
char **env;
int i;
struct tib *tib;
struct process *proc;
// Build command line
cmdline = get_command_line(&job->args);
// Create environment
env = get_env(job);
// Run command
job->handle = spawn(P_SUSPEND | P_DETACH, NULL, cmdline, env, &tib);
free(cmdline);
free_env(env);
if (job->handle < 0) {
perror("error");
return 1;
}
tib->proc->term = job->shell->term;
job->shell->lastpid = tib->pid;
// Setup standard I/O for program
proc = tib->proc;
for (i = 0; i < 3; i++) proc->iob[i] = get_fd(job, i, 1);
return 0;
}
static main_t lookup_internal(char *name) {
char procname[MAX_COMMAND_LEN + 5];
if (strlen(name) > MAX_COMMAND_LEN) return NULL;
strcpy(procname, "cmd_");
strcat(procname, name);
return (main_t) dlsym(getmodule(NULL), procname);
}
static void exit_internal(int status, int fast) {
struct process *proc = gettib()->proc;
struct crtbase *crtbase = (struct crtbase *) proc->crtbase;
if (!fast) run_atexit_handlers();
term_crtbase(crtbase);
}
static void __stdcall enter_internal(void *args) {
// Run internal command
struct job *job = (struct job *) args;
struct crtbase *crtbase = init_crtbase();
gettib()->proc->atexit = exit_internal;
exit(job->main(crtbase->argc, crtbase->argv));
}
static int run_internal_command(struct job *job) {
int i;
struct tib *tib;
struct process *proc;
// Create process for internal command
char *name = job->args.first->value;
job->handle = beginthread(enter_internal, 0, job, CREATE_SUSPENDED | CREATE_NEW_PROCESS | CREATE_NO_ENV | CREATE_DETACHED, name, &tib);
if (job->handle < 0) return 1;
proc = tib->proc;
proc->cmdline = get_command_line(&job->args);
proc->env = get_env(job);
proc->ident = strdup(name);
proc->term = job->shell->term;
for (i = 0; i < 3; i++) proc->iob[i] = get_fd(job, i, 1);
job->shell->lastpid = tib->pid;
return 0;
}
static builtin_t lookup_builtin(char *name) {
char procname[MAX_COMMAND_LEN + 9];
if (strlen(name) > MAX_COMMAND_LEN) return NULL;
strcpy(procname, "builtin_");
strcat(procname, name);
return (builtin_t) dlsym(getmodule(NULL), procname);
}
static char *lookup_alias(char *name) {
char **a = aliases;
while (*a) {
char *cmd = *a++;
char *alias = *a++;
if (strcmp(cmd, name) == 0) return alias;
}
return name;
}
struct function *lookup_func(struct shell *shell, char *name) {
struct function *func = shell->funcs;
while (func) {
if (strcmp(func->def->nfunc.name, name) == 0) return func;
func = func->next;
}
return NULL;
}
int execute_job(struct job *job) {
char *argv0;
builtin_t cmd;
struct function *func;
int rc;
// Ignore empty commands
if (job->args.num == 0) return 0;
// Get command name
argv0 = lookup_alias(job->args.first->value);
// Run builtin command
cmd = lookup_builtin(argv0);
if (cmd != NULL) {
rc = cmd(job);
job->exitcode = rc;
job->shell->lastrc = rc;
return 0;
}
// Run script function
func = lookup_func(job->shell, argv0);
if (func != NULL) {
job->flags |= J_FUNCTION | J_ARG_SCOPE;
rc = interp(job, func->def->nfunc.cmds);
job->exitcode = rc;
job->shell->lastrc = rc;
return 0;
}
// Launch program
job->main = lookup_internal(argv0);
if (job->main != NULL) {
rc = run_internal_command(job);
} else {
rc = run_external_command(job);
}
return rc;
}