xref: /openbsd-src/bin/ksh/c_ksh.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: c_ksh.c,v 1.50 2016/03/21 13:35:00 tb Exp $	*/
2 
3 /*
4  * built-in Korn commands: c_*
5  */
6 
7 #include <sys/stat.h>
8 
9 #include <ctype.h>
10 #include <errno.h>
11 #include <string.h>
12 #include <unistd.h>
13 
14 #include "sh.h"
15 
16 int
17 c_cd(char **wp)
18 {
19 	int optc;
20 	int physical = Flag(FPHYSICAL);
21 	int cdnode;			/* was a node from cdpath added in? */
22 	int printpath = 0;		/* print where we cd'd? */
23 	int rval;
24 	struct tbl *pwd_s, *oldpwd_s;
25 	XString xs;
26 	char *xp;
27 	char *dir, *try, *pwd;
28 	int phys_path;
29 	char *cdpath;
30 	char *fdir = NULL;
31 
32 	while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
33 		switch (optc) {
34 		case 'L':
35 			physical = 0;
36 			break;
37 		case 'P':
38 			physical = 1;
39 			break;
40 		case '?':
41 			return 1;
42 		}
43 	wp += builtin_opt.optind;
44 
45 	if (Flag(FRESTRICTED)) {
46 		bi_errorf("restricted shell - can't cd");
47 		return 1;
48 	}
49 
50 	pwd_s = global("PWD");
51 	oldpwd_s = global("OLDPWD");
52 
53 	if (!wp[0]) {
54 		/* No arguments - go home */
55 		if ((dir = str_val(global("HOME"))) == null) {
56 			bi_errorf("no home directory (HOME not set)");
57 			return 1;
58 		}
59 	} else if (!wp[1]) {
60 		/* One argument: - or dir */
61 		dir = wp[0];
62 		if (strcmp(dir, "-") == 0) {
63 			dir = str_val(oldpwd_s);
64 			if (dir == null) {
65 				bi_errorf("no OLDPWD");
66 				return 1;
67 			}
68 			printpath++;
69 		}
70 	} else if (!wp[2]) {
71 		/* Two arguments - substitute arg1 in PWD for arg2 */
72 		int ilen, olen, nlen, elen;
73 		char *cp;
74 
75 		if (!current_wd[0]) {
76 			bi_errorf("don't know current directory");
77 			return 1;
78 		}
79 		/* substitute arg1 for arg2 in current path.
80 		 * if the first substitution fails because the cd fails
81 		 * we could try to find another substitution. For now
82 		 * we don't
83 		 */
84 		if ((cp = strstr(current_wd, wp[0])) == NULL) {
85 			bi_errorf("bad substitution");
86 			return 1;
87 		}
88 		ilen = cp - current_wd;
89 		olen = strlen(wp[0]);
90 		nlen = strlen(wp[1]);
91 		elen = strlen(current_wd + ilen + olen) + 1;
92 		fdir = dir = alloc(ilen + nlen + elen, ATEMP);
93 		memcpy(dir, current_wd, ilen);
94 		memcpy(dir + ilen, wp[1], nlen);
95 		memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen);
96 		printpath++;
97 	} else {
98 		bi_errorf("too many arguments");
99 		return 1;
100 	}
101 
102 	Xinit(xs, xp, PATH, ATEMP);
103 	/* xp will have a bogus value after make_path() - set it to 0
104 	 * so that if it's used, it will cause a dump
105 	 */
106 	xp = NULL;
107 
108 	cdpath = str_val(global("CDPATH"));
109 	do {
110 		cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path);
111 		if (physical)
112 			rval = chdir(try = Xstring(xs, xp) + phys_path);
113 		else {
114 			simplify_path(Xstring(xs, xp));
115 			rval = chdir(try = Xstring(xs, xp));
116 		}
117 	} while (rval < 0 && cdpath != NULL);
118 
119 	if (rval < 0) {
120 		if (cdnode)
121 			bi_errorf("%s: bad directory", dir);
122 		else
123 			bi_errorf("%s - %s", try, strerror(errno));
124 		afree(fdir, ATEMP);
125 		return 1;
126 	}
127 
128 	/* Clear out tracked aliases with relative paths */
129 	flushcom(0);
130 
131 	/* Set OLDPWD (note: unsetting OLDPWD does not disable this
132 	 * setting in at&t ksh)
133 	 */
134 	if (current_wd[0])
135 		/* Ignore failure (happens if readonly or integer) */
136 		setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR);
137 
138 	if (Xstring(xs, xp)[0] != '/') {
139 		pwd = NULL;
140 	} else
141 	if (!physical || !(pwd = get_phys_path(Xstring(xs, xp))))
142 		pwd = Xstring(xs, xp);
143 
144 	/* Set PWD */
145 	if (pwd) {
146 		char *ptmp = pwd;
147 		set_current_wd(ptmp);
148 		/* Ignore failure (happens if readonly or integer) */
149 		setstr(pwd_s, ptmp, KSH_RETURN_ERROR);
150 	} else {
151 		set_current_wd(null);
152 		pwd = Xstring(xs, xp);
153 		/* XXX unset $PWD? */
154 	}
155 	if (printpath || cdnode)
156 		shprintf("%s\n", pwd);
157 
158 	afree(fdir, ATEMP);
159 
160 	return 0;
161 }
162 
163 int
164 c_pwd(char **wp)
165 {
166 	int optc;
167 	int physical = Flag(FPHYSICAL);
168 	char *p, *freep = NULL;
169 
170 	while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
171 		switch (optc) {
172 		case 'L':
173 			physical = 0;
174 			break;
175 		case 'P':
176 			physical = 1;
177 			break;
178 		case '?':
179 			return 1;
180 		}
181 	wp += builtin_opt.optind;
182 
183 	if (wp[0]) {
184 		bi_errorf("too many arguments");
185 		return 1;
186 	}
187 	p = current_wd[0] ? (physical ? get_phys_path(current_wd) : current_wd) :
188 	    NULL;
189 	if (p && access(p, R_OK) < 0)
190 		p = NULL;
191 	if (!p) {
192 		freep = p = ksh_get_wd(NULL, 0);
193 		if (!p) {
194 			bi_errorf("can't get current directory - %s",
195 			    strerror(errno));
196 			return 1;
197 		}
198 	}
199 	shprintf("%s\n", p);
200 	afree(freep, ATEMP);
201 	return 0;
202 }
203 
204 int
205 c_print(char **wp)
206 {
207 #define PO_NL		BIT(0)	/* print newline */
208 #define PO_EXPAND	BIT(1)	/* expand backslash sequences */
209 #define PO_PMINUSMINUS	BIT(2)	/* print a -- argument */
210 #define PO_HIST		BIT(3)	/* print to history instead of stdout */
211 #define PO_COPROC	BIT(4)	/* printing to coprocess: block SIGPIPE */
212 	int fd = 1;
213 	int flags = PO_EXPAND|PO_NL;
214 	char *s;
215 	const char *emsg;
216 	XString xs;
217 	char *xp;
218 
219 	if (wp[0][0] == 'e') {	/* echo command */
220 		int nflags = flags;
221 
222 		/* A compromise between sysV and BSD echo commands:
223 		 * escape sequences are enabled by default, and
224 		 * -n, -e and -E are recognized if they appear
225 		 * in arguments with no illegal options (ie, echo -nq
226 		 * will print -nq).
227 		 * Different from sysV echo since options are recognized,
228 		 * different from BSD echo since escape sequences are enabled
229 		 * by default.
230 		 */
231 		wp += 1;
232 		if (Flag(FPOSIX)) {
233 			if (*wp && strcmp(*wp, "-n") == 0) {
234 				flags &= ~PO_NL;
235 				wp++;
236 			}
237 		} else {
238 			while ((s = *wp) && *s == '-' && s[1]) {
239 				while (*++s)
240 					if (*s == 'n')
241 						nflags &= ~PO_NL;
242 					else if (*s == 'e')
243 						nflags |= PO_EXPAND;
244 					else if (*s == 'E')
245 						nflags &= ~PO_EXPAND;
246 					else
247 						/* bad option: don't use
248 						 * nflags, print argument
249 						 */
250 						break;
251 				if (*s)
252 					break;
253 				wp++;
254 				flags = nflags;
255 			}
256 		}
257 	} else {
258 		int optc;
259 		const char *options = "Rnprsu,";
260 		while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1)
261 			switch (optc) {
262 			case 'R': /* fake BSD echo command */
263 				flags |= PO_PMINUSMINUS;
264 				flags &= ~PO_EXPAND;
265 				options = "ne";
266 				break;
267 			case 'e':
268 				flags |= PO_EXPAND;
269 				break;
270 			case 'n':
271 				flags &= ~PO_NL;
272 				break;
273 			case 'p':
274 				if ((fd = coproc_getfd(W_OK, &emsg)) < 0) {
275 					bi_errorf("-p: %s", emsg);
276 					return 1;
277 				}
278 				break;
279 			case 'r':
280 				flags &= ~PO_EXPAND;
281 				break;
282 			case 's':
283 				flags |= PO_HIST;
284 				break;
285 			case 'u':
286 				if (!*(s = builtin_opt.optarg))
287 					fd = 0;
288 				else if ((fd = check_fd(s, W_OK, &emsg)) < 0) {
289 					bi_errorf("-u: %s: %s", s, emsg);
290 					return 1;
291 				}
292 				break;
293 			case '?':
294 				return 1;
295 			}
296 		if (!(builtin_opt.info & GI_MINUSMINUS)) {
297 			/* treat a lone - like -- */
298 			if (wp[builtin_opt.optind] &&
299 			    strcmp(wp[builtin_opt.optind], "-") == 0)
300 				builtin_opt.optind++;
301 		} else if (flags & PO_PMINUSMINUS)
302 			builtin_opt.optind--;
303 		wp += builtin_opt.optind;
304 	}
305 
306 	Xinit(xs, xp, 128, ATEMP);
307 
308 	while (*wp != NULL) {
309 		int c;
310 		s = *wp;
311 		while ((c = *s++) != '\0') {
312 			Xcheck(xs, xp);
313 			if ((flags & PO_EXPAND) && c == '\\') {
314 				int i;
315 
316 				switch ((c = *s++)) {
317 				/* Oddly enough, \007 seems more portable than
318 				 * \a (due to HP-UX cc, Ultrix cc, old pcc's,
319 				 * etc.).
320 				 */
321 				case 'a': c = '\007'; break;
322 				case 'b': c = '\b'; break;
323 				case 'c': flags &= ~PO_NL;
324 					  continue; /* AT&T brain damage */
325 				case 'f': c = '\f'; break;
326 				case 'n': c = '\n'; break;
327 				case 'r': c = '\r'; break;
328 				case 't': c = '\t'; break;
329 				case 'v': c = 0x0B; break;
330 				case '0':
331 					/* Look for an octal number: can have
332 					 * three digits (not counting the
333 					 * leading 0).  Truly burnt.
334 					 */
335 					c = 0;
336 					for (i = 0; i < 3; i++) {
337 						if (*s >= '0' && *s <= '7')
338 							c = c*8 + *s++ - '0';
339 						else
340 							break;
341 					}
342 					break;
343 				case '\0': s--; c = '\\'; break;
344 				case '\\': break;
345 				default:
346 					Xput(xs, xp, '\\');
347 				}
348 			}
349 			Xput(xs, xp, c);
350 		}
351 		if (*++wp != NULL)
352 			Xput(xs, xp, ' ');
353 	}
354 	if (flags & PO_NL)
355 		Xput(xs, xp, '\n');
356 
357 	if (flags & PO_HIST) {
358 		Xput(xs, xp, '\0');
359 		source->line++;
360 		histsave(source->line, Xstring(xs, xp), 1);
361 		Xfree(xs, xp);
362 	} else {
363 		int n, len = Xlength(xs, xp);
364 		int opipe = 0;
365 
366 		/* Ensure we aren't killed by a SIGPIPE while writing to
367 		 * a coprocess.  at&t ksh doesn't seem to do this (seems
368 		 * to just check that the co-process is alive, which is
369 		 * not enough).
370 		 */
371 		if (coproc.write >= 0 && coproc.write == fd) {
372 			flags |= PO_COPROC;
373 			opipe = block_pipe();
374 		}
375 		for (s = Xstring(xs, xp); len > 0; ) {
376 			n = write(fd, s, len);
377 			if (n < 0) {
378 				if (flags & PO_COPROC)
379 					restore_pipe(opipe);
380 				if (errno == EINTR) {
381 					/* allow user to ^C out */
382 					intrcheck();
383 					if (flags & PO_COPROC)
384 						opipe = block_pipe();
385 					continue;
386 				}
387 				/* This doesn't really make sense - could
388 				 * break scripts (print -p generates
389 				 * error message).
390 				*if (errno == EPIPE)
391 				*	coproc_write_close(fd);
392 				 */
393 				return 1;
394 			}
395 			s += n;
396 			len -= n;
397 		}
398 		if (flags & PO_COPROC)
399 			restore_pipe(opipe);
400 	}
401 
402 	return 0;
403 }
404 
405 int
406 c_whence(char **wp)
407 {
408 	struct tbl *tp;
409 	char *id;
410 	int pflag = 0, vflag = 0, Vflag = 0;
411 	int ret = 0;
412 	int optc;
413 	int iam_whence = wp[0][0] == 'w';
414 	int fcflags;
415 	const char *options = iam_whence ? "pv" : "pvV";
416 
417 	while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1)
418 		switch (optc) {
419 		case 'p':
420 			pflag = 1;
421 			break;
422 		case 'v':
423 			vflag = 1;
424 			break;
425 		case 'V':
426 			Vflag = 1;
427 			break;
428 		case '?':
429 			return 1;
430 		}
431 	wp += builtin_opt.optind;
432 
433 
434 	fcflags = FC_BI | FC_PATH | FC_FUNC;
435 	if (!iam_whence) {
436 		/* Note that -p on its own is dealt with in comexec() */
437 		if (pflag)
438 			fcflags |= FC_DEFPATH;
439 		/* Convert command options to whence options.  Note that
440 		 * command -pV and command -pv use a different path search
441 		 * than whence -v or whence -pv.  This should be considered
442 		 * a feature.
443 		 */
444 		vflag = Vflag;
445 	} else if (pflag)
446 		fcflags &= ~(FC_BI | FC_FUNC);
447 
448 	while ((vflag || ret == 0) && (id = *wp++) != NULL) {
449 		tp = NULL;
450 		if (!iam_whence || !pflag)
451 			tp = ktsearch(&keywords, id, hash(id));
452 		if (!tp && (!iam_whence || !pflag)) {
453 			tp = ktsearch(&aliases, id, hash(id));
454 			if (tp && !(tp->flag & ISSET))
455 				tp = NULL;
456 		}
457 		if (!tp)
458 			tp = findcom(id, fcflags);
459 		if (vflag || (tp->type != CALIAS && tp->type != CEXEC &&
460 		    tp->type != CTALIAS))
461 			shprintf("%s", id);
462 		switch (tp->type) {
463 		case CKEYWD:
464 			if (vflag)
465 				shprintf(" is a reserved word");
466 			break;
467 		case CALIAS:
468 			if (vflag)
469 				shprintf(" is an %salias for ",
470 				    (tp->flag & EXPORT) ? "exported " : "");
471 			if (!iam_whence && !vflag)
472 				shprintf("alias %s=", id);
473 			print_value_quoted(tp->val.s);
474 			break;
475 		case CFUNC:
476 			if (vflag) {
477 				shprintf(" is a");
478 				if (tp->flag & EXPORT)
479 					shprintf("n exported");
480 				if (tp->flag & TRACE)
481 					shprintf(" traced");
482 				if (!(tp->flag & ISSET)) {
483 					shprintf(" undefined");
484 					if (tp->u.fpath)
485 						shprintf(" (autoload from %s)",
486 						    tp->u.fpath);
487 				}
488 				shprintf(" function");
489 			}
490 			break;
491 		case CSHELL:
492 			if (vflag)
493 				shprintf(" is a%s shell builtin",
494 				    (tp->flag & SPEC_BI) ? " special" : "");
495 			break;
496 		case CTALIAS:
497 		case CEXEC:
498 			if (tp->flag & ISSET) {
499 				if (vflag) {
500 					shprintf(" is ");
501 					if (tp->type == CTALIAS)
502 						shprintf("a tracked %salias for ",
503 						    (tp->flag & EXPORT) ?
504 						    "exported " : "");
505 				}
506 				shprintf("%s", tp->val.s);
507 			} else {
508 				if (vflag)
509 					shprintf(" not found");
510 				ret = 1;
511 			}
512 			break;
513 		default:
514 			shprintf("%s is *GOK*", id);
515 			break;
516 		}
517 		if (vflag || !ret)
518 			shprintf("\n");
519 	}
520 	return ret;
521 }
522 
523 /* Deal with command -vV - command -p dealt with in comexec() */
524 int
525 c_command(char **wp)
526 {
527 	/* Let c_whence do the work.  Note that c_command() must be
528 	 * a distinct function from c_whence() (tested in comexec()).
529 	 */
530 	return c_whence(wp);
531 }
532 
533 /* typeset, export, and readonly */
534 int
535 c_typeset(char **wp)
536 {
537 	struct block *l;
538 	struct tbl *vp, **p;
539 	int fset = 0, fclr = 0, thing = 0, func = 0, local = 0, pflag = 0;
540 	const char *options = "L#R#UZ#fi#lprtux";	/* see comment below */
541 	char *fieldstr, *basestr;
542 	int field, base, optc, flag;
543 
544 	switch (**wp) {
545 	case 'e':		/* export */
546 		fset |= EXPORT;
547 		options = "p";
548 		break;
549 	case 'r':		/* readonly */
550 		fset |= RDONLY;
551 		options = "p";
552 		break;
553 	case 's':		/* set */
554 		/* called with 'typeset -' */
555 		break;
556 	case 't':		/* typeset */
557 		local = 1;
558 		break;
559 	}
560 
561 	fieldstr = basestr = NULL;
562 	builtin_opt.flags |= GF_PLUSOPT;
563 	/* at&t ksh seems to have 0-9 as options, which are multiplied
564 	 * to get a number that is used with -L, -R, -Z or -i (eg, -1R2
565 	 * sets right justify in a field of 12).  This allows options
566 	 * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and
567 	 * does not allow the number to be specified as a separate argument
568 	 * Here, the number must follow the RLZi option, but is optional
569 	 * (see the # kludge in ksh_getopt()).
570 	 */
571 	while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1) {
572 		flag = 0;
573 		switch (optc) {
574 		case 'L':
575 			flag = LJUST;
576 			fieldstr = builtin_opt.optarg;
577 			break;
578 		case 'R':
579 			flag = RJUST;
580 			fieldstr = builtin_opt.optarg;
581 			break;
582 		case 'U':
583 			/* at&t ksh uses u, but this conflicts with
584 			 * upper/lower case.  If this option is changed,
585 			 * need to change the -U below as well
586 			 */
587 			flag = INT_U;
588 			break;
589 		case 'Z':
590 			flag = ZEROFIL;
591 			fieldstr = builtin_opt.optarg;
592 			break;
593 		case 'f':
594 			func = 1;
595 			break;
596 		case 'i':
597 			flag = INTEGER;
598 			basestr = builtin_opt.optarg;
599 			break;
600 		case 'l':
601 			flag = LCASEV;
602 			break;
603 		case 'p':
604 			/* posix export/readonly -p flag.
605 			 * typeset -p is the same as typeset (in pdksh);
606 			 * here for compatibility with ksh93.
607 			 */
608 			pflag = 1;
609 			break;
610 		case 'r':
611 			flag = RDONLY;
612 			break;
613 		case 't':
614 			flag = TRACE;
615 			break;
616 		case 'u':
617 			flag = UCASEV_AL;	/* upper case / autoload */
618 			break;
619 		case 'x':
620 			flag = EXPORT;
621 			break;
622 		case '?':
623 			return 1;
624 		}
625 		if (builtin_opt.info & GI_PLUS) {
626 			fclr |= flag;
627 			fset &= ~flag;
628 			thing = '+';
629 		} else {
630 			fset |= flag;
631 			fclr &= ~flag;
632 			thing = '-';
633 		}
634 	}
635 
636 	field = 0;
637 	if (fieldstr && !bi_getn(fieldstr, &field))
638 		return 1;
639 	base = 0;
640 	if (basestr && !bi_getn(basestr, &base))
641 		return 1;
642 
643 	if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] &&
644 	    (wp[builtin_opt.optind][0] == '-' ||
645 	    wp[builtin_opt.optind][0] == '+') &&
646 	    wp[builtin_opt.optind][1] == '\0') {
647 		thing = wp[builtin_opt.optind][0];
648 		builtin_opt.optind++;
649 	}
650 
651 	if (func && ((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT))) {
652 		bi_errorf("only -t, -u and -x options may be used with -f");
653 		return 1;
654 	}
655 	if (wp[builtin_opt.optind]) {
656 		/* Take care of exclusions.
657 		 * At this point, flags in fset are cleared in fclr and vise
658 		 * versa.  This property should be preserved.
659 		 */
660 		if (fset & LCASEV)	/* LCASEV has priority over UCASEV_AL */
661 			fset &= ~UCASEV_AL;
662 		if (fset & LJUST)	/* LJUST has priority over RJUST */
663 			fset &= ~RJUST;
664 		if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */
665 			fset |= RJUST;
666 			fclr &= ~RJUST;
667 		}
668 		/* Setting these attributes clears the others, unless they
669 		 * are also set in this command
670 		 */
671 		if (fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV |
672 		    INTEGER | INT_U | INT_L))
673 			fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL |
674 			    LCASEV | INTEGER | INT_U | INT_L);
675 	}
676 
677 	/* set variables and attributes */
678 	if (wp[builtin_opt.optind]) {
679 		int i;
680 		int rval = 0;
681 		struct tbl *f;
682 
683 		if (local && !func)
684 			fset |= LOCAL;
685 		for (i = builtin_opt.optind; wp[i]; i++) {
686 			if (func) {
687 				f = findfunc(wp[i], hash(wp[i]),
688 				    (fset&UCASEV_AL) ? true : false);
689 				if (!f) {
690 					/* at&t ksh does ++rval: bogus */
691 					rval = 1;
692 					continue;
693 				}
694 				if (fset | fclr) {
695 					f->flag |= fset;
696 					f->flag &= ~fclr;
697 				} else
698 					fptreef(shl_stdout, 0,
699 					    f->flag & FKSH ?
700 					    "function %s %T\n" :
701 					    "%s() %T\n", wp[i], f->val.t);
702 			} else if (!typeset(wp[i], fset, fclr, field, base)) {
703 				bi_errorf("%s: not identifier", wp[i]);
704 				return 1;
705 			}
706 		}
707 		return rval;
708 	}
709 
710 	/* list variables and attributes */
711 	flag = fset | fclr; /* no difference at this point.. */
712 	if (func) {
713 		for (l = genv->loc; l; l = l->next) {
714 			for (p = ktsort(&l->funs); (vp = *p++); ) {
715 				if (flag && (vp->flag & flag) == 0)
716 					continue;
717 				if (thing == '-')
718 					fptreef(shl_stdout, 0, vp->flag & FKSH ?
719 					    "function %s %T\n" : "%s() %T\n",
720 					    vp->name, vp->val.t);
721 				else
722 					shprintf("%s\n", vp->name);
723 			}
724 		}
725 	} else {
726 		for (l = genv->loc; l; l = l->next) {
727 			for (p = ktsort(&l->vars); (vp = *p++); ) {
728 				struct tbl *tvp;
729 				int any_set = 0;
730 				/*
731 				 * See if the parameter is set (for arrays, if any
732 				 * element is set).
733 				 */
734 				for (tvp = vp; tvp; tvp = tvp->u.array)
735 					if (tvp->flag & ISSET) {
736 						any_set = 1;
737 						break;
738 					}
739 
740 				/*
741 				 * Check attributes - note that all array elements
742 				 * have (should have?) the same attributes, so checking
743 				 * the first is sufficient.
744 				 *
745 				 * Report an unset param only if the user has
746 				 * explicitly given it some attribute (like export);
747 				 * otherwise, after "echo $FOO", we would report FOO...
748 				 */
749 				if (!any_set && !(vp->flag & USERATTRIB))
750 					continue;
751 				if (flag && (vp->flag & flag) == 0)
752 					continue;
753 				for (; vp; vp = vp->u.array) {
754 					/* Ignore array elements that aren't
755 					 * set unless there are no set elements,
756 					 * in which case the first is reported on */
757 					if ((vp->flag&ARRAY) && any_set &&
758 					    !(vp->flag & ISSET))
759 						continue;
760 					/* no arguments */
761 					if (thing == 0 && flag == 0) {
762 						/* at&t ksh prints things
763 						 * like export, integer,
764 						 * leftadj, zerofill, etc.,
765 						 * but POSIX says must
766 						 * be suitable for re-entry...
767 						 */
768 						shprintf("typeset ");
769 						if ((vp->flag&INTEGER))
770 							shprintf("-i ");
771 						if ((vp->flag&EXPORT))
772 							shprintf("-x ");
773 						if ((vp->flag&RDONLY))
774 							shprintf("-r ");
775 						if ((vp->flag&TRACE))
776 							shprintf("-t ");
777 						if ((vp->flag&LJUST))
778 							shprintf("-L%d ", vp->u2.field);
779 						if ((vp->flag&RJUST))
780 							shprintf("-R%d ", vp->u2.field);
781 						if ((vp->flag&ZEROFIL))
782 							shprintf("-Z ");
783 						if ((vp->flag&LCASEV))
784 							shprintf("-l ");
785 						if ((vp->flag&UCASEV_AL))
786 							shprintf("-u ");
787 						if ((vp->flag&INT_U))
788 							shprintf("-U ");
789 						shprintf("%s\n", vp->name);
790 						    if (vp->flag&ARRAY)
791 						break;
792 					} else {
793 						if (pflag)
794 							shprintf("%s ",
795 							    (flag & EXPORT) ?
796 							    "export" : "readonly");
797 						if ((vp->flag&ARRAY) && any_set)
798 							shprintf("%s[%d]",
799 							    vp->name, vp->index);
800 						else
801 							shprintf("%s", vp->name);
802 						if (thing == '-' && (vp->flag&ISSET)) {
803 							char *s = str_val(vp);
804 
805 							shprintf("=");
806 							/* at&t ksh can't have
807 							 * justified integers.. */
808 							if ((vp->flag &
809 							    (INTEGER|LJUST|RJUST)) ==
810 							    INTEGER)
811 								shprintf("%s", s);
812 							else
813 								print_value_quoted(s);
814 						}
815 						shprintf("\n");
816 					}
817 					/* Only report first `element' of an array with
818 					* no set elements.
819 					*/
820 					if (!any_set)
821 						break;
822 				}
823 			}
824 		}
825 	}
826 	return 0;
827 }
828 
829 int
830 c_alias(char **wp)
831 {
832 	struct table *t = &aliases;
833 	int rv = 0, rflag = 0, tflag, Uflag = 0, pflag = 0, prefix = 0;
834 	int xflag = 0;
835 	int optc;
836 
837 	builtin_opt.flags |= GF_PLUSOPT;
838 	while ((optc = ksh_getopt(wp, &builtin_opt, "dprtUx")) != -1) {
839 		prefix = builtin_opt.info & GI_PLUS ? '+' : '-';
840 		switch (optc) {
841 		case 'd':
842 			t = &homedirs;
843 			break;
844 		case 'p':
845 			pflag = 1;
846 			break;
847 		case 'r':
848 			rflag = 1;
849 			break;
850 		case 't':
851 			t = &taliases;
852 			break;
853 		case 'U':
854 			/*
855 			 * kludge for tracked alias initialization
856 			 * (don't do a path search, just make an entry)
857 			 */
858 			Uflag = 1;
859 			break;
860 		case 'x':
861 			xflag = EXPORT;
862 			break;
863 		case '?':
864 			return 1;
865 		}
866 	}
867 	wp += builtin_opt.optind;
868 
869 	if (!(builtin_opt.info & GI_MINUSMINUS) && *wp &&
870 	    (wp[0][0] == '-' || wp[0][0] == '+') && wp[0][1] == '\0') {
871 		prefix = wp[0][0];
872 		wp++;
873 	}
874 
875 	tflag = t == &taliases;
876 
877 	/* "hash -r" means reset all the tracked aliases.. */
878 	if (rflag) {
879 		static const char *const args[] = {
880 			"unalias", "-ta", NULL
881 		};
882 
883 		if (!tflag || *wp) {
884 			shprintf("alias: -r flag can only be used with -t"
885 			    " and without arguments\n");
886 			return 1;
887 		}
888 		ksh_getopt_reset(&builtin_opt, GF_ERROR);
889 		return c_unalias((char **) args);
890 	}
891 
892 	if (*wp == NULL) {
893 		struct tbl *ap, **p;
894 
895 		for (p = ktsort(t); (ap = *p++) != NULL; )
896 			if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) {
897 				if (pflag)
898 					shf_puts("alias ", shl_stdout);
899 				shf_puts(ap->name, shl_stdout);
900 				if (prefix != '+') {
901 					shf_putc('=', shl_stdout);
902 					print_value_quoted(ap->val.s);
903 				}
904 				shprintf("\n");
905 			}
906 	}
907 
908 	for (; *wp != NULL; wp++) {
909 		char *alias = *wp;
910 		char *val = strchr(alias, '=');
911 		char *newval;
912 		struct tbl *ap;
913 		int h;
914 
915 		if (val)
916 			alias = str_nsave(alias, val++ - alias, ATEMP);
917 		h = hash(alias);
918 		if (val == NULL && !tflag && !xflag) {
919 			ap = ktsearch(t, alias, h);
920 			if (ap != NULL && (ap->flag&ISSET)) {
921 				if (pflag)
922 					shf_puts("alias ", shl_stdout);
923 				shf_puts(ap->name, shl_stdout);
924 				if (prefix != '+') {
925 					shf_putc('=', shl_stdout);
926 					print_value_quoted(ap->val.s);
927 				}
928 				shprintf("\n");
929 			} else {
930 				shprintf("%s alias not found\n", alias);
931 				rv = 1;
932 			}
933 			continue;
934 		}
935 		ap = ktenter(t, alias, h);
936 		ap->type = tflag ? CTALIAS : CALIAS;
937 		/* Are we setting the value or just some flags? */
938 		if ((val && !tflag) || (!val && tflag && !Uflag)) {
939 			if (ap->flag&ALLOC) {
940 				ap->flag &= ~(ALLOC|ISSET);
941 				afree(ap->val.s, APERM);
942 			}
943 			/* ignore values for -t (at&t ksh does this) */
944 			newval = tflag ? search(alias, path, X_OK, NULL) :
945 			    val;
946 			if (newval) {
947 				ap->val.s = str_save(newval, APERM);
948 				ap->flag |= ALLOC|ISSET;
949 			} else
950 				ap->flag &= ~ISSET;
951 		}
952 		ap->flag |= DEFINED;
953 		if (prefix == '+')
954 			ap->flag &= ~xflag;
955 		else
956 			ap->flag |= xflag;
957 		if (val)
958 			afree(alias, ATEMP);
959 	}
960 
961 	return rv;
962 }
963 
964 int
965 c_unalias(char **wp)
966 {
967 	struct table *t = &aliases;
968 	struct tbl *ap;
969 	int rv = 0, all = 0;
970 	int optc;
971 
972 	while ((optc = ksh_getopt(wp, &builtin_opt, "adt")) != -1)
973 		switch (optc) {
974 		case 'a':
975 			all = 1;
976 			break;
977 		case 'd':
978 			t = &homedirs;
979 			break;
980 		case 't':
981 			t = &taliases;
982 			break;
983 		case '?':
984 			return 1;
985 		}
986 	wp += builtin_opt.optind;
987 
988 	for (; *wp != NULL; wp++) {
989 		ap = ktsearch(t, *wp, hash(*wp));
990 		if (ap == NULL) {
991 			rv = 1;	/* POSIX */
992 			continue;
993 		}
994 		if (ap->flag&ALLOC) {
995 			ap->flag &= ~(ALLOC|ISSET);
996 			afree(ap->val.s, APERM);
997 		}
998 		ap->flag &= ~(DEFINED|ISSET|EXPORT);
999 	}
1000 
1001 	if (all) {
1002 		struct tstate ts;
1003 
1004 		for (ktwalk(&ts, t); (ap = ktnext(&ts)); ) {
1005 			if (ap->flag&ALLOC) {
1006 				ap->flag &= ~(ALLOC|ISSET);
1007 				afree(ap->val.s, APERM);
1008 			}
1009 			ap->flag &= ~(DEFINED|ISSET|EXPORT);
1010 		}
1011 	}
1012 
1013 	return rv;
1014 }
1015 
1016 int
1017 c_let(char **wp)
1018 {
1019 	int rv = 1;
1020 	long val;
1021 
1022 	if (wp[1] == NULL) /* at&t ksh does this */
1023 		bi_errorf("no arguments");
1024 	else
1025 		for (wp++; *wp; wp++)
1026 			if (!evaluate(*wp, &val, KSH_RETURN_ERROR, true)) {
1027 				rv = 2;	/* distinguish error from zero result */
1028 				break;
1029 			} else
1030 				rv = val == 0;
1031 	return rv;
1032 }
1033 
1034 int
1035 c_jobs(char **wp)
1036 {
1037 	int optc;
1038 	int flag = 0;
1039 	int nflag = 0;
1040 	int rv = 0;
1041 
1042 	while ((optc = ksh_getopt(wp, &builtin_opt, "lpnz")) != -1)
1043 		switch (optc) {
1044 		case 'l':
1045 			flag = 1;
1046 			break;
1047 		case 'p':
1048 			flag = 2;
1049 			break;
1050 		case 'n':
1051 			nflag = 1;
1052 			break;
1053 		case 'z':	/* debugging: print zombies */
1054 			nflag = -1;
1055 			break;
1056 		case '?':
1057 			return 1;
1058 		}
1059 	wp += builtin_opt.optind;
1060 	if (!*wp) {
1061 		if (j_jobs(NULL, flag, nflag))
1062 			rv = 1;
1063 	} else {
1064 		for (; *wp; wp++)
1065 			if (j_jobs(*wp, flag, nflag))
1066 				rv = 1;
1067 	}
1068 	return rv;
1069 }
1070 
1071 #ifdef JOBS
1072 int
1073 c_fgbg(char **wp)
1074 {
1075 	int bg = strcmp(*wp, "bg") == 0;
1076 	int rv = 0;
1077 
1078 	if (!Flag(FMONITOR)) {
1079 		bi_errorf("job control not enabled");
1080 		return 1;
1081 	}
1082 	if (ksh_getopt(wp, &builtin_opt, null) == '?')
1083 		return 1;
1084 	wp += builtin_opt.optind;
1085 	if (*wp)
1086 		for (; *wp; wp++)
1087 			rv = j_resume(*wp, bg);
1088 	else
1089 		rv = j_resume("%%", bg);
1090 	/* POSIX says fg shall return 0 (unless an error occurs).
1091 	 * at&t ksh returns the exit value of the job...
1092 	 */
1093 	return (bg || Flag(FPOSIX)) ? 0 : rv;
1094 }
1095 #endif
1096 
1097 struct kill_info {
1098 	int num_width;
1099 	int name_width;
1100 };
1101 static char *kill_fmt_entry(void *arg, int i, char *buf, int buflen);
1102 
1103 /* format a single kill item */
1104 static char *
1105 kill_fmt_entry(void *arg, int i, char *buf, int buflen)
1106 {
1107 	struct kill_info *ki = (struct kill_info *) arg;
1108 
1109 	i++;
1110 	if (sigtraps[i].name)
1111 		shf_snprintf(buf, buflen, "%*d %*s %s",
1112 		    ki->num_width, i,
1113 		    ki->name_width, sigtraps[i].name,
1114 		    sigtraps[i].mess);
1115 	else
1116 		shf_snprintf(buf, buflen, "%*d %*d %s",
1117 		    ki->num_width, i,
1118 		    ki->name_width, sigtraps[i].signal,
1119 		    sigtraps[i].mess);
1120 	return buf;
1121 }
1122 
1123 
1124 int
1125 c_kill(char **wp)
1126 {
1127 	Trap *t = NULL;
1128 	char *p;
1129 	int lflag = 0;
1130 	int i, n, rv, sig;
1131 
1132 	/* assume old style options if -digits or -UPPERCASE */
1133 	if ((p = wp[1]) && *p == '-' &&
1134 	    (digit(p[1]) || isupper((unsigned char)p[1]))) {
1135 		if (!(t = gettrap(p + 1, true))) {
1136 			bi_errorf("bad signal `%s'", p + 1);
1137 			return 1;
1138 		}
1139 		i = (wp[2] && strcmp(wp[2], "--") == 0) ? 3 : 2;
1140 	} else {
1141 		int optc;
1142 
1143 		while ((optc = ksh_getopt(wp, &builtin_opt, "ls:")) != -1)
1144 			switch (optc) {
1145 			case 'l':
1146 				lflag = 1;
1147 				break;
1148 			case 's':
1149 				if (!(t = gettrap(builtin_opt.optarg, true))) {
1150 					bi_errorf("bad signal `%s'",
1151 					    builtin_opt.optarg);
1152 					return 1;
1153 				}
1154 				break;
1155 			case '?':
1156 				return 1;
1157 			}
1158 		i = builtin_opt.optind;
1159 	}
1160 	if ((lflag && t) || (!wp[i] && !lflag)) {
1161 		shf_fprintf(shl_out,
1162 		    "usage: kill [-s signame | -signum | -signame] { job | pid | pgrp } ...\n"
1163 		    "       kill -l [exit_status ...]\n");
1164 		bi_errorf(NULL);
1165 		return 1;
1166 	}
1167 
1168 	if (lflag) {
1169 		if (wp[i]) {
1170 			for (; wp[i]; i++) {
1171 				if (!bi_getn(wp[i], &n))
1172 					return 1;
1173 				if (n > 128 && n < 128 + NSIG)
1174 					n -= 128;
1175 				if (n > 0 && n < NSIG && sigtraps[n].name)
1176 					shprintf("%s\n", sigtraps[n].name);
1177 				else
1178 					shprintf("%d\n", n);
1179 			}
1180 		} else if (Flag(FPOSIX)) {
1181 			p = null;
1182 			for (i = 1; i < NSIG; i++, p = " ")
1183 				if (sigtraps[i].name)
1184 					shprintf("%s%s", p, sigtraps[i].name);
1185 			shprintf("\n");
1186 		} else {
1187 			int mess_width = 0, w, i;
1188 			struct kill_info ki = {
1189 				.num_width = 1,
1190 				.name_width = 0,
1191 			};
1192 
1193 			for (i = NSIG; i >= 10; i /= 10)
1194 				ki.num_width++;
1195 
1196 			for (i = 0; i < NSIG; i++) {
1197 				w = sigtraps[i].name ? strlen(sigtraps[i].name) :
1198 				    ki.num_width;
1199 				if (w > ki.name_width)
1200 					ki.name_width = w;
1201 				w = strlen(sigtraps[i].mess);
1202 				if (w > mess_width)
1203 					mess_width = w;
1204 			}
1205 
1206 			print_columns(shl_stdout, NSIG - 1,
1207 			    kill_fmt_entry, (void *) &ki,
1208 			    ki.num_width + ki.name_width + mess_width + 3, 1);
1209 		}
1210 		return 0;
1211 	}
1212 	rv = 0;
1213 	sig = t ? t->signal : SIGTERM;
1214 	for (; (p = wp[i]); i++) {
1215 		if (*p == '%') {
1216 			if (j_kill(p, sig))
1217 				rv = 1;
1218 		} else if (!getn(p, &n)) {
1219 			bi_errorf("%s: arguments must be jobs or process IDs",
1220 			    p);
1221 			rv = 1;
1222 		} else {
1223 			/* use killpg if < -1 since -1 does special things for
1224 			 * some non-killpg-endowed kills
1225 			 */
1226 			if ((n < -1 ? killpg(-n, sig) : kill(n, sig)) < 0) {
1227 				bi_errorf("%s: %s", p, strerror(errno));
1228 				rv = 1;
1229 			}
1230 		}
1231 	}
1232 	return rv;
1233 }
1234 
1235 void
1236 getopts_reset(int val)
1237 {
1238 	if (val >= 1) {
1239 		ksh_getopt_reset(&user_opt,
1240 		    GF_NONAME | (Flag(FPOSIX) ? 0 : GF_PLUSOPT));
1241 		user_opt.optind = user_opt.uoptind = val;
1242 	}
1243 }
1244 
1245 int
1246 c_getopts(char **wp)
1247 {
1248 	int	argc;
1249 	const char *options;
1250 	const char *var;
1251 	int	optc;
1252 	int	ret;
1253 	char	buf[3];
1254 	struct tbl *vq, *voptarg;
1255 
1256 	if (ksh_getopt(wp, &builtin_opt, null) == '?')
1257 		return 1;
1258 	wp += builtin_opt.optind;
1259 
1260 	options = *wp++;
1261 	if (!options) {
1262 		bi_errorf("missing options argument");
1263 		return 1;
1264 	}
1265 
1266 	var = *wp++;
1267 	if (!var) {
1268 		bi_errorf("missing name argument");
1269 		return 1;
1270 	}
1271 	if (!*var || *skip_varname(var, true)) {
1272 		bi_errorf("%s: is not an identifier", var);
1273 		return 1;
1274 	}
1275 
1276 	if (genv->loc->next == NULL) {
1277 		internal_errorf(0, "c_getopts: no argv");
1278 		return 1;
1279 	}
1280 	/* Which arguments are we parsing... */
1281 	if (*wp == NULL)
1282 		wp = genv->loc->next->argv;
1283 	else
1284 		*--wp = genv->loc->next->argv[0];
1285 
1286 	/* Check that our saved state won't cause a core dump... */
1287 	for (argc = 0; wp[argc]; argc++)
1288 		;
1289 	if (user_opt.optind > argc ||
1290 	    (user_opt.p != 0 &&
1291 	    user_opt.p > strlen(wp[user_opt.optind - 1]))) {
1292 		bi_errorf("arguments changed since last call");
1293 		return 1;
1294 	}
1295 
1296 	user_opt.optarg = NULL;
1297 	optc = ksh_getopt(wp, &user_opt, options);
1298 
1299 	if (optc >= 0 && optc != '?' && (user_opt.info & GI_PLUS)) {
1300 		buf[0] = '+';
1301 		buf[1] = optc;
1302 		buf[2] = '\0';
1303 	} else {
1304 		/* POSIX says var is set to ? at end-of-options, at&t ksh
1305 		 * sets it to null - we go with POSIX...
1306 		 */
1307 		buf[0] = optc < 0 ? '?' : optc;
1308 		buf[1] = '\0';
1309 	}
1310 
1311 	/* at&t ksh does not change OPTIND if it was an unknown option.
1312 	 * Scripts counting on this are prone to break... (ie, don't count
1313 	 * on this staying).
1314 	 */
1315 	if (optc != '?') {
1316 		user_opt.uoptind = user_opt.optind;
1317 	}
1318 
1319 	voptarg = global("OPTARG");
1320 	voptarg->flag &= ~RDONLY;	/* at&t ksh clears ro and int */
1321 	/* Paranoia: ensure no bizarre results. */
1322 	if (voptarg->flag & INTEGER)
1323 	    typeset("OPTARG", 0, INTEGER, 0, 0);
1324 	if (user_opt.optarg == NULL)
1325 		unset(voptarg, 0);
1326 	else
1327 		/* This can't fail (have cleared readonly/integer) */
1328 		setstr(voptarg, user_opt.optarg, KSH_RETURN_ERROR);
1329 
1330 	ret = 0;
1331 
1332 	vq = global(var);
1333 	/* Error message already printed (integer, readonly) */
1334 	if (!setstr(vq, buf, KSH_RETURN_ERROR))
1335 	    ret = 1;
1336 	if (Flag(FEXPORT))
1337 		typeset(var, EXPORT, 0, 0, 0);
1338 
1339 	return optc < 0 ? 1 : ret;
1340 }
1341 
1342 #ifdef EMACS
1343 int
1344 c_bind(char **wp)
1345 {
1346 	int optc, rv = 0, macro = 0, list = 0;
1347 	char *cp;
1348 
1349 	while ((optc = ksh_getopt(wp, &builtin_opt, "lm")) != -1)
1350 		switch (optc) {
1351 		case 'l':
1352 			list = 1;
1353 			break;
1354 		case 'm':
1355 			macro = 1;
1356 			break;
1357 		case '?':
1358 			return 1;
1359 		}
1360 	wp += builtin_opt.optind;
1361 
1362 	if (*wp == NULL)	/* list all */
1363 		rv = x_bind(NULL, NULL, 0, list);
1364 
1365 	for (; *wp != NULL; wp++) {
1366 		cp = strchr(*wp, '=');
1367 		if (cp != NULL)
1368 			*cp++ = '\0';
1369 		if (x_bind(*wp, cp, macro, 0))
1370 			rv = 1;
1371 	}
1372 
1373 	return rv;
1374 }
1375 #endif
1376 
1377 /* A leading = means assignments before command are kept;
1378  * a leading * means a POSIX special builtin;
1379  * a leading + means a POSIX regular builtin
1380  * (* and + should not be combined).
1381  */
1382 const struct builtin kshbuiltins [] = {
1383 	{"+alias", c_alias},	/* no =: at&t manual wrong */
1384 	{"+cd", c_cd},
1385 	{"+command", c_command},
1386 	{"echo", c_print},
1387 	{"*=export", c_typeset},
1388 #ifdef HISTORY
1389 	{"+fc", c_fc},
1390 #endif /* HISTORY */
1391 	{"+getopts", c_getopts},
1392 	{"+jobs", c_jobs},
1393 	{"+kill", c_kill},
1394 	{"let", c_let},
1395 	{"print", c_print},
1396 	{"pwd", c_pwd},
1397 	{"*=readonly", c_typeset},
1398 	{"=typeset", c_typeset},
1399 	{"+unalias", c_unalias},
1400 	{"whence", c_whence},
1401 #ifdef JOBS
1402 	{"+bg", c_fgbg},
1403 	{"+fg", c_fgbg},
1404 #endif
1405 #ifdef EMACS
1406 	{"bind", c_bind},
1407 #endif
1408 	{NULL, NULL}
1409 };
1410