/* $NetBSD: c_ksh.c,v 1.7 2002/09/25 02:41:11 provos Exp $ */ /* * built-in Korn commands: c_* */ #include "sh.h" #include "ksh_stat.h" #include #ifdef __CYGWIN__ #include #endif /* __CYGWIN__ */ int c_cd(wp) char **wp; { int optc; int physical = Flag(FPHYSICAL); int cdnode; /* was a node from cdpath added in? */ int printpath = 0; /* print where we cd'd? */ int rval; struct tbl *pwd_s, *oldpwd_s; XString xs; char *xp; char *dir, *try, *pwd; int phys_path; char *cdpath; while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != EOF) switch (optc) { case 'L': physical = 0; break; case 'P': physical = 1; 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 */ dir = wp[0]; if (strcmp(dir, "-") == 0) { dir = str_val(oldpwd_s); if (dir == null) { bi_errorf("no OLDPWD"); return 1; } printpath++; } } 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; } /* substitue 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])) == (char *) 0) { 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 = 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++; } else { bi_errorf("too many arguments"); return 1; } Xinit(xs, xp, PATH, ATEMP); /* xp will have a bogus value after make_path() - set it to 0 * so that if it's used, it will cause a dump */ xp = (char *) 0; cdpath = str_val(global("CDPATH")); do { cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path); #ifdef S_ISLNK if (physical) rval = chdir(try = Xstring(xs, xp) + phys_path); else #endif /* S_ISLNK */ { simplify_path(Xstring(xs, xp)); rval = chdir(try = Xstring(xs, xp)); } } while (rval < 0 && cdpath != (char *) 0); if (rval < 0) { if (cdnode) bi_errorf("%s: bad directory", dir); else bi_errorf("%s - %s", try, strerror(errno)); 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 (!ISABSPATH(Xstring(xs, xp))) { #ifdef OS2 /* simplify_path() doesn't know about os/2's drive contexts, * so it can't set current_wd when changing to a:foo. * Handle this by calling getcwd()... */ pwd = ksh_get_wd((char *) 0, 0); #else /* OS2 */ pwd = (char *) 0; #endif /* OS2 */ } else #ifdef S_ISLNK if (!physical || !(pwd = get_phys_path(Xstring(xs, xp)))) #endif /* S_ISLNK */ pwd = Xstring(xs, xp); /* Set PWD */ if (pwd) { #ifdef __CYGWIN__ char ptmp[PATH]; /* larger than MAX_PATH */ cygwin_conv_to_full_posix_path(pwd, ptmp); #else /* __CYGWIN__ */ char *ptmp = pwd; #endif /* __CYGWIN__ */ 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); return 0; } int c_pwd(wp) char **wp; { int optc; int physical = Flag(FPHYSICAL); char *p; while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != EOF) switch (optc) { case 'L': physical = 0; break; case 'P': physical = 1; break; case '?': return 1; } wp += builtin_opt.optind; if (wp[0]) { bi_errorf("too many arguments"); return 1; } #ifdef S_ISLNK p = current_wd[0] ? (physical ? get_phys_path(current_wd) : current_wd) : (char *) 0; #else /* S_ISLNK */ p = current_wd[0] ? current_wd : (char *) 0; #endif /* S_ISLNK */ if (p && eaccess(p, R_OK) < 0) p = (char *) 0; if (!p) { p = ksh_get_wd((char *) 0, 0); if (!p) { bi_errorf("can't get current directory - %s", strerror(errno)); return 1; } } shprintf("%s\n", p); return 0; } int c_print(wp) 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 */ #define PO_FSLASH BIT(5) /* swap slash for backslash (for os2 ) */ int fd = 1; int flags = PO_EXPAND|PO_NL; char *s; const char *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 recognized if they appear * in arguments with no illegal options (ie, echo -nq * will print -nq). * Different from sysV echo since options are recognized, * different from BSD echo since escape sequences are enabled * by default. */ wp += 1; 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; #if OS2 const char *options = "Rnpfrsu,"; /* added f flag */ #else const char *options = "Rnprsu,"; #endif while ((optc = ksh_getopt(wp, &builtin_opt, options)) != EOF) switch (optc) { case 'R': /* fake BSD echo command */ flags |= PO_PMINUSMINUS; flags &= ~PO_EXPAND; options = "ne"; break; case 'e': flags |= PO_EXPAND; break; #ifdef OS2 case 'f': flags |= PO_FSLASH; break; #endif case 'n': flags &= ~PO_NL; break; #ifdef KSH case 'p': if ((fd = coproc_getfd(W_OK, &emsg)) < 0) { bi_errorf("-p: %s", emsg); return 1; } break; #endif /* KSH */ 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] && strcmp(wp[builtin_opt.optind], "-") == 0) builtin_opt.optind++; } else if (flags & PO_PMINUSMINUS) builtin_opt.optind--; wp += builtin_opt.optind; } Xinit(xs, xp, 128, ATEMP); while (*wp != NULL) { register int c; s = *wp; while ((c = *s++) != '\0') { Xcheck(xs, xp); #ifdef OS2 if ((flags & PO_FSLASH) && c == '\\') if (*s == '\\') *s++; else c = '/'; #endif /* OS2 */ 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 pcc's, * 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). Truely burnt. */ c = 0; for (i = 0; i < 3; i++) { if (*s >= '0' && *s <= '7') c = c*8 + *s++ - '0'; else break; } 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'); source->line++; histsave(source->line, Xstring(xs, xp), 1); Xfree(xs, xp); } else { int n, len = Xlength(xs, xp); #ifdef KSH int UNINITIALIZED(opipe); /* 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(); } #endif /* KSH */ for (s = Xstring(xs, xp); len > 0; ) { n = write(fd, s, len); if (n < 0) { #ifdef KSH if (flags & PO_COPROC) restore_pipe(opipe); #endif /* KSH */ if (errno == EINTR) { /* allow user to ^C out */ intrcheck(); #ifdef KSH if (flags & PO_COPROC) opipe = block_pipe(); #endif /* KSH */ continue; } #ifdef KSH /* This doesn't really make sense - could * break scripts (print -p generates * error message). *if (errno == EPIPE) * coproc_write_close(fd); */ #endif /* KSH */ return 1; } s += n; len -= n; } #ifdef KSH if (flags & PO_COPROC) restore_pipe(opipe); #endif /* KSH */ } return 0; } int c_whence(wp) char **wp; { struct tbl *tp; char *id; int pflag = 0, vflag = 0, Vflag = 0; int ret = 0; int optc; int iam_whence = wp[0][0] == 'w'; int fcflags; const char *options = iam_whence ? "pv" : "pvV"; while ((optc = ksh_getopt(wp, &builtin_opt, options)) != EOF) switch (optc) { case 'p': pflag = 1; break; case 'v': vflag = 1; break; case 'V': Vflag = 1; 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 || ret == 0) && (id = *wp++) != NULL) { tp = NULL; if ((iam_whence || vflag) && !pflag) tp = tsearch(&keywords, id, hash(id)); if (!tp && !pflag) { tp = tsearch(&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)) shprintf("%s", id); switch (tp->type) { case CKEYWD: if (vflag) shprintf(" is a reserved word"); 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) { shprintf(" is a"); if (tp->flag & EXPORT) shprintf("n exported"); if (tp->flag & TRACE) shprintf(" traced"); if (!(tp->flag & ISSET)) { shprintf(" undefined"); if (tp->u.fpath) shprintf(" (autoload from %s)", tp->u.fpath); } shprintf(" function"); } 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) { shprintf(" is "); if (tp->type == CTALIAS) shprintf( "a tracked %salias for ", (tp->flag & EXPORT) ? "exported " : null); } shprintf("%s", tp->val.s); } else { if (vflag) shprintf(" not found"); ret = 1; } break; default: shprintf("%s is *GOK*", id); break; } if (vflag || !ret) shprintf(newline); } return ret; } /* Deal with command -vV - command -p dealt with in comexec() */ int c_command(wp) 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(wp) char **wp; { struct block *l = e->loc; struct tbl *vp, **p; Tflag fset = 0, fclr = 0; int thing = 0, func = 0, local = 0; const char *options = "L#R#UZ#fi#lprtux"; /* see comment below */ char *fieldstr, *basestr; int field, base; int optc; Tflag flag; int pflag = 0; switch (**wp) { case 'e': /* export */ fset |= EXPORT; options = "p"; break; case 'r': /* readonly */ fset |= RDONLY; options = "p"; break; case 's': /* set */ /* called with 'typeset -' */ break; case 't': /* typeset */ local = 1; break; } fieldstr = basestr = (char *) 0; 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, options)) != EOF) { 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 = 1; break; case 'i': flag = INTEGER; basestr = builtin_opt.optarg; break; case 'l': flag = LCASEV; break; case 'p': /* posix export/readonly -p flag. * typset -p is the same as typeset (in pdksh); * here for compatibility with ksh93. */ pflag = 1; 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; int rval = 0; struct tbl *f; if (local && !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 ++rval: bogus */ rval = 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 rval; } /* list variables and attributes */ flag = fset | fclr; /* no difference at this point.. */ if (func) { for (l = e->loc; l; l = l->next) { for (p = tsort(&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 = tsort(&l->vars); (vp = *p++); ) { struct tbl *tvp; int any_set = 0; /* * 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 = 1; 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... */ shprintf("typeset "); if ((vp->flag&INTEGER)) shprintf("-i "); if ((vp->flag&EXPORT)) shprintf("-x "); if ((vp->flag&RDONLY)) shprintf("-r "); if ((vp->flag&TRACE)) shprintf("-t "); if ((vp->flag&LJUST)) shprintf("-L%d ", vp->u2.field); if ((vp->flag&RJUST)) shprintf("-R%d ", vp->u2.field); if ((vp->flag&ZEROFIL)) shprintf("-Z "); if ((vp->flag&LCASEV)) shprintf("-l "); if ((vp->flag&UCASEV_AL)) shprintf("-u "); if ((vp->flag&INT_U)) shprintf("-U "); shprintf("%s\n", vp->name); if (vp->flag&ARRAY) break; } else { if (pflag) shprintf("%s ", (flag & EXPORT) ? "export" : "readonly"); if ((vp->flag&ARRAY) && any_set) shprintf("%s[%d]", vp->name, vp->index); else shprintf("%s", vp->name); if (thing == '-' && (vp->flag&ISSET)) { char *s = str_val(vp); shprintf("="); /* at&t ksh can't have justified integers.. */ if ((vp->flag & (INTEGER|LJUST|RJUST)) == INTEGER) shprintf("%s", s); else print_value_quoted(s); } shprintf(newline); } /* Only report first `element' of an array with * no set elements. */ if (!any_set) break; } } } } return 0; } int c_alias(wp) char **wp; { struct table *t = &aliases; int rv = 0, rflag = 0, tflag, Uflag = 0, pflag = 0; int prefix = 0; Tflag xflag = 0; int optc; builtin_opt.flags |= GF_PLUSOPT; while ((optc = ksh_getopt(wp, &builtin_opt, "dprtUx")) != EOF) { prefix = builtin_opt.info & GI_PLUS ? '+' : '-'; switch (optc) { case 'd': t = &homedirs; break; case 'p': pflag = 1; break; case 'r': rflag = 1; break; case 't': t = &taliases; break; case 'U': /* kludge for tracked alias initialization * (don't do a path search, just make an entry) */ Uflag = 1; 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 *const args[] = { "unalias", "-ta", (const char *) 0 }; if (!tflag || *wp) { shprintf( "alias: -r flag can only be used with -t and without arguments\n"); return 1; } ksh_getopt_reset(&builtin_opt, GF_ERROR); return c_unalias((char **) args); } if (*wp == NULL) { struct tbl *ap, **p; for (p = tsort(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); } shprintf(newline); } } for (; *wp != NULL; wp++) { char *alias = *wp; char *val = strchr(alias, '='); char *newval; struct tbl *ap; int h; if (val) alias = str_nsave(alias, val++ - alias, ATEMP); h = hash(alias); if (val == NULL && !tflag && !xflag) { ap = tsearch(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); } shprintf(newline); } else { shprintf("%s alias not found\n", alias); rv = 1; } continue; } ap = tenter(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((void*)ap->val.s, APERM); } /* ignore values for -t (at&t ksh does this) */ newval = tflag ? search(alias, path, X_OK, (int *) 0) : val; if (newval) { ap->val.s = str_save(newval, APERM); ap->flag |= ALLOC|ISSET; } else ap->flag &= ~ISSET; } ap->flag |= DEFINED; if (prefix == '+') ap->flag &= ~xflag; else ap->flag |= xflag; if (val) afree(alias, ATEMP); } return rv; } int c_unalias(wp) char **wp; { register struct table *t = &aliases; register struct tbl *ap; int rv = 0, all = 0; int optc; while ((optc = ksh_getopt(wp, &builtin_opt, "adt")) != EOF) switch (optc) { case 'a': all = 1; break; case 'd': t = &homedirs; break; case 't': t = &taliases; break; case '?': return 1; } wp += builtin_opt.optind; for (; *wp != NULL; wp++) { ap = tsearch(t, *wp, hash(*wp)); if (ap == NULL) { rv = 1; /* POSIX */ continue; } if (ap->flag&ALLOC) { ap->flag &= ~(ALLOC|ISSET); afree((void*)ap->val.s, APERM); } ap->flag &= ~(DEFINED|ISSET|EXPORT); } if (all) { struct tstate ts; for (twalk(&ts, t); (ap = tnext(&ts)); ) { if (ap->flag&ALLOC) { ap->flag &= ~(ALLOC|ISSET); afree((void*)ap->val.s, APERM); } ap->flag &= ~(DEFINED|ISSET|EXPORT); } } return rv; } #ifdef KSH int c_let(wp) char **wp; { int rv = 1; long val; if (wp[1] == (char *) 0) /* at&t ksh does this */ bi_errorf("no arguments"); else for (wp++; *wp; wp++) if (!evaluate(*wp, &val, KSH_RETURN_ERROR)) { rv = 2; /* distinguish error from zero result */ break; } else rv = val == 0; return rv; } #endif /* KSH */ int c_jobs(wp) char **wp; { int optc; int flag = 0; int nflag = 0; int rv = 0; while ((optc = ksh_getopt(wp, &builtin_opt, "lpnz")) != EOF) 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((char *) 0, flag, nflag)) rv = 1; } else { for (; *wp; wp++) { if (j_jobs(*wp, flag, nflag)) rv = 1; } } return rv; } #ifdef JOBS int c_fgbg(wp) char **wp; { int bg = strcmp(*wp, "bg") == 0; int UNINITIALIZED(rv); 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); /* POSIX says fg shall return 0 (unless an error occurs). * at&t ksh returns the exit value of the job... */ return (bg || Flag(FPOSIX)) ? 0 : rv; } #endif struct kill_info { int num_width; int name_width; }; static char *kill_fmt_entry ARGS((void *arg, int i, char *buf, int buflen)); /* format a single kill item */ static char * kill_fmt_entry(arg, i, buf, buflen) void *arg; int i; char *buf; int buflen; { struct kill_info *ki = (struct kill_info *) arg; i++; if (sigtraps[i].name) shf_snprintf(buf, buflen, "%*d %*s %s", ki->num_width, i, ki->name_width, sigtraps[i].name, sigtraps[i].mess); else shf_snprintf(buf, buflen, "%*d %*d %s", ki->num_width, i, ki->name_width, sigtraps[i].signal, sigtraps[i].mess); return buf; } int c_kill(wp) char **wp; { Trap *t = (Trap *) 0; char *p; int lflag = 0; int i, n, rv, sig; /* assume old style options if -digits or -UPPERCASE */ if ((p = wp[1]) && *p == '-' && (digit(p[1]) || 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:")) != EOF) switch (optc) { case 'l': lflag = 1; break; case 's': if (!(t = gettrap(builtin_opt.optarg, TRUE))) { bi_errorf("bad signal `%s'", builtin_opt.optarg); return 1; } case '?': return 1; } i = builtin_opt.optind; } if ((lflag && t) || (!wp[i] && !lflag)) { shf_fprintf(shl_out, "Usage: kill [ -s signame | -signum | -signame ] {pid|job}...\n\ kill -l [exit_status]\n" ); bi_errorf(null); return 1; } if (lflag) { if (wp[i]) { for (; wp[i]; i++) { if (!bi_getn(wp[i], &n)) return 1; if (n > 128 && n < 128 + SIGNALS) n -= 128; if (n > 0 && n < SIGNALS && sigtraps[n].name) shprintf("%s\n", sigtraps[n].name); else shprintf("%d\n", n); } } else if (Flag(FPOSIX)) { p = null; for (i = 1; i < SIGNALS; i++, p = space) if (sigtraps[i].name) shprintf("%s%s", p, sigtraps[i].name); shprintf(newline); } else { int w, i; int mess_width; struct kill_info ki; for (i = SIGNALS, ki.num_width = 1; i >= 10; i /= 10) ki.num_width++; ki.name_width = mess_width = 0; for (i = 0; i < SIGNALS; i++) { w = sigtraps[i].name ? strlen(sigtraps[i].name) : ki.num_width; if (w > ki.name_width) ki.name_width = w; w = strlen(sigtraps[i].mess); if (w > mess_width) mess_width = w; } print_columns(shl_stdout, SIGNALS - 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(val) int val; { if (val >= 1) { ksh_getopt_reset(&user_opt, GF_NONAME | (Flag(FPOSIX) ? 0 : GF_PLUSOPT)); user_opt.optind = user_opt.uoptind = val; } } int c_getopts(wp) char **wp; { int argc; const char *options; const char *var; int optc; int ret; char buf[3]; struct tbl *vq, *voptarg; if (ksh_getopt(wp, &builtin_opt, null) == '?') return 1; wp += builtin_opt.optind; options = *wp++; if (!options) { 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 == (struct block *) 0) { internal_errorf(0, "c_getopts: no argv"); return 1; } /* Which arguments are we parsing... */ if (*wp == (char *) 0) 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 = (char *) 0; optc = ksh_getopt(wp, &user_opt, options); 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 == (char *) 0) unset(voptarg, 0); else /* This can't fail (have cleared readonly/integer) */ setstr(voptarg, user_opt.optarg, KSH_RETURN_ERROR); ret = 0; vq = global(var); /* Error message already printed (integer, readonly) */ if (!setstr(vq, buf, KSH_RETURN_ERROR)) ret = 1; if (Flag(FEXPORT)) typeset(var, EXPORT, 0, 0, 0); return optc < 0 ? 1 : ret; } #ifdef EMACS int c_bind(wp) char **wp; { int rv = 0, macro = 0, list = 0; register char *cp; int optc; while ((optc = ksh_getopt(wp, &builtin_opt, "lm")) != EOF) switch (optc) { case 'l': list = 1; break; case 'm': macro = 1; break; case '?': return 1; } wp += builtin_opt.optind; if (*wp == NULL) /* list all */ rv = x_bind((char*)NULL, (char*)NULL, 0, list); for (; *wp != NULL; wp++) { cp = strchr(*wp, '='); if (cp != NULL) *cp++ = '\0'; if (x_bind(*wp, cp, macro, 0)) rv = 1; } return rv; } #endif /* 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 kshbuiltins [] = { {"+alias", c_alias}, /* no =: at&t manual wrong */ {"+cd", c_cd}, {"+command", c_command}, {"echo", c_print}, {"*=export", c_typeset}, #ifdef HISTORY {"+fc", c_fc}, #endif /* HISTORY */ {"+getopts", c_getopts}, {"+jobs", c_jobs}, {"+kill", c_kill}, #ifdef KSH {"let", c_let}, #endif /* KSH */ {"print", c_print}, {"pwd", c_pwd}, {"*=readonly", c_typeset}, {"=typeset", c_typeset}, {"+unalias", c_unalias}, {"whence", c_whence}, #ifdef JOBS {"+bg", c_fgbg}, {"+fg", c_fgbg}, #endif #ifdef EMACS {"bind", c_bind}, #endif {NULL, NULL} };