mksh/funcs.c
tg cde2f02986 apply most of OpenBSD’s
│ Fix usage string for mknod builtin.
but don’t do a binary change as it doesn’t actually change anything

XXX fix all these usages anyway
XXX cd: cd: /home/tg/... - No such file or directory
XXX /bin/mksh: mknod: usage: mknod [-m mode] name [b | c] major minor
XXX /bin/mksh: mknod: mknod: No such file or directory
XXX etc. – but find out what POSuX demands ☹
2009-05-16 17:33:10 +00:00

3118 lines
69 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* $OpenBSD: c_ksh.c,v 1.33 2009/02/07 14:03:24 kili Exp $ */
/* $OpenBSD: c_sh.c,v 1.40 2009/05/05 17:59:55 millert Exp $ */
/* $OpenBSD: c_test.c,v 1.18 2009/03/01 20:11:06 otto Exp $ */
/* $OpenBSD: c_ulimit.c,v 1.17 2008/03/21 12:51:19 millert Exp $ */
/*-
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009
* Thorsten Glaser <tg@mirbsd.org>
*
* Provided that these terms and disclaimer and all copyright notices
* are retained or reproduced in an accompanying document, permission
* is granted to deal in this work without restriction, including un-
* limited rights to use, publicly perform, distribute, sell, modify,
* merge, give away, or sublicence.
*
* This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
* the utmost extent permitted by applicable law, neither express nor
* implied; without malicious intent or gross negligence. In no event
* may a licensor, author or contributor be held liable for indirect,
* direct, other damage, loss, or other issues arising in any way out
* of dealing in the work, even if advised of the possibility of such
* damage or existence of a defect, except proven that it results out
* of said person's immediate fault when using the work as intended.
*/
#include "sh.h"
__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.106 2009/05/16 17:33:10 tg Exp $");
/* 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 mkshbuiltins[] = {
{"*=.", 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},
{"+alias", c_alias}, /* no =: at&t manual wrong */
{"+cd", c_cd},
{"+command", c_command},
{"echo", c_print},
{"*=export", c_typeset},
{"+fc", c_fc},
{"+getopts", c_getopts},
{"+jobs", c_jobs},
{"+kill", c_kill},
{"let", c_let},
{"print", c_print},
{"pwd", c_pwd},
{"*=readonly", c_typeset},
{"=typeset", c_typeset},
{"+unalias", c_unalias},
{"whence", c_whence},
#ifndef MKSH_UNEMPLOYED
{"+bg", c_fgbg},
{"+fg", c_fgbg},
#endif
{"bind", c_bind},
#if HAVE_MKNOD
{"mknod", c_mknod},
#endif
#if HAVE_REALPATH
{"realpath", c_realpath},
#endif
{"rename", c_rename},
{NULL, (int (*)(const char **))NULL}
};
struct kill_info {
int num_width;
int name_width;
};
static const struct t_op {
char op_text[4];
Test_op op_num;
} u_ops[] = {
{"-a", TO_FILAXST },
{"-b", TO_FILBDEV },
{"-c", TO_FILCDEV },
{"-d", TO_FILID },
{"-e", TO_FILEXST },
{"-f", TO_FILREG },
{"-G", TO_FILGID },
{"-g", TO_FILSETG },
{"-h", TO_FILSYM },
{"-H", TO_FILCDF },
{"-k", TO_FILSTCK },
{"-L", TO_FILSYM },
{"-n", TO_STNZE },
{"-O", TO_FILUID },
{"-o", TO_OPTION },
{"-p", TO_FILFIFO },
{"-r", TO_FILRD },
{"-s", TO_FILGZ },
{"-S", TO_FILSOCK },
{"-t", TO_FILTT },
{"-u", TO_FILSETU },
{"-w", TO_FILWR },
{"-x", TO_FILEX },
{"-z", TO_STZER },
{"", TO_NONOP }
};
static const struct t_op b_ops[] = {
{"=", TO_STEQL },
{"==", TO_STEQL },
{"!=", TO_STNEQ },
{"<", TO_STLT },
{">", TO_STGT },
{"-eq", TO_INTEQ },
{"-ne", TO_INTNE },
{"-gt", TO_INTGT },
{"-ge", TO_INTGE },
{"-lt", TO_INTLT },
{"-le", TO_INTLE },
{"-ef", TO_FILEQ },
{"-nt", TO_FILNT },
{"-ot", TO_FILOT },
{"", TO_NONOP }
};
static int test_eaccess(const char *, int);
static int test_oexpr(Test_env *, bool);
static int test_aexpr(Test_env *, bool);
static int test_nexpr(Test_env *, bool);
static int test_primary(Test_env *, bool);
static int ptest_isa(Test_env *, Test_meta);
static const char *ptest_getopnd(Test_env *, Test_op, bool);
static void ptest_error(Test_env *, int, const char *);
static char *kill_fmt_entry(const void *, int, char *, int);
static void p_time(struct shf *, int, struct timeval *, int,
const char *, const char *);
int
c_cd(const char **wp)
{
int optc, rv, phys_path;
bool physical = Flag(FPHYSICAL) ? true : false;
int cdnode; /* was a node from cdpath added in? */
bool printpath = false; /* print where we cd'd? */
struct tbl *pwd_s, *oldpwd_s;
XString xs;
char *dir, *allocd = NULL, *try, *pwd, *cdpath;
while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
switch (optc) {
case 'L':
physical = false;
break;
case 'P':
physical = true;
break;
case '?':
return 1;
}
wp += builtin_opt.optind;
if (Flag(FRESTRICTED)) {
bi_errorf("restricted shell - can't cd");
return 1;
}
pwd_s = global("PWD");
oldpwd_s = global("OLDPWD");
if (!wp[0]) {
/* No arguments - go home */
if ((dir = str_val(global("HOME"))) == null) {
bi_errorf("no home directory (HOME not set)");
return 1;
}
} else if (!wp[1]) {
/* One argument: - or dir */
strdupx(allocd, wp[0], ATEMP);
if (ksh_isdash((dir = allocd))) {
afree(allocd, ATEMP);
allocd = NULL;
dir = str_val(oldpwd_s);
if (dir == null) {
bi_errorf("no OLDPWD");
return 1;
}
printpath = true;
}
} else if (!wp[2]) {
/* Two arguments - substitute arg1 in PWD for arg2 */
int ilen, olen, nlen, elen;
char *cp;
if (!current_wd[0]) {
bi_errorf("don't know current directory");
return 1;
}
/* substitute arg1 for arg2 in current path.
* if the first substitution fails because the cd fails
* we could try to find another substitution. For now
* we don't
*/
if ((cp = strstr(current_wd, wp[0])) == NULL) {
bi_errorf("bad substitution");
return 1;
}
ilen = cp - current_wd;
olen = strlen(wp[0]);
nlen = strlen(wp[1]);
elen = strlen(current_wd + ilen + olen) + 1;
dir = allocd = alloc(ilen + nlen + elen, ATEMP);
memcpy(dir, current_wd, ilen);
memcpy(dir + ilen, wp[1], nlen);
memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen);
printpath = true;
} else {
bi_errorf("too many arguments");
return 1;
}
XinitN(xs, PATH_MAX, ATEMP);
cdpath = str_val(global("CDPATH"));
do {
cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path);
if (physical)
rv = chdir(try = Xstring(xs, xp) + phys_path);
else {
simplify_path(Xstring(xs, xp));
rv = chdir(try = Xstring(xs, xp));
}
} while (rv < 0 && cdpath != NULL);
if (rv < 0) {
if (cdnode)
bi_errorf("%s: bad directory", dir);
else
bi_errorf("%s - %s", try, strerror(errno));
afree(allocd, ATEMP);
return 1;
}
/* Clear out tracked aliases with relative paths */
flushcom(0);
/* Set OLDPWD (note: unsetting OLDPWD does not disable this
* setting in at&t ksh)
*/
if (current_wd[0])
/* Ignore failure (happens if readonly or integer) */
setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR);
if (Xstring(xs, xp)[0] != '/') {
pwd = NULL;
} else
if (!physical || !(pwd = get_phys_path(Xstring(xs, xp))))
pwd = Xstring(xs, xp);
/* Set PWD */
if (pwd) {
char *ptmp = pwd;
set_current_wd(ptmp);
/* Ignore failure (happens if readonly or integer) */
setstr(pwd_s, ptmp, KSH_RETURN_ERROR);
} else {
set_current_wd(null);
pwd = Xstring(xs, xp);
/* XXX unset $PWD? */
}
if (printpath || cdnode)
shprintf("%s\n", pwd);
afree(allocd, ATEMP);
return 0;
}
int
c_pwd(const char **wp)
{
int optc;
bool physical = Flag(FPHYSICAL) ? true : false;
char *p, *allocd = NULL;
while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
switch (optc) {
case 'L':
physical = false;
break;
case 'P':
physical = true;
break;
case '?':
return 1;
}
wp += builtin_opt.optind;
if (wp[0]) {
bi_errorf("too many arguments");
return 1;
}
p = current_wd[0] ? (physical ? get_phys_path(current_wd) :
current_wd) : NULL;
if (p && access(p, R_OK) < 0)
p = NULL;
if (!p && !(p = allocd = ksh_get_wd(NULL))) {
bi_errorf("can't get current directory - %s", strerror(errno));
return (1);
}
shprintf("%s\n", p);
afree(allocd, ATEMP);
return 0;
}
int
c_print(const char **wp)
{
#define PO_NL BIT(0) /* print newline */
#define PO_EXPAND BIT(1) /* expand backslash sequences */
#define PO_PMINUSMINUS BIT(2) /* print a -- argument */
#define PO_HIST BIT(3) /* print to history instead of stdout */
#define PO_COPROC BIT(4) /* printing to coprocess: block SIGPIPE */
int fd = 1;
int flags = PO_EXPAND|PO_NL;
const char *s, *emsg;
XString xs;
char *xp;
if (wp[0][0] == 'e') { /* echo command */
int nflags = flags;
/* A compromise between sysV and BSD echo commands:
* escape sequences are enabled by default, and
* -n, -e and -E are recognised if they appear
* in arguments with no illegal options (ie, echo -nq
* will print -nq).
* Different from sysV echo since options are recognised,
* different from BSD echo since escape sequences are enabled
* by default.
*/
wp += 1;
if (Flag(FPOSIX)) {
if (*wp && strcmp(*wp, "-n") == 0) {
flags &= ~PO_NL;
wp++;
}
} else
while ((s = *wp) && *s == '-' && s[1]) {
while (*++s)
if (*s == 'n')
nflags &= ~PO_NL;
else if (*s == 'e')
nflags |= PO_EXPAND;
else if (*s == 'E')
nflags &= ~PO_EXPAND;
else
/*
* bad option: don't use
* nflags, print argument
*/
break;
if (*s)
break;
wp++;
flags = nflags;
}
} else {
int optc;
const char *opts = "Rnprsu,";
while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1)
switch (optc) {
case 'R': /* fake BSD echo command */
flags |= PO_PMINUSMINUS;
flags &= ~PO_EXPAND;
opts = "ne";
break;
case 'e':
flags |= PO_EXPAND;
break;
case 'n':
flags &= ~PO_NL;
break;
case 'p':
if ((fd = coproc_getfd(W_OK, &emsg)) < 0) {
bi_errorf("-p: %s", emsg);
return 1;
}
break;
case 'r':
flags &= ~PO_EXPAND;
break;
case 's':
flags |= PO_HIST;
break;
case 'u':
if (!*(s = builtin_opt.optarg))
fd = 0;
else if ((fd = check_fd(s, W_OK, &emsg)) < 0) {
bi_errorf("-u: %s: %s", s, emsg);
return 1;
}
break;
case '?':
return 1;
}
if (!(builtin_opt.info & GI_MINUSMINUS)) {
/* treat a lone - like -- */
if (wp[builtin_opt.optind] &&
ksh_isdash(wp[builtin_opt.optind]))
builtin_opt.optind++;
} else if (flags & PO_PMINUSMINUS)
builtin_opt.optind--;
wp += builtin_opt.optind;
}
Xinit(xs, xp, 128, ATEMP);
while (*wp != NULL) {
int c;
s = *wp;
while ((c = *s++) != '\0') {
Xcheck(xs, xp);
if ((flags & PO_EXPAND) && c == '\\') {
int i;
switch ((c = *s++)) {
/* Oddly enough, \007 seems more portable than
* \a (due to HP-UX cc, Ultrix cc, old PCCs,
* etc.).
*/
case 'a': c = '\007'; break;
case 'b': c = '\b'; break;
case 'c': flags &= ~PO_NL;
continue; /* AT&T brain damage */
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'v': c = 0x0B; break;
case '0':
/* Look for an octal number: can have
* three digits (not counting the
* leading 0). Truly burnt.
*/
c = 0;
for (i = 0; i < 3; i++) {
if (*s >= '0' && *s <= '7')
c = c*8 + *s++ - '0';
else
break;
}
break;
case 'x':
/* Look for a hexadecimal number of
* up to 2 digits, write raw octet.
*/
c = 0;
for (i = 0; i < 2; i++) {
c <<= 4;
if (*s >= '0' && *s <= '9')
c += *s++ - '0';
else if (*s >= 'A' && *s <= 'F')
c += *s++ - 'A' + 10;
else if (*s >= 'a' && *s <= 'f')
c += *s++ - 'a' + 10;
else {
c >>= 4;
break;
}
}
break;
case 'u':
/* Look for a hexadecimal number of
* up to 4 digits, write Unicode.
*/
c = 0;
for (i = 0; i < 4; i++) {
c <<= 4;
if (*s >= '0' && *s <= '9')
c += *s++ - '0';
else if (*s >= 'A' && *s <= 'F')
c += *s++ - 'A' + 10;
else if (*s >= 'a' && *s <= 'f')
c += *s++ - 'a' + 10;
else {
c >>= 4;
break;
}
}
if (c < 0x80)
/* Xput below writes ASCII */;
else if (c < 0x0800) {
Xput(xs, xp, (c >> 6) | 0xC0);
c = 0x80 | (c & 0x3F);
/* leave 2nd octet to below */
} else {
Xput(xs, xp, (c >> 12) | 0xE0);
Xput(xs, xp,
((c >> 6) & 0x3F) | 0x80);
c = 0x80 | (c & 0x3F);
/* leave 3rd octet to below */
}
break;
case '\0': s--; c = '\\'; break;
case '\\': break;
default:
Xput(xs, xp, '\\');
}
}
Xput(xs, xp, c);
}
if (*++wp != NULL)
Xput(xs, xp, ' ');
}
if (flags & PO_NL)
Xput(xs, xp, '\n');
if (flags & PO_HIST) {
Xput(xs, xp, '\0');
histsave(&source->line, Xstring(xs, xp), true, false);
Xfree(xs, xp);
} else {
int n, len = Xlength(xs, xp);
int opipe = 0;
/* Ensure we aren't killed by a SIGPIPE while writing to
* a coprocess. at&t ksh doesn't seem to do this (seems
* to just check that the co-process is alive which is
* not enough).
*/
if (coproc.write >= 0 && coproc.write == fd) {
flags |= PO_COPROC;
opipe = block_pipe();
}
for (s = Xstring(xs, xp); len > 0; ) {
n = write(fd, s, len);
if (n < 0) {
if (flags & PO_COPROC)
restore_pipe(opipe);
if (errno == EINTR) {
/* allow user to ^C out */
intrcheck();
if (flags & PO_COPROC)
opipe = block_pipe();
continue;
}
return 1;
}
s += n;
len -= n;
}
if (flags & PO_COPROC)
restore_pipe(opipe);
}
return 0;
}
int
c_whence(const char **wp)
{
struct tbl *tp;
const char *id;
bool pflag = false, vflag = false, Vflag = false;
int rv = 0, optc, fcflags;
bool iam_whence = wp[0][0] == 'w';
const char *opts = iam_whence ? "pv" : "pvV";
while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1)
switch (optc) {
case 'p':
pflag = true;
break;
case 'v':
vflag = true;
break;
case 'V':
Vflag = true;
break;
case '?':
return 1;
}
wp += builtin_opt.optind;
fcflags = FC_BI | FC_PATH | FC_FUNC;
if (!iam_whence) {
/* Note that -p on its own is deal with in comexec() */
if (pflag)
fcflags |= FC_DEFPATH;
/* Convert command options to whence options - note that
* command -pV uses a different path search than whence -v
* or whence -pv. This should be considered a feature.
*/
vflag = Vflag;
}
if (pflag)
fcflags &= ~(FC_BI | FC_FUNC);
while ((vflag || rv == 0) && (id = *wp++) != NULL) {
tp = NULL;
if ((iam_whence || vflag) && !pflag)
tp = ktsearch(&keywords, id, hash(id));
if (!tp && !pflag) {
tp = ktsearch(&aliases, id, hash(id));
if (tp && !(tp->flag & ISSET))
tp = NULL;
}
if (!tp)
tp = findcom(id, fcflags);
if (vflag || (tp->type != CALIAS && tp->type != CEXEC &&
tp->type != CTALIAS))
shf_puts(id, shl_stdout);
switch (tp->type) {
case CKEYWD:
if (vflag)
shf_puts(" is a reserved word", shl_stdout);
break;
case CALIAS:
if (vflag)
shprintf(" is an %salias for ",
(tp->flag & EXPORT) ? "exported " : null);
if (!iam_whence && !vflag)
shprintf("alias %s=", id);
print_value_quoted(tp->val.s);
break;
case CFUNC:
if (vflag) {
shf_puts(" is a", shl_stdout);
if (tp->flag & EXPORT)
shf_puts("n exported", shl_stdout);
if (tp->flag & TRACE)
shf_puts(" traced", shl_stdout);
if (!(tp->flag & ISSET)) {
shf_puts(" undefined", shl_stdout);
if (tp->u.fpath)
shprintf(" (autoload from %s)",
tp->u.fpath);
}
shf_puts(" function", shl_stdout);
}
break;
case CSHELL:
if (vflag)
shprintf(" is a%s shell builtin",
(tp->flag & SPEC_BI) ? " special" : null);
break;
case CTALIAS:
case CEXEC:
if (tp->flag & ISSET) {
if (vflag) {
shf_puts(" is ", shl_stdout);
if (tp->type == CTALIAS)
shprintf("a tracked %salias for ",
(tp->flag & EXPORT) ?
"exported " : null);
}
shf_puts(tp->val.s, shl_stdout);
} else {
if (vflag)
shf_puts(" not found", shl_stdout);
rv = 1;
}
break;
default:
shprintf("%s is *GOK*", id);
break;
}
if (vflag || !rv)
shf_putc('\n', shl_stdout);
}
return rv;
}
/* Deal with command -vV - command -p dealt with in comexec() */
int
c_command(const char **wp)
{
/* Let c_whence do the work. Note that c_command() must be
* a distinct function from c_whence() (tested in comexec()).
*/
return c_whence(wp);
}
/* typeset, export, and readonly */
int
c_typeset(const char **wp)
{
struct block *l;
struct tbl *vp, **p;
Tflag fset = 0, fclr = 0, flag;
int thing = 0, field, base, optc;
const char *opts;
const char *fieldstr, *basestr;
bool localv = false, func = false, pflag = false, istset = true;
switch (**wp) {
case 'e': /* export */
fset |= EXPORT;
istset = false;
break;
case 'r': /* readonly */
fset |= RDONLY;
istset = false;
break;
case 's': /* set */
/* called with 'typeset -' */
break;
case 't': /* typeset */
localv = true;
break;
}
/* see comment below regarding possible opions */
opts = istset ? "L#R#UZ#fi#lprtux" : "p";
fieldstr = basestr = NULL;
builtin_opt.flags |= GF_PLUSOPT;
/* at&t ksh seems to have 0-9 as options which are multiplied
* to get a number that is used with -L, -R, -Z or -i (eg, -1R2
* sets right justify in a field of 12). This allows options
* to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and
* does not allow the number to be specified as a separate argument
* Here, the number must follow the RLZi option, but is optional
* (see the # kludge in ksh_getopt()).
*/
while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1) {
flag = 0;
switch (optc) {
case 'L':
flag = LJUST;
fieldstr = builtin_opt.optarg;
break;
case 'R':
flag = RJUST;
fieldstr = builtin_opt.optarg;
break;
case 'U':
/* at&t ksh uses u, but this conflicts with
* upper/lower case. If this option is changed,
* need to change the -U below as well
*/
flag = INT_U;
break;
case 'Z':
flag = ZEROFIL;
fieldstr = builtin_opt.optarg;
break;
case 'f':
func = true;
break;
case 'i':
flag = INTEGER;
basestr = builtin_opt.optarg;
break;
case 'l':
flag = LCASEV;
break;
case 'p':
/* export, readonly: POSIX -p flag */
/* typeset: show values as well */
pflag = true;
if (istset)
continue;
break;
case 'r':
flag = RDONLY;
break;
case 't':
flag = TRACE;
break;
case 'u':
flag = UCASEV_AL; /* upper case / autoload */
break;
case 'x':
flag = EXPORT;
break;
case '?':
return 1;
}
if (builtin_opt.info & GI_PLUS) {
fclr |= flag;
fset &= ~flag;
thing = '+';
} else {
fset |= flag;
fclr &= ~flag;
thing = '-';
}
}
field = 0;
if (fieldstr && !bi_getn(fieldstr, &field))
return 1;
base = 0;
if (basestr && !bi_getn(basestr, &base))
return 1;
if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] &&
(wp[builtin_opt.optind][0] == '-' ||
wp[builtin_opt.optind][0] == '+') &&
wp[builtin_opt.optind][1] == '\0') {
thing = wp[builtin_opt.optind][0];
builtin_opt.optind++;
}
if (func && ((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT))) {
bi_errorf("only -t, -u and -x options may be used with -f");
return 1;
}
if (wp[builtin_opt.optind]) {
/* Take care of exclusions.
* At this point, flags in fset are cleared in fclr and vise
* versa. This property should be preserved.
*/
if (fset & LCASEV) /* LCASEV has priority over UCASEV_AL */
fset &= ~UCASEV_AL;
if (fset & LJUST) /* LJUST has priority over RJUST */
fset &= ~RJUST;
if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */
fset |= RJUST;
fclr &= ~RJUST;
}
/* Setting these attributes clears the others, unless they
* are also set in this command
*/
if (fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV |
INTEGER | INT_U | INT_L))
fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL |
LCASEV | INTEGER | INT_U | INT_L);
}
/* set variables and attributes */
if (wp[builtin_opt.optind]) {
int i, rv = 0;
struct tbl *f;
if (localv && !func)
fset |= LOCAL;
for (i = builtin_opt.optind; wp[i]; i++) {
if (func) {
f = findfunc(wp[i], hash(wp[i]),
(fset&UCASEV_AL) ? true : false);
if (!f) {
/* at&t ksh does ++rv: bogus */
rv = 1;
continue;
}
if (fset | fclr) {
f->flag |= fset;
f->flag &= ~fclr;
} else
fptreef(shl_stdout, 0,
f->flag & FKSH ?
"function %s %T\n" :
"%s() %T\n", wp[i], f->val.t);
} else if (!typeset(wp[i], fset, fclr, field, base)) {
bi_errorf("%s: not identifier", wp[i]);
return 1;
}
}
return rv;
}
/* list variables and attributes */
flag = fset | fclr; /* no difference at this point.. */
if (func) {
for (l = e->loc; l; l = l->next) {
for (p = ktsort(&l->funs); (vp = *p++); ) {
if (flag && (vp->flag & flag) == 0)
continue;
if (thing == '-')
fptreef(shl_stdout, 0, vp->flag & FKSH ?
"function %s %T\n" : "%s() %T\n",
vp->name, vp->val.t);
else
shprintf("%s\n", vp->name);
}
}
} else {
for (l = e->loc; l; l = l->next) {
for (p = ktsort(&l->vars); (vp = *p++); ) {
struct tbl *tvp;
bool any_set = false;
/*
* See if the parameter is set (for arrays, if any
* element is set).
*/
for (tvp = vp; tvp; tvp = tvp->u.array)
if (tvp->flag & ISSET) {
any_set = true;
break;
}
/*
* Check attributes - note that all array elements
* have (should have?) the same attributes, so checking
* the first is sufficient.
*
* Report an unset param only if the user has
* explicitly given it some attribute (like export);
* otherwise, after "echo $FOO", we would report FOO...
*/
if (!any_set && !(vp->flag & USERATTRIB))
continue;
if (flag && (vp->flag & flag) == 0)
continue;
for (; vp; vp = vp->u.array) {
/* Ignore array elements that aren't
* set unless there are no set elements,
* in which case the first is reported on */
if ((vp->flag&ARRAY) && any_set &&
!(vp->flag & ISSET))
continue;
/* no arguments */
if (thing == 0 && flag == 0) {
/* at&t ksh prints things
* like export, integer,
* leftadj, zerofill, etc.,
* but POSIX says must
* be suitable for re-entry...
*/
shf_puts("typeset ", shl_stdout);
if ((vp->flag&INTEGER))
shf_puts("-i ", shl_stdout);
if ((vp->flag&EXPORT))
shf_puts("-x ", shl_stdout);
if ((vp->flag&RDONLY))
shf_puts("-r ", shl_stdout);
if ((vp->flag&TRACE))
shf_puts("-t ", shl_stdout);
if ((vp->flag&LJUST))
shprintf("-L%d ", vp->u2.field);
if ((vp->flag&RJUST))
shprintf("-R%d ", vp->u2.field);
if ((vp->flag&ZEROFIL))
shf_puts("-Z ", shl_stdout);
if ((vp->flag&LCASEV))
shf_puts("-l ", shl_stdout);
if ((vp->flag&UCASEV_AL))
shf_puts("-u ", shl_stdout);
if ((vp->flag&INT_U))
shf_puts("-U ", shl_stdout);
shf_puts(vp->name, shl_stdout);
if (pflag) {
char *s = str_val(vp);
shf_putc('=', shl_stdout);
/* at&t ksh can't have
* justified integers.. */
if ((vp->flag &
(INTEGER|LJUST|RJUST)) ==
INTEGER)
shf_puts(s, shl_stdout);
else
print_value_quoted(s);
}
shf_putc('\n', shl_stdout);
if (vp->flag & ARRAY)
break;
} else {
if (pflag)
shf_puts(istset ?
"typeset " :
(flag & EXPORT) ?
"export " :
"readonly ",
shl_stdout);
if ((vp->flag&ARRAY) && any_set)
shprintf("%s[%lu]",
vp->name,
(unsigned long)vp->index);
else
shf_puts(vp->name, shl_stdout);
if (thing == '-' && (vp->flag&ISSET)) {
char *s = str_val(vp);
shf_putc('=', shl_stdout);
/* at&t ksh can't have
* justified integers.. */
if ((vp->flag &
(INTEGER|LJUST|RJUST)) ==
INTEGER)
shf_puts(s, shl_stdout);
else
print_value_quoted(s);
}
shf_putc('\n', shl_stdout);
}
/* Only report first 'element' of an array with
* no set elements.
*/
if (!any_set)
break;
}
}
}
}
return 0;
}
int
c_alias(const char **wp)
{
struct table *t = &aliases;
int rv = 0, prefix = 0;
bool rflag = false, tflag, Uflag = false, pflag = false;
Tflag xflag = 0;
int optc;
builtin_opt.flags |= GF_PLUSOPT;
while ((optc = ksh_getopt(wp, &builtin_opt, "dprtUx")) != -1) {
prefix = builtin_opt.info & GI_PLUS ? '+' : '-';
switch (optc) {
case 'd':
#ifdef MKSH_SMALL
return (0);
#else
t = &homedirs;
#endif
break;
case 'p':
pflag = true;
break;
case 'r':
rflag = true;
break;
case 't':
t = &taliases;
break;
case 'U':
/*
* kludge for tracked alias initialization
* (don't do a path search, just make an entry)
*/
Uflag = true;
break;
case 'x':
xflag = EXPORT;
break;
case '?':
return 1;
}
}
wp += builtin_opt.optind;
if (!(builtin_opt.info & GI_MINUSMINUS) && *wp &&
(wp[0][0] == '-' || wp[0][0] == '+') && wp[0][1] == '\0') {
prefix = wp[0][0];
wp++;
}
tflag = t == &taliases;
/* "hash -r" means reset all the tracked aliases.. */
if (rflag) {
static const char *args[] = {
"unalias", "-ta", NULL
};
if (!tflag || *wp) {
shf_puts("alias: -r flag can only be used with -t"
" and without arguments\n", shl_stdout);
return 1;
}
ksh_getopt_reset(&builtin_opt, GF_ERROR);
return c_unalias(args);
}
if (*wp == NULL) {
struct tbl *ap, **p;
for (p = ktsort(t); (ap = *p++) != NULL; )
if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) {
if (pflag)
shf_puts("alias ", shl_stdout);
shf_puts(ap->name, shl_stdout);
if (prefix != '+') {
shf_putc('=', shl_stdout);
print_value_quoted(ap->val.s);
}
shf_putc('\n', shl_stdout);
}
}
for (; *wp != NULL; wp++) {
const char *alias = *wp, *val, *newval;
char *xalias = NULL;
struct tbl *ap;
int h;
if ((val = cstrchr(alias, '='))) {
strndupx(xalias, alias, val++ - alias, ATEMP);
alias = xalias;
}
h = hash(alias);
if (val == NULL && !tflag && !xflag) {
ap = ktsearch(t, alias, h);
if (ap != NULL && (ap->flag&ISSET)) {
if (pflag)
shf_puts("alias ", shl_stdout);
shf_puts(ap->name, shl_stdout);
if (prefix != '+') {
shf_putc('=', shl_stdout);
print_value_quoted(ap->val.s);
}
shf_putc('\n', shl_stdout);
} else {
shprintf("%s alias not found\n", alias);
rv = 1;
}
continue;
}
ap = ktenter(t, alias, h);
ap->type = tflag ? CTALIAS : CALIAS;
/* Are we setting the value or just some flags? */
if ((val && !tflag) || (!val && tflag && !Uflag)) {
if (ap->flag&ALLOC) {
ap->flag &= ~(ALLOC|ISSET);
afree(ap->val.s, APERM);
}
/* ignore values for -t (at&t ksh does this) */
newval = tflag ? search(alias, path, X_OK, NULL) : val;
if (newval) {
strdupx(ap->val.s, newval, APERM);
ap->flag |= ALLOC|ISSET;
} else
ap->flag &= ~ISSET;
}
ap->flag |= DEFINED;
if (prefix == '+')
ap->flag &= ~xflag;
else
ap->flag |= xflag;
afree(xalias, ATEMP);
}
return rv;
}
int
c_unalias(const char **wp)
{
struct table *t = &aliases;
struct tbl *ap;
int optc, rv = 0;
bool all = false;
while ((optc = ksh_getopt(wp, &builtin_opt, "adt")) != -1)
switch (optc) {
case 'a':
all = true;
break;
case 'd':
#ifdef MKSH_SMALL
return (0);
#else
t = &homedirs;
#endif
break;
case 't':
t = &taliases;
break;
case '?':
return 1;
}
wp += builtin_opt.optind;
for (; *wp != NULL; wp++) {
ap = ktsearch(t, *wp, hash(*wp));
if (ap == NULL) {
rv = 1; /* POSIX */
continue;
}
if (ap->flag&ALLOC) {
ap->flag &= ~(ALLOC|ISSET);
afree(ap->val.s, APERM);
}
ap->flag &= ~(DEFINED|ISSET|EXPORT);
}
if (all) {
struct tstate ts;
for (ktwalk(&ts, t); (ap = ktnext(&ts)); ) {
if (ap->flag&ALLOC) {
ap->flag &= ~(ALLOC|ISSET);
afree(ap->val.s, APERM);
}
ap->flag &= ~(DEFINED|ISSET|EXPORT);
}
}
return rv;
}
int
c_let(const char **wp)
{
int rv = 1;
mksh_ari_t val;
if (wp[1] == NULL) /* at&t ksh does this */
bi_errorf("no arguments");
else
for (wp++; *wp; wp++)
if (!evaluate(*wp, &val, KSH_RETURN_ERROR, true)) {
rv = 2; /* distinguish error from zero result */
break;
} else
rv = val == 0;
return rv;
}
int
c_jobs(const char **wp)
{
int optc, flag = 0, nflag = 0, rv = 0;
while ((optc = ksh_getopt(wp, &builtin_opt, "lpnz")) != -1)
switch (optc) {
case 'l':
flag = 1;
break;
case 'p':
flag = 2;
break;
case 'n':
nflag = 1;
break;
case 'z': /* debugging: print zombies */
nflag = -1;
break;
case '?':
return 1;
}
wp += builtin_opt.optind;
if (!*wp) {
if (j_jobs(NULL, flag, nflag))
rv = 1;
} else {
for (; *wp; wp++)
if (j_jobs(*wp, flag, nflag))
rv = 1;
}
return rv;
}
#ifndef MKSH_UNEMPLOYED
int
c_fgbg(const char **wp)
{
bool bg = strcmp(*wp, "bg") == 0;
int rv = 0;
if (!Flag(FMONITOR)) {
bi_errorf("job control not enabled");
return 1;
}
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
wp += builtin_opt.optind;
if (*wp)
for (; *wp; wp++)
rv = j_resume(*wp, bg);
else
rv = j_resume("%%", bg);
return bg ? 0 : rv;
}
#endif
/* format a single kill item */
static char *
kill_fmt_entry(const void *arg, int i, char *buf, int buflen)
{
const struct kill_info *ki = (const struct kill_info *)arg;
i++;
shf_snprintf(buf, buflen, "%*d %*s %s",
ki->num_width, i,
ki->name_width, sigtraps[i].name,
sigtraps[i].mess);
return buf;
}
int
c_kill(const char **wp)
{
Trap *t = NULL;
const char *p;
bool lflag = false;
int i, n, rv, sig;
/* assume old style options if -digits or -UPPERCASE */
if ((p = wp[1]) && *p == '-' && (ksh_isdigit(p[1]) ||
ksh_isupper(p[1]))) {
if (!(t = gettrap(p + 1, true))) {
bi_errorf("bad signal '%s'", p + 1);
return 1;
}
i = (wp[2] && strcmp(wp[2], "--") == 0) ? 3 : 2;
} else {
int optc;
while ((optc = ksh_getopt(wp, &builtin_opt, "ls:")) != -1)
switch (optc) {
case 'l':
lflag = true;
break;
case 's':
if (!(t = gettrap(builtin_opt.optarg, true))) {
bi_errorf("bad signal '%s'",
builtin_opt.optarg);
return 1;
}
break;
case '?':
return 1;
}
i = builtin_opt.optind;
}
if ((lflag && t) || (!wp[i] && !lflag)) {
#ifndef MKSH_SMALL
shf_puts("usage:\tkill [-s signame | -signum | -signame]"
" { job | pid | pgrp } ...\n"
"\tkill -l [exit_status ...]\n", shl_out);
#endif
bi_errorfz();
return 1;
}
if (lflag) {
if (wp[i]) {
for (; wp[i]; i++) {
if (!bi_getn(wp[i], &n))
return 1;
if (n > 128 && n < 128 + NSIG)
n -= 128;
if (n > 0 && n < NSIG)
shprintf("%s\n", sigtraps[n].name);
else
shprintf("%d\n", n);
}
} else {
int w, j;
int mess_width;
struct kill_info ki;
for (j = NSIG, ki.num_width = 1; j >= 10; j /= 10)
ki.num_width++;
ki.name_width = mess_width = 0;
for (j = 0; j < NSIG; j++) {
w = strlen(sigtraps[j].name);
if (w > ki.name_width)
ki.name_width = w;
w = strlen(sigtraps[j].mess);
if (w > mess_width)
mess_width = w;
}
print_columns(shl_stdout, NSIG - 1,
kill_fmt_entry, (void *)&ki,
ki.num_width + ki.name_width + mess_width + 3, 1);
}
return 0;
}
rv = 0;
sig = t ? t->signal : SIGTERM;
for (; (p = wp[i]); i++) {
if (*p == '%') {
if (j_kill(p, sig))
rv = 1;
} else if (!getn(p, &n)) {
bi_errorf("%s: arguments must be jobs or process IDs",
p);
rv = 1;
} else {
/* use killpg if < -1 since -1 does special things for
* some non-killpg-endowed kills
*/
if ((n < -1 ? killpg(-n, sig) : kill(n, sig)) < 0) {
bi_errorf("%s: %s", p, strerror(errno));
rv = 1;
}
}
}
return rv;
}
void
getopts_reset(int val)
{
if (val >= 1) {
ksh_getopt_reset(&user_opt, GF_NONAME | GF_PLUSOPT);
user_opt.optind = user_opt.uoptind = val;
}
}
int
c_getopts(const char **wp)
{
int argc, optc, rv;
const char *opts, *var;
char buf[3];
struct tbl *vq, *voptarg;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
wp += builtin_opt.optind;
opts = *wp++;
if (!opts) {
bi_errorf("missing options argument");
return 1;
}
var = *wp++;
if (!var) {
bi_errorf("missing name argument");
return 1;
}
if (!*var || *skip_varname(var, true)) {
bi_errorf("%s: is not an identifier", var);
return 1;
}
if (e->loc->next == NULL) {
internal_warningf("c_getopts: no argv");
return 1;
}
/* Which arguments are we parsing... */
if (*wp == NULL)
wp = e->loc->next->argv;
else
*--wp = e->loc->next->argv[0];
/* Check that our saved state won't cause a core dump... */
for (argc = 0; wp[argc]; argc++)
;
if (user_opt.optind > argc ||
(user_opt.p != 0 &&
user_opt.p > strlen(wp[user_opt.optind - 1]))) {
bi_errorf("arguments changed since last call");
return 1;
}
user_opt.optarg = NULL;
optc = ksh_getopt(wp, &user_opt, opts);
if (optc >= 0 && optc != '?' && (user_opt.info & GI_PLUS)) {
buf[0] = '+';
buf[1] = optc;
buf[2] = '\0';
} else {
/* POSIX says var is set to ? at end-of-options, at&t ksh
* sets it to null - we go with POSIX...
*/
buf[0] = optc < 0 ? '?' : optc;
buf[1] = '\0';
}
/* at&t ksh does not change OPTIND if it was an unknown option.
* Scripts counting on this are prone to break... (ie, don't count
* on this staying).
*/
if (optc != '?') {
user_opt.uoptind = user_opt.optind;
}
voptarg = global("OPTARG");
voptarg->flag &= ~RDONLY; /* at&t ksh clears ro and int */
/* Paranoia: ensure no bizarre results. */
if (voptarg->flag & INTEGER)
typeset("OPTARG", 0, INTEGER, 0, 0);
if (user_opt.optarg == NULL)
unset(voptarg, 0);
else
/* This can't fail (have cleared readonly/integer) */
setstr(voptarg, user_opt.optarg, KSH_RETURN_ERROR);
rv = 0;
vq = global(var);
/* Error message already printed (integer, readonly) */
if (!setstr(vq, buf, KSH_RETURN_ERROR))
rv = 1;
if (Flag(FEXPORT))
typeset(var, EXPORT, 0, 0, 0);
return optc < 0 ? 1 : rv;
}
int
c_bind(const char **wp)
{
int optc, rv = 0;
bool macro = false, list = false;
const char *cp;
char *up;
while ((optc = ksh_getopt(wp, &builtin_opt, "lm")) != -1)
switch (optc) {
case 'l':
list = true;
break;
case 'm':
macro = true;
break;
case '?':
return 1;
}
wp += builtin_opt.optind;
if (*wp == NULL) /* list all */
rv = x_bind(NULL, NULL, 0, list);
for (; *wp != NULL; wp++) {
if ((cp = cstrchr(*wp, '=')) == NULL)
up = NULL;
else {
strdupx(up, *wp, ATEMP);
up[cp++ - *wp] = '\0';
}
if (x_bind(up ? up : *wp, cp, macro, 0))
rv = 1;
afree(up, ATEMP);
}
return rv;
}
/* :, false and true */
int
c_label(const char **wp)
{
return wp[0][0] == 'f' ? 1 : 0;
}
int
c_shift(const char **wp)
{
struct block *l = e->loc;
int n;
mksh_ari_t val;
const char *arg;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
arg = wp[builtin_opt.optind];
if (arg) {
evaluate(arg, &val, KSH_UNWIND_ERROR, false);
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(const char **wp)
{
int i, optc;
const char *cp;
bool symbolic = false;
mode_t old_umask;
while ((optc = ksh_getopt(wp, &builtin_opt, "S")) != -1)
switch (optc) {
case 'S':
symbolic = true;
break;
case '?':
return 1;
}
cp = wp[builtin_opt.optind];
if (cp == NULL) {
old_umask = umask(0);
umask(old_umask);
if (symbolic) {
char buf[18], *p;
int j;
old_umask = ~old_umask;
p = buf;
for (i = 0; i < 3; i++) {
*p++ = "ugo"[i];
*p++ = '=';
for (j = 0; j < 3; j++)
if (old_umask & (1 << (8 - (3*i + j))))
*p++ = "rwx"[j];
*p++ = ',';
}
p[-1] = '\0';
shprintf("%s\n", buf);
} else
shprintf("%#3.3o\n", (unsigned int)old_umask);
} else {
mode_t new_umask;
if (ksh_isdigit(*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 && vstrchr("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 (!vstrchr("=+-", op = *cp))
break;
cp++;
new_val = 0;
while (*cp && vstrchr("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 (!vstrchr("=+-", *cp))
break;
}
if (*cp) {
bi_errorf("bad mask");
return 1;
}
new_umask = ~new_umask;
}
umask(new_umask);
}
return (0);
}
int
c_dot(const char **wp)
{
const char *file, *cp, **argv;
int argc, i, errcode;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return (1);
if ((cp = wp[builtin_opt.optind]) == NULL) {
bi_errorf("missing argument");
return (1);
}
if ((file = search(cp, path, R_OK, &errcode)) == NULL) {
bi_errorf("%s: %s", cp,
errcode ? strerror(errcode) : "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;
}
if ((i = include(file, argc, argv, 0)) < 0) {
/* should not happen */
bi_errorf("%s: %s", cp, strerror(errno));
return (1);
}
return (i);
}
int
c_wait(const char **wp)
{
int rv = 0, 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(const char **wp)
{
int c = 0, ecode = 0, fd = 0, optc;
bool expande = true, historyr = false, expanding;
const char *cp, *emsg;
struct shf *shf;
XString cs, xs = { NULL, NULL, 0, NULL};
struct tbl *vp;
char *ccp, *xp = NULL, *wpalloc = NULL;
static char REPLY[] = "REPLY";
while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != -1)
switch (optc) {
case 'p':
if ((fd = coproc_getfd(R_OK, &emsg)) < 0) {
bi_errorf("-p: %s", emsg);
return 1;
}
break;
case 'r':
expande = false;
break;
case 's':
historyr = true;
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 = cstrchr(*wp, '?')) != NULL) {
strdupx(wpalloc, *wp, ATEMP);
wpalloc[cp - *wp] = '\0';
*wp = wpalloc;
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 (historyr)
Xinit(xs, xp, 128, ATEMP);
expanding = false;
Xinit(cs, ccp, 128, ATEMP);
for (; *wp != NULL; wp++) {
for (ccp = Xstring(cs, ccp); ; ) {
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 (historyr) {
Xcheck(xs, xp);
Xput(xs, xp, c);
}
Xcheck(cs, ccp);
if (expanding) {
expanding = false;
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, ccp, c);
continue;
}
if (expande && c == '\\') {
expanding = true;
continue;
}
if (c == '\n' || c == EOF)
break;
if (ctype(c, C_IFS)) {
if (Xlength(cs, ccp) == 0 && ctype(c, C_IFSWS))
continue;
if (wp[1])
break;
}
Xput(cs, ccp, c);
}
/* strip trailing IFS white space from last variable */
if (!wp[1])
while (Xlength(cs, ccp) && ctype(ccp[-1], C_IFS) &&
ctype(ccp[-1], C_IFSWS))
ccp--;
Xput(cs, ccp, '\0');
vp = global(*wp);
/* Must be done before setting export. */
if (vp->flag & RDONLY) {
shf_flush(shf);
bi_errorf("%s is read only", *wp);
afree(wpalloc, ATEMP);
return 1;
}
if (Flag(FEXPORT))
typeset(*wp, EXPORT, 0, 0, 0);
if (!setstr(vp, Xstring(cs, ccp), KSH_RETURN_ERROR)) {
shf_flush(shf);
afree(wpalloc, ATEMP);
return 1;
}
}
shf_flush(shf);
if (historyr) {
Xput(xs, xp, '\0');
histsave(&source->line, Xstring(xs, xp), true, false);
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);
afree(wpalloc, ATEMP);
return ecode ? ecode : c == EOF;
}
int
c_eval(const char **wp)
{
struct source *s, *saves = source;
char savef;
int rv;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
s = pushs(SWORDS, ATEMP);
s->u.strv = wp + builtin_opt.optind;
/*
* 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;
savef = Flag(FERREXIT);
Flag(FERREXIT) = 0;
rv = shell(s, false);
Flag(FERREXIT) = savef;
source = saves;
afree(s, ATEMP);
return (rv);
}
int
c_trap(const char **wp)
{
int i;
const char *s;
Trap *p;
if (ksh_getopt(wp, &builtin_opt, null) == '?')
return 1;
wp += builtin_opt.optind;
if (*wp == NULL) {
for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
if (p->trap != NULL) {
shf_puts("trap -- ", shl_stdout);
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(const char **wp)
{
int n, how = LEXIT;
const 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 */
}
int
c_brkcont(const char **wp)
{
int n, quit;
struct env *ep, *last_ep = NULL;
const 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.
*/
if (last_ep)
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(const char **wp)
{
int argi, setargs;
struct block *l = e->loc;
const char **owp;
if (wp[1] == NULL) {
static const char *args [] = { "set", "-", NULL };
return c_typeset(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)
strdupx(*wp, *wp, &l->area);
l->argc = wp - owp - 1;
l->argv = alloc((l->argc + 2) * sizeof (char *), &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 subst_exstat;
}
int
c_unset(const char **wp)
{
const char *id;
int optc;
bool unset_var = true;
while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != -1)
switch (optc) {
case 'f':
unset_var = false;
break;
case 'v':
unset_var = true;
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&RDONLY)) {
bi_errorf("%s is read only", vp->name);
return (1);
}
unset(vp, vstrchr(id, '[') ? 1 : 0);
} else /* unset function */
define(id, NULL);
return (0);
}
static void
p_time(struct shf *shf, int posix, struct timeval *tv, int width,
const char *prefix, const char *suffix)
{
if (posix)
shf_fprintf(shf, "%s%*ld.%02d%s", prefix ? prefix : "",
width, (long)tv->tv_sec, (int)tv->tv_usec / 10000, suffix);
else
shf_fprintf(shf, "%s%*ldm%d.%02ds%s", prefix ? prefix : "",
width, (long)tv->tv_sec / 60, (int)tv->tv_sec % 60,
(int)tv->tv_usec / 10000, suffix);
}
int
c_times(const char **wp __unused)
{
struct rusage usage;
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");
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, volatile int *xerrok)
{
#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, tf = 0;
struct rusage ru0, ru1, cru0, cru1;
struct timeval usrtime, systime, tv0, tv1;
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);
rv = execute(t->left, f | XTIME, xerrok);
if (t->left->type == TCOM)
tf |= t->left->str[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, i, j;
Getopt opt;
ksh_getopt_reset(&opt, 0);
opt.optind = 0; /* start at the start */
while ((optc = ksh_getopt((const char **)wp, &opt, ":p")) != -1)
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(const char **wp __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 (but not sh), keep anything > 2 private */
if (!Flag(FPOSIX) && i > 2 && e->savefd[i])
fcntl(i, F_SETFD, FD_CLOEXEC);
}
e->savefd = NULL;
}
return 0;
}
#if HAVE_MKNOD
int
c_mknod(const char **wp)
{
int argc, optc, rv = 0;
bool ismkfifo = false;
const char **argv;
void *set = NULL;
mode_t mode = 0, oldmode = 0;
while ((optc = ksh_getopt(wp, &builtin_opt, "m:")) != -1) {
switch (optc) {
case 'm':
set = setmode(builtin_opt.optarg);
if (set == NULL) {
bi_errorf("invalid file mode");
return (1);
}
mode = getmode(set, DEFFILEMODE);
free(set);
break;
default:
goto c_mknod_usage;
}
}
argv = &wp[builtin_opt.optind];
if (argv[0] == NULL)
goto c_mknod_usage;
for (argc = 0; argv[argc]; argc++)
;
if (argc == 2 && argv[1][0] == 'p')
ismkfifo = true;
else if (argc != 4 || (argv[1][0] != 'b' && argv[1][0] != 'c'))
goto c_mknod_usage;
if (set != NULL)
oldmode = umask(0);
else
mode = DEFFILEMODE;
mode |= (argv[1][0] == 'b') ? S_IFBLK :
(argv[1][0] == 'c') ? S_IFCHR : 0;
if (!ismkfifo) {
unsigned long majnum, minnum;
dev_t dv;
char *c;
majnum = strtoul(argv[2], &c, 0);
if ((c == argv[2]) || (*c != '\0')) {
bi_errorf("non-numeric device major '%s'", argv[2]);
goto c_mknod_err;
}
minnum = strtoul(argv[3], &c, 0);
if ((c == argv[3]) || (*c != '\0')) {
bi_errorf("non-numeric device minor '%s'", argv[3]);
goto c_mknod_err;
}
dv = makedev(majnum, minnum);
if ((unsigned long)major(dv) != majnum) {
bi_errorf("device major too large: %lu", majnum);
goto c_mknod_err;
}
if ((unsigned long)minor(dv) != minnum) {
bi_errorf("device minor too large: %lu", minnum);
goto c_mknod_err;
}
if (mknod(argv[0], mode, dv))
goto c_mknod_failed;
} else if (mkfifo(argv[0], mode)) {
c_mknod_failed:
bi_errorf("%s: %s", *wp, strerror(errno));
c_mknod_err:
rv = 1;
}
if (set)
umask(oldmode);
return (rv);
c_mknod_usage:
#if 0
/* XXX doesnt help */
builtin_argv0 = NULL;
#endif
bi_errorf("usage: mknod [-m mode] name [b | c] major minor");
bi_errorf("usage: mknod [-m mode] name p");
return (1);
}
#endif
/* dummy function, special case in comexec() */
int
c_builtin(const char **wp __unused)
{
return 0;
}
/* test(1) accepts the following grammar:
oexpr ::= aexpr | aexpr "-o" oexpr ;
aexpr ::= nexpr | nexpr "-a" aexpr ;
nexpr ::= primary | "!" nexpr ;
primary ::= unary-operator operand
| operand binary-operator operand
| operand
| "(" oexpr ")"
;
unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
"-L"|"-h"|"-S"|"-H";
binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
"-nt"|"-ot"|"-ef"|
"<"|">" # rules used for [[ .. ]] expressions
;
operand ::= <any thing>
*/
#define T_ERR_EXIT 2 /* POSIX says > 1 for errors */
int
c_test(const char **wp)
{
int argc, res;
Test_env te;
te.flags = 0;
te.isa = ptest_isa;
te.getopnd = ptest_getopnd;
te.eval = test_eval;
te.error = ptest_error;
for (argc = 0; wp[argc]; argc++)
;
if (strcmp(wp[0], "[") == 0) {
if (strcmp(wp[--argc], "]") != 0) {
bi_errorf("missing ]");
return T_ERR_EXIT;
}
}
te.pos.wp = wp + 1;
te.wp_end = wp + argc;
/*
* Handle the special cases from POSIX.2, section 4.62.4.
* Implementation of all the rules isn't necessary since
* our parser does the right thing for the omitted steps.
*/
if (argc <= 5) {
const char **owp = wp;
int invert = 0;
Test_op op;
const char *opnd1, *opnd2;
while (--argc >= 0) {
if ((*te.isa)(&te, TM_END))
return !0;
if (argc == 3) {
opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
if ((op = (*te.isa)(&te, TM_BINOP))) {
opnd2 = (*te.getopnd)(&te, op, 1);
res = (*te.eval)(&te, op, opnd1,
opnd2, 1);
if (te.flags & TEF_ERROR)
return T_ERR_EXIT;
if (invert & 1)
res = !res;
return !res;
}
/* back up to opnd1 */
te.pos.wp--;
}
if (argc == 1) {
opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
if (strcmp(opnd1, "-t") == 0)
break;
res = (*te.eval)(&te, TO_STNZE, opnd1,
NULL, 1);
if (invert & 1)
res = !res;
return !res;
}
if ((*te.isa)(&te, TM_NOT)) {
invert++;
} else
break;
}
te.pos.wp = owp + 1;
}
return test_parse(&te);
}
/*
* Generic test routines.
*/
Test_op
test_isop(Test_meta meta, const char *s)
{
char sc1;
const struct t_op *tbl;
tbl = meta == TM_UNOP ? u_ops : b_ops;
if (*s) {
sc1 = s[1];
for (; tbl->op_text[0]; tbl++)
if (sc1 == tbl->op_text[1] && !strcmp(s, tbl->op_text))
return tbl->op_num;
}
return TO_NONOP;
}
int
test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
bool do_eval)
{
int i, s;
size_t k;
struct stat b1, b2;
mksh_ari_t v1, v2;
if (!do_eval)
return 0;
switch ((int)op) {
/*
* Unary Operators
*/
case TO_STNZE: /* -n */
return *opnd1 != '\0';
case TO_STZER: /* -z */
return *opnd1 == '\0';
case TO_OPTION: /* -o */
if ((i = *opnd1 == '!'))
opnd1++;
if ((k = option(opnd1)) == (size_t)-1)
k = 0;
else {
k = Flag(k);
if (i)
k = !k;
}
return k;
case TO_FILRD: /* -r */
return test_eaccess(opnd1, R_OK) == 0;
case TO_FILWR: /* -w */
return test_eaccess(opnd1, W_OK) == 0;
case TO_FILEX: /* -x */
return test_eaccess(opnd1, X_OK) == 0;
case TO_FILAXST: /* -a */
case TO_FILEXST: /* -e */
return stat(opnd1, &b1) == 0;
case TO_FILREG: /* -r */
return stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode);
case TO_FILID: /* -d */
return stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode);
case TO_FILCDEV: /* -c */
return stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode);
case TO_FILBDEV: /* -b */
return stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode);
case TO_FILFIFO: /* -p */
return stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode);
case TO_FILSYM: /* -h -L */
return lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode);
case TO_FILSOCK: /* -S */
return stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode);
case TO_FILCDF:/* -H HP context dependent files (directories) */
return 0;
case TO_FILSETU: /* -u */
return stat(opnd1, &b1) == 0 &&
(b1.st_mode & S_ISUID) == S_ISUID;
case TO_FILSETG: /* -g */
return stat(opnd1, &b1) == 0 &&
(b1.st_mode & S_ISGID) == S_ISGID;
case TO_FILSTCK: /* -k */
#ifdef S_ISVTX
return stat(opnd1, &b1) == 0 &&
(b1.st_mode & S_ISVTX) == S_ISVTX;
#else
return (0);
#endif
case TO_FILGZ: /* -s */
return stat(opnd1, &b1) == 0 && b1.st_size > 0L;
case TO_FILTT: /* -t */
if (opnd1 && !bi_getn(opnd1, &i)) {
te->flags |= TEF_ERROR;
i = 0;
} else
i = isatty(opnd1 ? i : 0);
return (i);
case TO_FILUID: /* -O */
return stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid;
case TO_FILGID: /* -G */
return stat(opnd1, &b1) == 0 && b1.st_gid == getegid();
/*
* Binary Operators
*/
case TO_STEQL: /* = */
if (te->flags & TEF_DBRACKET)
return gmatchx(opnd1, opnd2, false);
return strcmp(opnd1, opnd2) == 0;
case TO_STNEQ: /* != */
if (te->flags & TEF_DBRACKET)
return !gmatchx(opnd1, opnd2, false);
return strcmp(opnd1, opnd2) != 0;
case TO_STLT: /* < */
return strcmp(opnd1, opnd2) < 0;
case TO_STGT: /* > */
return strcmp(opnd1, opnd2) > 0;
case TO_INTEQ: /* -eq */
case TO_INTNE: /* -ne */
case TO_INTGE: /* -ge */
case TO_INTGT: /* -gt */
case TO_INTLE: /* -le */
case TO_INTLT: /* -lt */
if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR, false) ||
!evaluate(opnd2, &v2, KSH_RETURN_ERROR, false)) {
/* error already printed.. */
te->flags |= TEF_ERROR;
return 1;
}
switch ((int)op) {
case TO_INTEQ:
return (v1 == v2);
case TO_INTNE:
return (v1 != v2);
case TO_INTGE:
return (v1 >= v2);
case TO_INTGT:
return (v1 > v2);
case TO_INTLE:
return (v1 <= v2);
case TO_INTLT:
return (v1 < v2);
}
case TO_FILNT: /* -nt */
/* ksh88/ksh93 succeed if file2 can't be stated
* (subtly different from 'does not exist').
*/
return stat(opnd1, &b1) == 0 &&
(((s = stat(opnd2, &b2)) == 0 &&
b1.st_mtime > b2.st_mtime) || s < 0);
case TO_FILOT: /* -ot */
/* ksh88/ksh93 succeed if file1 can't be stated
* (subtly different from 'does not exist').
*/
return stat(opnd2, &b2) == 0 &&
(((s = stat(opnd1, &b1)) == 0 &&
b1.st_mtime < b2.st_mtime) || s < 0);
case TO_FILEQ: /* -ef */
return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 &&
b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino;
}
(*te->error)(te, 0, "internal error: unknown op");
return 1;
}
/* On most/all unixen, access() says everything is executable for root... */
static int
test_eaccess(const char *pathl, int mode)
{
int rv;
if ((rv = access(pathl, mode)) == 0 && ksheuid == 0 && (mode & X_OK)) {
struct stat statb;
if (stat(pathl, &statb) < 0)
rv = -1;
else if (S_ISDIR(statb.st_mode))
rv = 0;
else
rv = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) ?
0 : -1;
}
return rv;
}
int
test_parse(Test_env *te)
{
int rv;
rv = test_oexpr(te, 1);
if (!(te->flags & TEF_ERROR) && !(*te->isa)(te, TM_END))
(*te->error)(te, 0, "unexpected operator/operand");
return (te->flags & TEF_ERROR) ? T_ERR_EXIT : !rv;
}
static int
test_oexpr(Test_env *te, bool do_eval)
{
int rv;
if ((rv = test_aexpr(te, do_eval)))
do_eval = false;
if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_OR))
return test_oexpr(te, do_eval) || rv;
return rv;
}
static int
test_aexpr(Test_env *te, bool do_eval)
{
int rv;
if (!(rv = test_nexpr(te, do_eval)))
do_eval = false;
if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_AND))
return test_aexpr(te, do_eval) && rv;
return rv;
}
static int
test_nexpr(Test_env *te, bool do_eval)
{
if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_NOT))
return !test_nexpr(te, do_eval);
return test_primary(te, do_eval);
}
static int
test_primary(Test_env *te, bool do_eval)
{
const char *opnd1, *opnd2;
int rv;
Test_op op;
if (te->flags & TEF_ERROR)
return 0;
if ((*te->isa)(te, TM_OPAREN)) {
rv = test_oexpr(te, do_eval);
if (te->flags & TEF_ERROR)
return 0;
if (!(*te->isa)(te, TM_CPAREN)) {
(*te->error)(te, 0, "missing closing paren");
return 0;
}
return rv;
}
/*
* Binary should have precedence over unary in this case
* so that something like test \( -f = -f \) is accepted
*/
if ((te->flags & TEF_DBRACKET) || (&te->pos.wp[1] < te->wp_end &&
!test_isop(TM_BINOP, te->pos.wp[1]))) {
if ((op = (*te->isa)(te, TM_UNOP))) {
/* unary expression */
opnd1 = (*te->getopnd)(te, op, do_eval);
if (!opnd1) {
(*te->error)(te, -1, "missing argument");
return 0;
}
return (*te->eval)(te, op, opnd1, NULL, do_eval);
}
}
opnd1 = (*te->getopnd)(te, TO_NONOP, do_eval);
if (!opnd1) {
(*te->error)(te, 0, "expression expected");
return 0;
}
if ((op = (*te->isa)(te, TM_BINOP))) {
/* binary expression */
opnd2 = (*te->getopnd)(te, op, do_eval);
if (!opnd2) {
(*te->error)(te, -1, "missing second argument");
return 0;
}
return (*te->eval)(te, op, opnd1, opnd2, do_eval);
}
if (te->flags & TEF_DBRACKET) {
(*te->error)(te, -1, "missing expression operator");
return 0;
}
return (*te->eval)(te, TO_STNZE, opnd1, NULL, do_eval);
}
/*
* Plain test (test and [ .. ]) specific routines.
*/
/* Test if the current token is a whatever. Accepts the current token if
* it is. Returns 0 if it is not, non-zero if it is (in the case of
* TM_UNOP and TM_BINOP, the returned value is a Test_op).
*/
static int
ptest_isa(Test_env *te, Test_meta meta)
{
/* Order important - indexed by Test_meta values */
static const char *const tokens[] = {
"-o", "-a", "!", "(", ")"
};
int rv;
if (te->pos.wp >= te->wp_end)
return meta == TM_END;
if (meta == TM_UNOP || meta == TM_BINOP)
rv = test_isop(meta, *te->pos.wp);
else if (meta == TM_END)
rv = 0;
else
rv = strcmp(*te->pos.wp, tokens[(int) meta]) == 0;
/* Accept the token? */
if (rv)
te->pos.wp++;
return rv;
}
static const char *
ptest_getopnd(Test_env *te, Test_op op, bool do_eval __unused)
{
if (te->pos.wp >= te->wp_end)
return op == TO_FILTT ? "1" : NULL;
return *te->pos.wp++;
}
static void
ptest_error(Test_env *te, int ofs, const char *msg)
{
const char *op;
te->flags |= TEF_ERROR;
if ((op = te->pos.wp + ofs >= te->wp_end ? NULL : te->pos.wp[ofs]))
bi_errorf("%s: %s", op, msg);
else
bi_errorf("%s", msg);
}
#ifdef RLIM_INFINITY
#define SOFT 0x1
#define HARD 0x2
struct limits {
const char *name;
int resource; /* resource to get/set */
int factor; /* multiply by to get rlim_{cur,max} values */
char option;
};
static void print_ulimit(const struct limits *, int);
static int set_ulimit(const struct limits *, const char *, int);
#endif
int
c_ulimit(const char **wp)
{
#ifdef RLIM_INFINITY
static const struct limits limits[] = {
/* do not use options -H, -S or -a or change the order */
#ifdef RLIMIT_CPU
{ "time(cpu-seconds)", RLIMIT_CPU, 1, 't' },
#endif
#ifdef RLIMIT_FSIZE
{ "file(blocks)", RLIMIT_FSIZE, 512, 'f' },
#endif
#ifdef RLIMIT_CORE
{ "coredump(blocks)", RLIMIT_CORE, 512, 'c' },
#endif
#ifdef RLIMIT_DATA
{ "data(KiB)", RLIMIT_DATA, 1024, 'd' },
#endif
#ifdef RLIMIT_STACK
{ "stack(KiB)", RLIMIT_STACK, 1024, 's' },
#endif
#ifdef RLIMIT_MEMLOCK
{ "lockedmem(KiB)", RLIMIT_MEMLOCK, 1024, 'l' },
#endif
#ifdef RLIMIT_RSS
{ "memory(KiB)", RLIMIT_RSS, 1024, 'm' },
#endif
#ifdef RLIMIT_NOFILE
{ "nofiles(descriptors)", RLIMIT_NOFILE, 1, 'n' },
#endif
#ifdef RLIMIT_NPROC
{ "processes", RLIMIT_NPROC, 1, 'p' },
#endif
#ifdef RLIMIT_VMEM
{ "vmemory(KiB)", RLIMIT_VMEM, 1024, 'v' },
#endif
#ifdef RLIMIT_SWAP
{ "swap(KiB)", RLIMIT_SWAP, 1024, 'w' },
#endif
#ifdef RLIMIT_LOCKS
{ "flocks", RLIMIT_LOCKS, -1, 'L' },
#endif
#ifdef RLIMIT_TIME
{ "humantime(seconds)", RLIMIT_TIME, 1, 'T' },
#endif
{ NULL, 0, 0, 0 }
};
static char opts[3 + NELEM(limits)];
int how = SOFT | HARD, optc, what = 'f';
bool all = false;
const struct limits *l;
if (!opts[0]) {
/* build options string on first call - yuck */
char *p = opts;
*p++ = 'H'; *p++ = 'S'; *p++ = 'a';
for (l = limits; l->name; l++)
*p++ = l->option;
*p = '\0';
}
while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1)
switch (optc) {
case 'H':
how = HARD;
break;
case 'S':
how = SOFT;
break;
case 'a':
all = true;
break;
case '?':
bi_errorf("usage: ulimit [-acdfHLlmnpSsTtvw] [value]");
return (1);
default:
what = optc;
}
for (l = limits; l->name && l->option != what; l++)
;
if (!l->name) {
internal_warningf("ulimit: %c", what);
return (1);
}
if (wp[builtin_opt.optind]) {
if (all || wp[builtin_opt.optind + 1]) {
bi_errorf("too many arguments");
return (1);
}
return (set_ulimit(l, wp[builtin_opt.optind], how));
}
if (!all)
print_ulimit(l, how);
else for (l = limits; l->name; l++) {
shprintf("%-20s ", l->name);
print_ulimit(l, how);
}
#endif
return (0);
}
#ifdef RLIM_INFINITY
static int
set_ulimit(const struct limits *l, const char *v, int how)
{
rlim_t val = (rlim_t)0;
struct rlimit limit;
if (strcmp(v, "unlimited") == 0)
val = (rlim_t)RLIM_INFINITY;
else {
mksh_ari_t rval;
if (!evaluate(v, &rval, KSH_RETURN_ERROR, false))
return (1);
/*
* Avoid problems caused by typos that evaluate misses due
* to evaluating unset parameters to 0...
* If this causes problems, will have to add parameter to
* evaluate() to control if unset params are 0 or an error.
*/
if (!rval && !ksh_isdigit(v[0])) {
bi_errorf("invalid %s limit: %s", l->name, v);
return (1);
}
val = (rlim_t)((rlim_t)rval * l->factor);
}
if (getrlimit(l->resource, &limit) < 0) {
/* some cannot be read, e.g. Linux RLIMIT_LOCKS */
limit.rlim_cur = RLIM_INFINITY;
limit.rlim_max = RLIM_INFINITY;
}
if (how & SOFT)
limit.rlim_cur = val;
if (how & HARD)
limit.rlim_max = val;
if (!setrlimit(l->resource, &limit))
return (0);
if (errno == EPERM)
bi_errorf("%s exceeds allowable %s limit", v, l->name);
else
bi_errorf("bad %s limit: %s", l->name, strerror(errno));
return (1);
}
static void
print_ulimit(const struct limits *l, int how)
{
rlim_t val = (rlim_t)0;
struct rlimit limit;
if (getrlimit(l->resource, &limit)) {
shf_puts("unknown\n", shl_stdout);
return;
}
if (how & SOFT)
val = limit.rlim_cur;
else if (how & HARD)
val = limit.rlim_max;
if (val == RLIM_INFINITY)
shf_puts("unlimited\n", shl_stdout);
else
shprintf("%ld\n", (long)(val / l->factor));
}
#endif
int
c_rename(const char **wp)
{
int rv = 1;
if (wp == NULL /* argv */ ||
wp[0] == NULL /* name of builtin */ ||
wp[1] == NULL /* first argument */ ||
wp[2] == NULL /* second argument */ ||
wp[3] != NULL /* no further args please */)
bi_errorf(T_synerr);
else if ((rv = rename(wp[1], wp[2])) != 0) {
rv = errno;
bi_errorf("failed: %s", strerror(rv));
}
return (rv);
}
#if HAVE_REALPATH
int
c_realpath(const char **wp)
{
int rv = 1;
if (wp != NULL && wp[0] != NULL && wp[1] != NULL) {
if (strcmp(wp[1], "--")) {
if (wp[2] == NULL) {
wp += 1;
rv = 0;
}
} else {
if (wp[2] != NULL && wp[3] == NULL) {
wp += 2;
rv = 0;
}
}
}
if (rv)
bi_errorf(T_synerr);
else {
char *buf;
if (realpath(*wp, (buf = alloc(PATH_MAX, ATEMP))) == NULL) {
rv = errno;
bi_errorf("%s: %s", *wp, strerror(rv));
} else
shprintf("%s\n", buf);
afree(buf, ATEMP);
}
return (rv);
}
#endif