mksh/c_sh.c
tg f6e24ec417 Our times(3) just calls getrusage(2) and gettimeofday(2), converting seconds
to ticks.  Since ksh needs things in seconds it then converted them back.
Avoid the silliness and use the getrusage(2) and gettimeofday(2) directly.
With man page help from jmc@
From: Todd C. Miller <millert@cvs.openbsd.org>
2004-12-31 18:41:47 +00:00

862 lines
19 KiB
C

/** $MirBSD: src/bin/ksh/c_sh.c,v 2.11 2004/12/31 18:41:46 tg Exp $ */
/* $OpenBSD: c_sh.c,v 1.25 2004/12/22 18:48:56 millert Exp $ */
/*
* built-in Bourne commands
*/
#include "sh.h"
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
__RCSID("$MirBSD: src/bin/ksh/c_sh.c,v 2.11 2004/12/31 18:41:46 tg Exp $");
static void p_time(struct shf *, int, struct timeval *, int, char *, char *);
/* :, false and true */
int
c_label(char **wp)
{
return wp[0][0] == 'f' ? 1 : 0;
}
int
c_shift(char **wp)
{
struct block *l = e->loc;
int n;
long val;
char *arg;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
arg = wp[builtin_opt.optind];
if (arg) {
evaluate(arg, &val, KSH_UNWIND_ERROR);
n = val;
} else
n = 1;
if (n < 0) {
bi_errorf("%s: bad number", arg);
return (1);
}
if (l->argc < n) {
bi_errorf("nothing to shift");
return (1);
}
l->argv[n] = l->argv[0];
l->argv += n;
l->argc -= n;
return 0;
}
int
c_umask(char **wp)
{
int i;
char *cp;
int symbolic = 0;
mode_t old_umask;
int optc;
while ((optc = ksh_getopt(wp, &builtin_opt, "S")) != EOF)
switch (optc) {
case 'S':
symbolic = 1;
break;
case '?':
return 1;
}
cp = wp[builtin_opt.optind];
if (cp == NULL) {
old_umask = umask(0);
umask(old_umask);
if (symbolic) {
char buf[18];
int j;
old_umask = ~old_umask;
cp = buf;
for (i = 0; i < 3; i++) {
*cp++ = "ugo"[i];
*cp++ = '=';
for (j = 0; j < 3; j++)
if (old_umask & (1 << (8 - (3*i + j))))
*cp++ = "rwx"[j];
*cp++ = ',';
}
cp[-1] = '\0';
shprintf("%s\n", buf);
} else
shprintf("%#3.3o\n", old_umask);
} else {
mode_t new_umask;
if (digit(*cp)) {
for (new_umask = 0; *cp >= '0' && *cp <= '7'; cp++)
new_umask = new_umask * 8 + (*cp - '0');
if (*cp) {
bi_errorf("bad number");
return 1;
}
} else {
/* symbolic format */
int positions, new_val;
char op;
old_umask = umask(0);
umask(old_umask); /* in case of error */
old_umask = ~old_umask;
new_umask = old_umask;
positions = 0;
while (*cp) {
while (*cp && strchr("augo", *cp))
switch (*cp++) {
case 'a': positions |= 0111; break;
case 'u': positions |= 0100; break;
case 'g': positions |= 0010; break;
case 'o': positions |= 0001; break;
}
if (!positions)
positions = 0111; /* default is a */
if (!strchr("=+-", op = *cp))
break;
cp++;
new_val = 0;
while (*cp && strchr("rwxugoXs", *cp))
switch (*cp++) {
case 'r': new_val |= 04; break;
case 'w': new_val |= 02; break;
case 'x': new_val |= 01; break;
case 'u': new_val |= old_umask >> 6;
break;
case 'g': new_val |= old_umask >> 3;
break;
case 'o': new_val |= old_umask >> 0;
break;
case 'X': if (old_umask & 0111)
new_val |= 01;
break;
case 's': /* ignored */
break;
}
new_val = (new_val & 07) * positions;
switch (op) {
case '-':
new_umask &= ~new_val;
break;
case '=':
new_umask = new_val
| (new_umask & ~(positions * 07));
break;
case '+':
new_umask |= new_val;
}
if (*cp == ',') {
positions = 0;
cp++;
} else if (!strchr("=+-", *cp))
break;
}
if (*cp) {
bi_errorf("bad mask");
return 1;
}
new_umask = ~new_umask;
}
umask(new_umask);
}
return 0;
}
int
c_dot(char **wp)
{
char *file, *cp;
char **argv;
int argc;
int i;
int err;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
if ((cp = wp[builtin_opt.optind]) == NULL)
return 0;
file = search(cp, path, R_OK, &err);
if (file == NULL) {
bi_errorf("%s: %s", cp, err ? strerror(err) : "not found");
return 1;
}
/* Set positional parameters? */
if (wp[builtin_opt.optind + 1]) {
argv = wp + builtin_opt.optind;
argv[0] = e->loc->argv[0]; /* preserve $0 */
for (argc = 0; argv[argc + 1]; argc++)
;
} else {
argc = 0;
argv = NULL;
}
i = include(file, argc, argv, 0);
if (i < 0) { /* should not happen */
bi_errorf("%s: %s", cp, strerror(errno));
return 1;
}
return i;
}
int
c_wait(char **wp)
{
int UNINITIALIZED(rv);
int sig;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
wp += builtin_opt.optind;
if (*wp == NULL) {
while (waitfor(NULL, &sig) >= 0)
;
rv = sig;
} else {
for (; *wp; wp++)
rv = waitfor(*wp, &sig);
if (rv < 0)
rv = sig ? sig : 127; /* magic exit code: bad job-id */
}
return rv;
}
int
c_read(char **wp)
{
int c = 0;
int expand = 1, history = 0;
int expanding;
int ecode = 0;
char *cp;
int fd = 0;
struct shf *shf;
int optc;
const char *emsg;
XString cs, xs;
struct tbl *vp;
char UNINITIALIZED(*xp);
while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != EOF)
switch (optc) {
case 'p':
if ((fd = coproc_getfd(R_OK, &emsg)) < 0) {
bi_errorf("-p: %s", emsg);
return 1;
}
break;
case 'r':
expand = 0;
break;
case 's':
history = 1;
break;
case 'u':
if (!*(cp = builtin_opt.optarg))
fd = 0;
else if ((fd = check_fd(cp, R_OK, &emsg)) < 0) {
bi_errorf("-u: %s: %s", cp, emsg);
return 1;
}
break;
case '?':
return 1;
}
wp += builtin_opt.optind;
if (*wp == NULL)
*--wp = "REPLY";
/* Since we can't necessarily seek backwards on non-regular files,
* don't buffer them so we can't read too much.
*/
shf = shf_reopen(fd, SHF_RD | SHF_INTERRUPT | can_seek(fd), shl_spare);
if ((cp = strchr(*wp, '?')) != NULL) {
*cp = 0;
if (isatty(fd)) {
/* at&t ksh says it prints prompt on fd if it's open
* for writing and is a tty, but it doesn't do it
* (it also doesn't check the interactive flag,
* as is indicated in the Kornshell book).
*/
shellf("%s", cp+1);
}
}
/* If we are reading from the co-process for the first time,
* make sure the other side of the pipe is closed first. This allows
* the detection of eof.
*
* This is not compatible with at&t ksh... the fd is kept so another
* coproc can be started with same output, however, this means eof
* can't be detected... This is why it is closed here.
* If this call is removed, remove the eof check below, too.
* coproc_readw_close(fd);
*/
if (history)
Xinit(xs, xp, 128, ATEMP);
expanding = 0;
Xinit(cs, cp, 128, ATEMP);
for (; *wp != NULL; wp++) {
for (cp = Xstring(cs, cp); ; ) {
if (c == '\n' || c == EOF)
break;
while (1) {
c = shf_getc(shf);
if (c == '\0')
continue;
if (c == EOF && shf_error(shf)
&& shf_errno(shf) == EINTR)
{
/* Was the offending signal one that
* would normally kill a process?
* If so, pretend the read was killed.
*/
ecode = fatal_trap_check();
/* non fatal (eg, CHLD), carry on */
if (!ecode) {
shf_clearerr(shf);
continue;
}
}
break;
}
if (history) {
Xcheck(xs, xp);
Xput(xs, xp, c);
}
Xcheck(cs, cp);
if (expanding) {
expanding = 0;
if (c == '\n') {
c = 0;
if (Flag(FTALKING_I) && isatty(fd)) {
/* set prompt in case this is
* called from .profile or $ENV
*/
set_prompt(PS2, NULL);
pprompt(prompt, 0);
}
} else if (c != EOF)
Xput(cs, cp, c);
continue;
}
if (expand && c == '\\') {
expanding = 1;
continue;
}
if (c == '\n' || c == EOF)
break;
if (ctype(c, C_IFS)) {
if (Xlength(cs, cp) == 0 && ctype(c, C_IFSWS))
continue;
if (wp[1])
break;
}
Xput(cs, cp, c);
}
/* strip trailing IFS white space from last variable */
if (!wp[1])
while (Xlength(cs, cp) && ctype(cp[-1], C_IFS)
&& ctype(cp[-1], C_IFSWS))
cp--;
Xput(cs, cp, '\0');
vp = global(*wp);
/* Must be done before setting export. */
if (vp->flag & RDONLY) {
shf_flush(shf);
bi_errorf("%s is read only", *wp);
return 1;
}
if (Flag(FEXPORT))
typeset(*wp, EXPORT, 0, 0, 0);
if (!setstr(vp, Xstring(cs, cp), KSH_RETURN_ERROR)) {
shf_flush(shf);
return 1;
}
}
shf_flush(shf);
if (history) {
Xput(xs, xp, '\0');
source->line++;
histsave(source->line, Xstring(xs, xp), 1);
Xfree(xs, xp);
}
/* if this is the co-process fd, close the file descriptor
* (can get eof if and only if all processes are have died, ie,
* coproc.njobs is 0 and the pipe is closed).
*/
if (c == EOF && !ecode)
coproc_read_close(fd);
return ecode ? ecode : c == EOF;
}
int
c_eval(char **wp)
{
struct source *s;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
s = pushs(SWORDS, ATEMP);
s->u.strv = wp + builtin_opt.optind;
if (!Flag(FPOSIX)) {
/*
* Handle case where the command is empty due to failed
* command substitution, eg, eval "$(false)".
* In this case, shell() will not set/change exstat (because
* compiled tree is empty), so will use this value.
* subst_exstat is cleared in execute(), so should be 0 if
* there were no substitutions.
*
* A strict reading of POSIX says we don't do this (though
* it is traditionally done). [from 1003.2-1992]
* 3.9.1: Simple Commands
* ... If there is a command name, execution shall
* continue as described in 3.9.1.1. If there
* is no command name, but the command contained a command
* substitution, the command shall complete with the exit
* status of the last command substitution
* 3.9.1.1: Command Search and Execution
* ...(1)...(a) If the command name matches the name of
* a special built-in utility, that special built-in
* utility shall be invoked.
* 3.14.5: Eval
* ... If there are no arguments, or only null arguments,
* eval shall return an exit status of zero.
*/
exstat = subst_exstat;
}
return shell(s, false);
}
int
c_trap(char **wp)
{
int i;
char *s;
Trap *p;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
wp += builtin_opt.optind;
if (*wp == NULL) {
int anydfl = 0;
for (p = sigtraps, i = NSIG + 1; --i >= 0; p++) {
if (p->trap == NULL)
anydfl = 1;
else {
shprintf("trap -- ");
print_value_quoted(p->trap);
shprintf(" %s\n", p->name);
}
}
return 0;
}
/*
* Use case sensitive lookup for first arg so the
* command 'exit' isn't confused with the pseudo-signal
* 'EXIT'.
*/
s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL; /* get command */
if (s != NULL && s[0] == '-' && s[1] == '\0')
s = NULL;
/* set/clear traps */
while (*wp != NULL) {
p = gettrap(*wp++, true);
if (p == NULL) {
bi_errorf("bad signal %s", wp[-1]);
return 1;
}
settrap(p, s);
}
return 0;
}
int
c_exitreturn(char **wp)
{
int how = LEXIT;
int n;
char *arg;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
arg = wp[builtin_opt.optind];
if (arg) {
if (!getn(arg, &n)) {
exstat = 1;
warningf(true, "%s: bad number", arg);
} else
exstat = n;
}
if (wp[0][0] == 'r') { /* return */
struct env *ep;
/* need to tell if this is exit or return so trap exit will
* work right (POSIX)
*/
for (ep = e; ep; ep = ep->oenv)
if (STOP_RETURN(ep->type)) {
how = LRETURN;
break;
}
}
if (how == LEXIT && !really_exit && j_stopped_running()) {
really_exit = 1;
how = LSHELL;
}
quitenv(NULL); /* get rid of any i/o redirections */
unwind(how);
/*NOTREACHED*/
return 0;
}
int
c_brkcont(char **wp)
{
int n, quit;
struct env *ep, *last_ep = NULL;
char *arg;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
arg = wp[builtin_opt.optind];
if (!arg)
n = 1;
else if (!bi_getn(arg, &n))
return 1;
quit = n;
if (quit <= 0) {
/* at&t ksh does this for non-interactive shells only - weird */
bi_errorf("%s: bad value", arg);
return 1;
}
/* Stop at E_NONE, E_PARSE, E_FUNC, or E_INCL */
for (ep = e; ep && !STOP_BRKCONT(ep->type); ep = ep->oenv)
if (ep->type == E_LOOP) {
if (--quit == 0)
break;
ep->flags |= EF_BRKCONT_PASS;
last_ep = ep;
}
if (quit) {
/* at&t ksh doesn't print a message - just does what it
* can. We print a message 'cause it helps in debugging
* scripts, but don't generate an error (ie, keep going).
*/
if (n == quit) {
warningf(true, "%s: cannot %s", wp[0], wp[0]);
return 0;
}
/* POSIX says if n is too big, the last enclosing loop
* shall be used. Doesn't say to print an error but we
* do anyway 'cause the user messed up.
*/
last_ep->flags &= ~EF_BRKCONT_PASS;
warningf(true, "%s: can only %s %d level(s)",
wp[0], wp[0], n - quit);
}
unwind(*wp[0] == 'b' ? LBREAK : LCONTIN);
/*NOTREACHED*/
}
int
c_set(char **wp)
{
int argi, setargs;
struct block *l = e->loc;
char **owp = wp;
if (wp[1] == NULL) {
static const char *const args [] = { "set", "-", NULL };
return c_typeset((char **) args);
}
argi = parse_args(wp, OF_SET, &setargs);
if (argi < 0)
return 1;
/* set $# and $* */
if (setargs) {
owp = wp += argi - 1;
wp[0] = l->argv[0]; /* save $0 */
while (*++wp != NULL)
*wp = str_save(*wp, &l->area);
l->argc = wp - owp - 1;
l->argv = (char **) alloc(sizeofN(char *, l->argc+2), &l->area);
for (wp = l->argv; (*wp++ = *owp++) != NULL; )
;
}
/* POSIX says set exit status is 0, but old scripts that use
* getopt(1), use the construct: set -- $(getopt ab:c "$@")
* which assumes the exit value set will be that of the $()
* (subst_exstat is cleared in execute() so that it will be 0
* if there are no command substitutions).
*/
return Flag(FPOSIX) ? 0 : subst_exstat;
}
int
c_unset(char **wp)
{
char *id;
int optc, unset_var = 1;
int ret = 0;
while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != EOF)
switch (optc) {
case 'f':
unset_var = 0;
break;
case 'v':
unset_var = 1;
break;
case '?':
return 1;
}
wp += builtin_opt.optind;
for (; (id = *wp) != NULL; wp++)
if (unset_var) { /* unset variable */
struct tbl *vp = global(id);
if (!(vp->flag & ISSET))
ret = 1;
if ((vp->flag&RDONLY)) {
bi_errorf("%s is read only", vp->name);
return 1;
}
unset(vp, strchr(id, '[') ? 1 : 0);
} else { /* unset function */
if (define(id, (struct op *) NULL))
ret = 1;
}
return ret;
}
static void
p_time(struct shf *shf, int posix, struct timeval *tv, int width,
char *prefix, char *suffix)
{
if (posix)
shf_fprintf(shf, "%s%*ld.%02ld%s", prefix ? prefix : "",
width, (long)tv->tv_sec, tv->tv_usec / 10000, suffix);
else
shf_fprintf(shf, "%s%*ldm%ld.%02lds%s", prefix ? prefix : "",
width, (long)(tv->tv_sec / 60), (long)(tv->tv_sec % 60),
tv->tv_usec / 10000, suffix);
}
int
c_times(char **wp GCC_FUNC_ATTR(unused))
{
struct rusage usage;
(void) getrusage(RUSAGE_SELF, &usage);
p_time(shl_stdout, 0, &usage.ru_utime, 0, NULL, " ");
p_time(shl_stdout, 0, &usage.ru_stime, 0, NULL, "\n");
(void) getrusage(RUSAGE_CHILDREN, &usage);
p_time(shl_stdout, 0, &usage.ru_utime, 0, NULL, " ");
p_time(shl_stdout, 0, &usage.ru_stime, 0, NULL, "\n");
return 0;
}
/*
* time pipeline (really a statement, not a built-in command)
*/
int
timex(struct op *t, int f)
{
#define TF_NOARGS BIT(0)
#define TF_NOREAL BIT(1) /* don't report real time */
#define TF_POSIX BIT(2) /* report in posix format */
int rv = 0;
struct rusage ru0, ru1, cru0, cru1;
struct timeval usrtime, systime, tv0, tv1;
int tf = 0;
extern struct timeval j_usrtime, j_systime; /* computed by j_wait */
char opts[1];
gettimeofday(&tv0, NULL);
getrusage(RUSAGE_SELF, &ru0);
getrusage(RUSAGE_CHILDREN, &cru0);
if (t->left) {
/*
* Two ways of getting cpu usage of a command: just use t0
* and t1 (which will get cpu usage from other jobs that
* finish while we are executing t->left), or get the
* cpu usage of t->left. at&t ksh does the former, while
* pdksh tries to do the later (the j_usrtime hack doesn't
* really work as it only counts the last job).
*/
timerclear(&j_usrtime);
timerclear(&j_systime);
if (t->left->type == TCOM)
t->left->str = opts;
opts[0] = 0;
rv = execute(t->left, f | XTIME);
tf |= opts[0];
gettimeofday(&tv1, NULL);
getrusage(RUSAGE_SELF, &ru1);
getrusage(RUSAGE_CHILDREN, &cru1);
} else
tf = TF_NOARGS;
if (tf & TF_NOARGS) { /* ksh93 - report shell times (shell+kids) */
tf |= TF_NOREAL;
timeradd(&ru0.ru_utime, &cru0.ru_utime, &usrtime);
timeradd(&ru0.ru_stime, &cru0.ru_stime, &systime);
} else {
timersub(&ru1.ru_utime, &ru0.ru_utime, &usrtime);
timeradd(&usrtime, &j_usrtime, &usrtime);
timersub(&ru1.ru_stime, &ru0.ru_stime, &systime);
timeradd(&systime, &j_systime, &systime);
}
if (!(tf & TF_NOREAL)) {
timersub(&tv1, &tv0, &tv1);
if (tf & TF_POSIX)
p_time(shl_out, 1, &tv1, 5, "real ", "\n");
else
p_time(shl_out, 0, &tv1, 5, NULL, " real ");
}
if (tf & TF_POSIX)
p_time(shl_out, 1, &usrtime, 5, "user ", "\n");
else
p_time(shl_out, 0, &usrtime, 5, NULL, " user ");
if (tf & TF_POSIX)
p_time(shl_out, 1, &systime, 5, "sys ", "\n");
else
p_time(shl_out, 0, &systime, 5, NULL, " system\n");
shf_flush(shl_out);
return rv;
}
void
timex_hook(struct op *t, char **volatile *app)
{
char **wp = *app;
int optc;
int i, j;
Getopt opt;
ksh_getopt_reset(&opt, 0);
opt.optind = 0; /* start at the start */
while ((optc = ksh_getopt(wp, &opt, ":p")) != EOF)
switch (optc) {
case 'p':
t->str[0] |= TF_POSIX;
break;
case '?':
errorf("time: -%s unknown option", opt.optarg);
case ':':
errorf("time: -%s requires an argument",
opt.optarg);
}
/* Copy command words down over options. */
if (opt.optind != 0) {
for (i = 0; i < opt.optind; i++)
afree(wp[i], ATEMP);
for (i = 0, j = opt.optind; (wp[i] = wp[j]); i++, j++)
;
}
if (!wp[0])
t->str[0] |= TF_NOARGS;
*app = wp;
}
/* exec with no args - args case is taken care of in comexec() */
int
c_exec(char **wp GCC_FUNC_ATTR(unused))
{
int i;
/* make sure redirects stay in place */
if (e->savefd != NULL) {
for (i = 0; i < NUFILE; i++) {
if (e->savefd[i] > 0)
close(e->savefd[i]);
/*
* For ksh keep anything > 2 private,
* for sh, let them be (POSIX says what
* happens is unspecified and the bourne shell
* keeps them open).
*/
if (!Flag(FSH) && i > 2 && e->savefd[i])
fcntl(i, F_SETFD, FD_CLOEXEC);
}
e->savefd = NULL;
}
return 0;
}
/* dummy function, special case in comexec() */
int
c_builtin(char **wp GCC_FUNC_ATTR(unused))
{
return 0;
}
extern int c_test(char **wp); /* in c_test.c */
extern int c_ulimit(char **wp); /* in c_ulimit.c */
/* A leading = means assignments before command are kept;
* a leading * means a POSIX special builtin;
* a leading + means a POSIX regular builtin
* (* and + should not be combined).
*/
const struct builtin shbuiltins [] = {
{"*=.", c_dot},
{"*=:", c_label},
{"[", c_test},
{"*=break", c_brkcont},
{"=builtin", c_builtin},
{"*=continue", c_brkcont},
{"*=eval", c_eval},
{"*=exec", c_exec},
{"*=exit", c_exitreturn},
{"+false", c_label},
{"*=return", c_exitreturn},
{"*=set", c_set},
{"*=shift", c_shift},
{"=times", c_times},
{"*=trap", c_trap},
{"+=wait", c_wait},
{"+read", c_read},
{"test", c_test},
{"+true", c_label},
{"ulimit", c_ulimit},
{"+umask", c_umask},
{"*=unset", c_unset},
{NULL, NULL}
};