Home | History | Annotate | Download | only in src
      1 /*	$OpenBSD: misc.c,v 1.37 2009/04/19 20:34:05 sthen Exp $	*/
      2 /*	$OpenBSD: path.c,v 1.12 2005/03/30 17:16:37 deraadt Exp $	*/
      3 
      4 /*-
      5  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
      6  *	Thorsten Glaser <tg (at) mirbsd.org>
      7  *
      8  * Provided that these terms and disclaimer and all copyright notices
      9  * are retained or reproduced in an accompanying document, permission
     10  * is granted to deal in this work without restriction, including un-
     11  * limited rights to use, publicly perform, distribute, sell, modify,
     12  * merge, give away, or sublicence.
     13  *
     14  * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
     15  * the utmost extent permitted by applicable law, neither express nor
     16  * implied; without malicious intent or gross negligence. In no event
     17  * may a licensor, author or contributor be held liable for indirect,
     18  * direct, other damage, loss, or other issues arising in any way out
     19  * of dealing in the work, even if advised of the possibility of such
     20  * damage or existence of a defect, except proven that it results out
     21  * of said person's immediate fault when using the work as intended.
     22  */
     23 
     24 #include "sh.h"
     25 #if !HAVE_GETRUSAGE
     26 #include <sys/times.h>
     27 #endif
     28 #if HAVE_GRP_H
     29 #include <grp.h>
     30 #endif
     31 
     32 __RCSID("$MirOS: src/bin/mksh/misc.c,v 1.172 2011/09/07 15:24:18 tg Exp $");
     33 
     34 /* type bits for unsigned char */
     35 unsigned char chtypes[UCHAR_MAX + 1];
     36 
     37 static const unsigned char *pat_scan(const unsigned char *,
     38     const unsigned char *, bool);
     39 static int do_gmatch(const unsigned char *, const unsigned char *,
     40     const unsigned char *, const unsigned char *);
     41 static const unsigned char *cclass(const unsigned char *, int);
     42 #ifdef TIOCSCTTY
     43 static void chvt(const char *);
     44 #endif
     45 
     46 /*XXX this should go away */
     47 static int make_path(const char *, const char *, char **, XString *, int *);
     48 
     49 #ifdef SETUID_CAN_FAIL_WITH_EAGAIN
     50 /* we don't need to check for other codes, EPERM won't happen */
     51 #define DO_SETUID(func, argvec) do {					\
     52 	if ((func argvec) && errno == EAGAIN)				\
     53 		errorf("%s failed with EAGAIN, probably due to a"	\
     54 		    " too low process limit; aborting", #func);		\
     55 } while (/* CONSTCOND */ 0)
     56 #else
     57 #define DO_SETUID(func, argvec) func argvec
     58 #endif
     59 
     60 /*
     61  * Fast character classes
     62  */
     63 void
     64 setctypes(const char *s, int t)
     65 {
     66 	unsigned int i;
     67 
     68 	if (t & C_IFS) {
     69 		for (i = 0; i < UCHAR_MAX + 1; i++)
     70 			chtypes[i] &= ~C_IFS;
     71 		/* include \0 in C_IFS */
     72 		chtypes[0] |= C_IFS;
     73 	}
     74 	while (*s != 0)
     75 		chtypes[(unsigned char)*s++] |= t;
     76 }
     77 
     78 void
     79 initctypes(void)
     80 {
     81 	int c;
     82 
     83 	for (c = 'a'; c <= 'z'; c++)
     84 		chtypes[c] |= C_ALPHA;
     85 	for (c = 'A'; c <= 'Z'; c++)
     86 		chtypes[c] |= C_ALPHA;
     87 	chtypes['_'] |= C_ALPHA;
     88 	setctypes("0123456789", C_DIGIT);
     89 	/* \0 added automatically */
     90 	setctypes(" \t\n|&;<>()", C_LEX1);
     91 	setctypes("*@#!$-?", C_VAR1);
     92 	setctypes(" \t\n", C_IFSWS);
     93 	setctypes("=-+?", C_SUBOP1);
     94 	setctypes("\t\n \"#$&'()*;<=>?[\\]`|", C_QUOTE);
     95 }
     96 
     97 /* called from XcheckN() to grow buffer */
     98 char *
     99 Xcheck_grow_(XString *xsp, const char *xp, size_t more)
    100 {
    101 	const char *old_beg = xsp->beg;
    102 
    103 	if (more < xsp->len)
    104 		more = xsp->len;
    105 	/* (xsp->len + X_EXTRA) never overflows */
    106 	checkoktoadd(more, xsp->len + X_EXTRA);
    107 	xsp->beg = aresize(xsp->beg, (xsp->len += more) + X_EXTRA, xsp->areap);
    108 	xsp->end = xsp->beg + xsp->len;
    109 	return (xsp->beg + (xp - old_beg));
    110 }
    111 
    112 #define SHFLAGS_DEFNS
    113 #include "sh_flags.h"
    114 
    115 const struct shoption options[] = {
    116 #define SHFLAGS_ITEMS
    117 #include "sh_flags.h"
    118 };
    119 
    120 /*
    121  * translate -o option into F* constant (also used for test -o option)
    122  */
    123 size_t
    124 option(const char *n)
    125 {
    126 	size_t i;
    127 
    128 	if ((n[0] == '-' || n[0] == '+') && n[1] && !n[2]) {
    129 		for (i = 0; i < NELEM(options); i++)
    130 			if (options[i].c == n[1])
    131 				return (i);
    132 	} else for (i = 0; i < NELEM(options); i++)
    133 		if (options[i].name && strcmp(options[i].name, n) == 0)
    134 			return (i);
    135 
    136 	return ((size_t)-1);
    137 }
    138 
    139 struct options_info {
    140 	int opt_width;
    141 	int opts[NELEM(options)];
    142 };
    143 
    144 static char *options_fmt_entry(char *, size_t, int, const void *);
    145 static void printoptions(bool);
    146 
    147 /* format a single select menu item */
    148 static char *
    149 options_fmt_entry(char *buf, size_t buflen, int i, const void *arg)
    150 {
    151 	const struct options_info *oi = (const struct options_info *)arg;
    152 
    153 	shf_snprintf(buf, buflen, "%-*s %s",
    154 	    oi->opt_width, options[oi->opts[i]].name,
    155 	    Flag(oi->opts[i]) ? "on" : "off");
    156 	return (buf);
    157 }
    158 
    159 static void
    160 printoptions(bool verbose)
    161 {
    162 	size_t i = 0;
    163 
    164 	if (verbose) {
    165 		ssize_t n = 0, len, octs = 0;
    166 		struct options_info oi;
    167 
    168 		/* verbose version */
    169 		shf_puts("Current option settings\n", shl_stdout);
    170 
    171 		oi.opt_width = 0;
    172 		while (i < NELEM(options)) {
    173 			if (options[i].name) {
    174 				oi.opts[n++] = i;
    175 				len = strlen(options[i].name);
    176 				if (len > octs)
    177 					octs = len;
    178 				len = utf_mbswidth(options[i].name);
    179 				if (len > oi.opt_width)
    180 					oi.opt_width = len;
    181 			}
    182 			++i;
    183 		}
    184 		print_columns(shl_stdout, n, options_fmt_entry, &oi,
    185 		    octs + 4, oi.opt_width + 4, true);
    186 	} else {
    187 		/* short version like AT&T ksh93 */
    188 		shf_puts(Tset, shl_stdout);
    189 		while (i < (int)NELEM(options)) {
    190 			if (Flag(i) && options[i].name)
    191 				shprintf("%s %s %s", null, "-o",
    192 				    options[i].name);
    193 			++i;
    194 		}
    195 		shf_putc('\n', shl_stdout);
    196 	}
    197 }
    198 
    199 char *
    200 getoptions(void)
    201 {
    202 	size_t i;
    203 	char m[(int)FNFLAGS + 1];
    204 	char *cp = m;
    205 
    206 	for (i = 0; i < NELEM(options); i++)
    207 		if (options[i].c && Flag(i))
    208 			*cp++ = options[i].c;
    209 	strndupx(cp, m, cp - m, ATEMP);
    210 	return (cp);
    211 }
    212 
    213 /* change a Flag(*) value; takes care of special actions */
    214 void
    215 change_flag(enum sh_flag f, int what, unsigned int newval)
    216 {
    217 	unsigned char oldval;
    218 
    219 	oldval = Flag(f);
    220 	/* needed for tristates */
    221 	Flag(f) = newval ? 1 : 0;
    222 #ifndef MKSH_UNEMPLOYED
    223 	if (f == FMONITOR) {
    224 		if (what != OF_CMDLINE && newval != oldval)
    225 			j_change();
    226 	} else
    227 #endif
    228 	  if ((
    229 #if !MKSH_S_NOVI
    230 	    f == FVI ||
    231 #endif
    232 	    f == FEMACS || f == FGMACS) && newval) {
    233 #if !MKSH_S_NOVI
    234 		Flag(FVI) =
    235 #endif
    236 		    Flag(FEMACS) = Flag(FGMACS) = 0;
    237 		Flag(f) = (unsigned char)newval;
    238 	} else if (f == FPRIVILEGED && oldval && !newval) {
    239 		/* Turning off -p? */
    240 
    241 		/*XXX this can probably be optimised */
    242 		kshegid = kshgid = getgid();
    243 #if HAVE_SETRESUGID
    244 		DO_SETUID(setresgid, (kshegid, kshegid, kshegid));
    245 #if HAVE_SETGROUPS
    246 		/* setgroups doesn't EAGAIN on Linux */
    247 		setgroups(1, &kshegid);
    248 #endif
    249 		DO_SETUID(setresuid, (ksheuid, ksheuid, ksheuid));
    250 #else
    251 		/* seteuid, setegid, setgid don't EAGAIN on Linux */
    252 		seteuid(ksheuid = kshuid = getuid());
    253 		DO_SETUID(setuid, (ksheuid));
    254 		setegid(kshegid);
    255 		setgid(kshegid);
    256 #endif
    257 	} else if ((f == FPOSIX || f == FSH) && newval) {
    258 		Flag(FPOSIX) = Flag(FSH) = Flag(FBRACEEXPAND) = 0;
    259 		Flag(f) = (unsigned char)newval;
    260 	}
    261 	/* Changing interactive flag? */
    262 	if (f == FTALKING) {
    263 		if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid)
    264 			Flag(FTALKING_I) = (unsigned char)newval;
    265 	}
    266 }
    267 
    268 /*
    269  * Parse command line and set command arguments. Returns the index of
    270  * non-option arguments, -1 if there is an error.
    271  */
    272 int
    273 parse_args(const char **argv,
    274     /* OF_CMDLINE or OF_SET */
    275     int what,
    276     bool *setargsp)
    277 {
    278 	static char cmd_opts[NELEM(options) + 5]; /* o:T:\0 */
    279 	static char set_opts[NELEM(options) + 6]; /* A:o;s\0 */
    280 	char set, *opts;
    281 	const char *array = NULL;
    282 	Getopt go;
    283 	size_t i;
    284 	int optc, arrayset = 0;
    285 	bool sortargs = false;
    286 
    287 	/* First call? Build option strings... */
    288 	if (cmd_opts[0] == '\0') {
    289 		char *p = cmd_opts, *q = set_opts;
    290 
    291 		/* see cmd_opts[] declaration */
    292 		*p++ = 'o';
    293 		*p++ = ':';
    294 #if !defined(MKSH_SMALL) || defined(TIOCSCTTY)
    295 		*p++ = 'T';
    296 		*p++ = ':';
    297 #endif
    298 		/* see set_opts[] declaration */
    299 		*q++ = 'A';
    300 		*q++ = ':';
    301 		*q++ = 'o';
    302 		*q++ = ';';
    303 		*q++ = 's';
    304 
    305 		for (i = 0; i < NELEM(options); i++) {
    306 			if (options[i].c) {
    307 				if (options[i].flags & OF_CMDLINE)
    308 					*p++ = options[i].c;
    309 				if (options[i].flags & OF_SET)
    310 					*q++ = options[i].c;
    311 			}
    312 		}
    313 		*p = '\0';
    314 		*q = '\0';
    315 	}
    316 
    317 	if (what == OF_CMDLINE) {
    318 		const char *p = argv[0], *q;
    319 		/*
    320 		 * Set FLOGIN before parsing options so user can clear
    321 		 * flag using +l.
    322 		 */
    323 		if (*p != '-')
    324 			for (q = p; *q; )
    325 				if (*q++ == '/')
    326 					p = q;
    327 		Flag(FLOGIN) = (*p == '-');
    328 		opts = cmd_opts;
    329 	} else if (what == OF_FIRSTTIME) {
    330 		opts = cmd_opts;
    331 	} else
    332 		opts = set_opts;
    333 	ksh_getopt_reset(&go, GF_ERROR|GF_PLUSOPT);
    334 	while ((optc = ksh_getopt(argv, &go, opts)) != -1) {
    335 		set = (go.info & GI_PLUS) ? 0 : 1;
    336 		switch (optc) {
    337 		case 'A':
    338 			if (what == OF_FIRSTTIME)
    339 				break;
    340 			arrayset = set ? 1 : -1;
    341 			array = go.optarg;
    342 			break;
    343 
    344 		case 'o':
    345 			if (what == OF_FIRSTTIME)
    346 				break;
    347 			if (go.optarg == NULL) {
    348 				/*
    349 				 * lone -o: print options
    350 				 *
    351 				 * Note that on the command line, -o requires
    352 				 * an option (ie, can't get here if what is
    353 				 * OF_CMDLINE).
    354 				 */
    355 				printoptions(set);
    356 				break;
    357 			}
    358 			i = option(go.optarg);
    359 			if ((i != (size_t)-1) && set == Flag(i))
    360 				/*
    361 				 * Don't check the context if the flag
    362 				 * isn't changing - makes "set -o interactive"
    363 				 * work if you're already interactive. Needed
    364 				 * if the output of "set +o" is to be used.
    365 				 */
    366 				;
    367 			else if ((i != (size_t)-1) && (options[i].flags & what))
    368 				change_flag((enum sh_flag)i, what, set);
    369 			else {
    370 				bi_errorf("%s: %s", go.optarg, "bad option");
    371 				return (-1);
    372 			}
    373 			break;
    374 
    375 #if !defined(MKSH_SMALL) || defined(TIOCSCTTY)
    376 		case 'T':
    377 			if (what != OF_FIRSTTIME)
    378 				break;
    379 #ifndef TIOCSCTTY
    380 			errorf("no TIOCSCTTY ioctl");
    381 #else
    382 			change_flag(FTALKING, OF_CMDLINE, 1);
    383 			chvt(go.optarg);
    384 			break;
    385 #endif
    386 #endif
    387 
    388 		case '?':
    389 			return (-1);
    390 
    391 		default:
    392 			if (what == OF_FIRSTTIME)
    393 				break;
    394 			/* -s: sort positional params (AT&T ksh stupidity) */
    395 			if (what == OF_SET && optc == 's') {
    396 				sortargs = true;
    397 				break;
    398 			}
    399 			for (i = 0; i < NELEM(options); i++)
    400 				if (optc == options[i].c &&
    401 				    (what & options[i].flags)) {
    402 					change_flag((enum sh_flag)i, what, set);
    403 					break;
    404 				}
    405 			if (i == NELEM(options))
    406 				internal_errorf("parse_args: '%c'", optc);
    407 		}
    408 	}
    409 	if (!(go.info & GI_MINUSMINUS) && argv[go.optind] &&
    410 	    (argv[go.optind][0] == '-' || argv[go.optind][0] == '+') &&
    411 	    argv[go.optind][1] == '\0') {
    412 		/* lone - clears -v and -x flags */
    413 		if (argv[go.optind][0] == '-')
    414 			Flag(FVERBOSE) = Flag(FXTRACE) = 0;
    415 		/* set skips lone - or + option */
    416 		go.optind++;
    417 	}
    418 	if (setargsp)
    419 		/* -- means set $#/$* even if there are no arguments */
    420 		*setargsp = !arrayset && ((go.info & GI_MINUSMINUS) ||
    421 		    argv[go.optind]);
    422 
    423 	if (arrayset) {
    424 		const char *ccp = NULL;
    425 
    426 		if (*array)
    427 			ccp = skip_varname(array, false);
    428 		if (!ccp || !(!ccp[0] || (ccp[0] == '+' && !ccp[1]))) {
    429 			bi_errorf("%s: %s", array, "is not an identifier");
    430 			return (-1);
    431 		}
    432 	}
    433 	if (sortargs) {
    434 		for (i = go.optind; argv[i]; i++)
    435 			;
    436 		qsort(&argv[go.optind], i - go.optind, sizeof(void *),
    437 		    xstrcmp);
    438 	}
    439 	if (arrayset)
    440 		go.optind += set_array(array, tobool(arrayset > 0),
    441 		    argv + go.optind);
    442 
    443 	return (go.optind);
    444 }
    445 
    446 /* parse a decimal number: returns 0 if string isn't a number, 1 otherwise */
    447 int
    448 getn(const char *s, int *ai)
    449 {
    450 	int i, c, rv = 0;
    451 	bool neg = false;
    452 
    453 	do {
    454 		c = *s++;
    455 	} while (ksh_isspace(c));
    456 	if (c == '-') {
    457 		neg = true;
    458 		c = *s++;
    459 	} else if (c == '+')
    460 		c = *s++;
    461 	*ai = i = 0;
    462 	do {
    463 		if (!ksh_isdigit(c))
    464 			goto getn_out;
    465 		i *= 10;
    466 		if (i < *ai)
    467 			/* overflow */
    468 			goto getn_out;
    469 		i += c - '0';
    470 		*ai = i;
    471 	} while ((c = *s++));
    472 	rv = 1;
    473 
    474  getn_out:
    475 	if (neg)
    476 		*ai = -*ai;
    477 	return (rv);
    478 }
    479 
    480 /* getn() that prints error */
    481 int
    482 bi_getn(const char *as, int *ai)
    483 {
    484 	int rv;
    485 
    486 	if (!(rv = getn(as, ai)))
    487 		bi_errorf("%s: %s", as, "bad number");
    488 	return (rv);
    489 }
    490 
    491 /**
    492  * pattern simplifications:
    493  * - @(x) -> x (not @(x|y) though)
    494  * - ** -> *
    495  */
    496 static void *
    497 simplify_gmatch_pattern(const unsigned char *sp)
    498 {
    499 	uint8_t c;
    500 	unsigned char *cp, *dp;
    501 	const unsigned char *ps, *se;
    502 
    503 	cp = alloc(strlen((const void *)sp) + 1, ATEMP);
    504 	goto simplify_gmatch_pat1a;
    505 
    506 	/* foo@(b@(a)r)b@(a|a)z -> foobarb@(a|a)z */
    507  simplify_gmatch_pat1:
    508 	sp = cp;
    509  simplify_gmatch_pat1a:
    510 	dp = cp;
    511 	se = sp + strlen((const void *)sp);
    512 	while ((c = *sp++)) {
    513 		if (!ISMAGIC(c)) {
    514 			*dp++ = c;
    515 			continue;
    516 		}
    517 		switch ((c = *sp++)) {
    518 		case 0x80|'@':
    519 		/* simile for @ */
    520 		case 0x80|' ':
    521 			/* check whether it has only one clause */
    522 			ps = pat_scan(sp, se, true);
    523 			if (!ps || ps[-1] != /*(*/ ')')
    524 				/* nope */
    525 				break;
    526 			/* copy inner clause until matching close */
    527 			ps -= 2;
    528 			while ((const unsigned char *)sp < ps)
    529 				*dp++ = *sp++;
    530 			/* skip MAGIC and closing parenthesis */
    531 			sp += 2;
    532 			/* copy the rest of the pattern */
    533 			memmove(dp, sp, strlen((const void *)sp) + 1);
    534 			/* redo from start */
    535 			goto simplify_gmatch_pat1;
    536 		}
    537 		*dp++ = MAGIC;
    538 		*dp++ = c;
    539 	}
    540 	*dp = '\0';
    541 
    542 	/* collapse adjacent asterisk wildcards */
    543 	sp = dp = cp;
    544 	while ((c = *sp++)) {
    545 		if (!ISMAGIC(c)) {
    546 			*dp++ = c;
    547 			continue;
    548 		}
    549 		switch ((c = *sp++)) {
    550 		case '*':
    551 			while (ISMAGIC(sp[0]) && sp[1] == c)
    552 				sp += 2;
    553 			break;
    554 		}
    555 		*dp++ = MAGIC;
    556 		*dp++ = c;
    557 	}
    558 	*dp = '\0';
    559 
    560 	/* return the result, allocated from ATEMP */
    561 	return (cp);
    562 }
    563 
    564 /* -------- gmatch.c -------- */
    565 
    566 /*
    567  * int gmatch(string, pattern)
    568  * char *string, *pattern;
    569  *
    570  * Match a pattern as in sh(1).
    571  * pattern character are prefixed with MAGIC by expand.
    572  */
    573 int
    574 gmatchx(const char *s, const char *p, bool isfile)
    575 {
    576 	const char *se, *pe;
    577 	char *pnew;
    578 	int rv;
    579 
    580 	if (s == NULL || p == NULL)
    581 		return (0);
    582 
    583 	se = s + strlen(s);
    584 	pe = p + strlen(p);
    585 	/*
    586 	 * isfile is false iff no syntax check has been done on
    587 	 * the pattern. If check fails, just to a strcmp().
    588 	 */
    589 	if (!isfile && !has_globbing(p, pe)) {
    590 		size_t len = pe - p + 1;
    591 		char tbuf[64];
    592 		char *t = len <= sizeof(tbuf) ? tbuf : alloc(len, ATEMP);
    593 		debunk(t, p, len);
    594 		return (!strcmp(t, s));
    595 	}
    596 
    597 	/*
    598 	 * since the do_gmatch() engine sucks so much, we must do some
    599 	 * pattern simplifications
    600 	 */
    601 	pnew = simplify_gmatch_pattern((const unsigned char *)p);
    602 	pe = pnew + strlen(pnew);
    603 
    604 	rv = do_gmatch((const unsigned char *)s, (const unsigned char *)se,
    605 	    (const unsigned char *)pnew, (const unsigned char *)pe);
    606 	afree(pnew, ATEMP);
    607 	return (rv);
    608 }
    609 
    610 /**
    611  * Returns if p is a syntacticly correct globbing pattern, false
    612  * if it contains no pattern characters or if there is a syntax error.
    613  * Syntax errors are:
    614  *	- [ with no closing ]
    615  *	- imbalanced $(...) expression
    616  *	- [...] and *(...) not nested (eg, [a$(b|]c), *(a[b|c]d))
    617  */
    618 /*XXX
    619  * - if no magic,
    620  *	if dest given, copy to dst
    621  *	return ?
    622  * - if magic && (no globbing || syntax error)
    623  *	debunk to dst
    624  *	return ?
    625  * - return ?
    626  */
    627 int
    628 has_globbing(const char *xp, const char *xpe)
    629 {
    630 	const unsigned char *p = (const unsigned char *) xp;
    631 	const unsigned char *pe = (const unsigned char *) xpe;
    632 	int c;
    633 	int nest = 0, bnest = 0;
    634 	bool saw_glob = false;
    635 	/* inside [...] */
    636 	bool in_bracket = false;
    637 
    638 	for (; p < pe; p++) {
    639 		if (!ISMAGIC(*p))
    640 			continue;
    641 		if ((c = *++p) == '*' || c == '?')
    642 			saw_glob = true;
    643 		else if (c == '[') {
    644 			if (!in_bracket) {
    645 				saw_glob = true;
    646 				in_bracket = true;
    647 				if (ISMAGIC(p[1]) && p[2] == NOT)
    648 					p += 2;
    649 				if (ISMAGIC(p[1]) && p[2] == ']')
    650 					p += 2;
    651 			}
    652 			/*XXX Do we need to check ranges here? POSIX Q */
    653 		} else if (c == ']') {
    654 			if (in_bracket) {
    655 				if (bnest)
    656 					/* [a*(b]) */
    657 					return (0);
    658 				in_bracket = false;
    659 			}
    660 		} else if ((c & 0x80) && vstrchr("*+?@! ", c & 0x7f)) {
    661 			saw_glob = true;
    662 			if (in_bracket)
    663 				bnest++;
    664 			else
    665 				nest++;
    666 		} else if (c == '|') {
    667 			if (in_bracket && !bnest)
    668 				/* *(a[foo|bar]) */
    669 				return (0);
    670 		} else if (c == /*(*/ ')') {
    671 			if (in_bracket) {
    672 				if (!bnest--)
    673 					/* *(a[b)c] */
    674 					return (0);
    675 			} else if (nest)
    676 				nest--;
    677 		}
    678 		/*
    679 		 * else must be a MAGIC-MAGIC, or MAGIC-!,
    680 		 * MAGIC--, MAGIC-], MAGIC-{, MAGIC-, MAGIC-}
    681 		 */
    682 	}
    683 	return (saw_glob && !in_bracket && !nest);
    684 }
    685 
    686 /* Function must return either 0 or 1 (assumed by code for 0x80|'!') */
    687 static int
    688 do_gmatch(const unsigned char *s, const unsigned char *se,
    689     const unsigned char *p, const unsigned char *pe)
    690 {
    691 	int sc, pc;
    692 	const unsigned char *prest, *psub, *pnext;
    693 	const unsigned char *srest;
    694 
    695 	if (s == NULL || p == NULL)
    696 		return (0);
    697 	while (p < pe) {
    698 		pc = *p++;
    699 		sc = s < se ? *s : '\0';
    700 		s++;
    701 		if (!ISMAGIC(pc)) {
    702 			if (sc != pc)
    703 				return (0);
    704 			continue;
    705 		}
    706 		switch (*p++) {
    707 		case '[':
    708 			if (sc == 0 || (p = cclass(p, sc)) == NULL)
    709 				return (0);
    710 			break;
    711 
    712 		case '?':
    713 			if (sc == 0)
    714 				return (0);
    715 			if (UTFMODE) {
    716 				--s;
    717 				s += utf_ptradj((const void *)s);
    718 			}
    719 			break;
    720 
    721 		case '*':
    722 			if (p == pe)
    723 				return (1);
    724 			s--;
    725 			do {
    726 				if (do_gmatch(s, se, p, pe))
    727 					return (1);
    728 			} while (s++ < se);
    729 			return (0);
    730 
    731 		/**
    732 		 * [*+?@!](pattern|pattern|..)
    733 		 * This is also needed for ${..%..}, etc.
    734 		 */
    735 
    736 		/* matches one or more times */
    737 		case 0x80|'+':
    738 		/* matches zero or more times */
    739 		case 0x80|'*':
    740 			if (!(prest = pat_scan(p, pe, false)))
    741 				return (0);
    742 			s--;
    743 			/* take care of zero matches */
    744 			if (p[-1] == (0x80 | '*') &&
    745 			    do_gmatch(s, se, prest, pe))
    746 				return (1);
    747 			for (psub = p; ; psub = pnext) {
    748 				pnext = pat_scan(psub, pe, true);
    749 				for (srest = s; srest <= se; srest++) {
    750 					if (do_gmatch(s, srest, psub, pnext - 2) &&
    751 					    (do_gmatch(srest, se, prest, pe) ||
    752 					    (s != srest && do_gmatch(srest,
    753 					    se, p - 2, pe))))
    754 						return (1);
    755 				}
    756 				if (pnext == prest)
    757 					break;
    758 			}
    759 			return (0);
    760 
    761 		/* matches zero or once */
    762 		case 0x80|'?':
    763 		/* matches one of the patterns */
    764 		case 0x80|'@':
    765 		/* simile for @ */
    766 		case 0x80|' ':
    767 			if (!(prest = pat_scan(p, pe, false)))
    768 				return (0);
    769 			s--;
    770 			/* Take care of zero matches */
    771 			if (p[-1] == (0x80 | '?') &&
    772 			    do_gmatch(s, se, prest, pe))
    773 				return (1);
    774 			for (psub = p; ; psub = pnext) {
    775 				pnext = pat_scan(psub, pe, true);
    776 				srest = prest == pe ? se : s;
    777 				for (; srest <= se; srest++) {
    778 					if (do_gmatch(s, srest, psub, pnext - 2) &&
    779 					    do_gmatch(srest, se, prest, pe))
    780 						return (1);
    781 				}
    782 				if (pnext == prest)
    783 					break;
    784 			}
    785 			return (0);
    786 
    787 		/* matches none of the patterns */
    788 		case 0x80|'!':
    789 			if (!(prest = pat_scan(p, pe, false)))
    790 				return (0);
    791 			s--;
    792 			for (srest = s; srest <= se; srest++) {
    793 				int matched = 0;
    794 
    795 				for (psub = p; ; psub = pnext) {
    796 					pnext = pat_scan(psub, pe, true);
    797 					if (do_gmatch(s, srest, psub,
    798 					    pnext - 2)) {
    799 						matched = 1;
    800 						break;
    801 					}
    802 					if (pnext == prest)
    803 						break;
    804 				}
    805 				if (!matched &&
    806 				    do_gmatch(srest, se, prest, pe))
    807 					return (1);
    808 			}
    809 			return (0);
    810 
    811 		default:
    812 			if (sc != p[-1])
    813 				return (0);
    814 			break;
    815 		}
    816 	}
    817 	return (s == se);
    818 }
    819 
    820 static const unsigned char *
    821 cclass(const unsigned char *p, int sub)
    822 {
    823 	int c, d, notp, found = 0;
    824 	const unsigned char *orig_p = p;
    825 
    826 	if ((notp = (ISMAGIC(*p) && *++p == NOT)))
    827 		p++;
    828 	do {
    829 		c = *p++;
    830 		if (ISMAGIC(c)) {
    831 			c = *p++;
    832 			if ((c & 0x80) && !ISMAGIC(c)) {
    833 				/* extended pattern matching: *+?@! */
    834 				c &= 0x7F;
    835 				/* XXX the ( char isn't handled as part of [] */
    836 				if (c == ' ')
    837 					/* simile for @: plain (..) */
    838 					c = '(' /*)*/;
    839 			}
    840 		}
    841 		if (c == '\0')
    842 			/* No closing ] - act as if the opening [ was quoted */
    843 			return (sub == '[' ? orig_p : NULL);
    844 		if (ISMAGIC(p[0]) && p[1] == '-' &&
    845 		    (!ISMAGIC(p[2]) || p[3] != ']')) {
    846 			/* MAGIC- */
    847 			p += 2;
    848 			d = *p++;
    849 			if (ISMAGIC(d)) {
    850 				d = *p++;
    851 				if ((d & 0x80) && !ISMAGIC(d))
    852 					d &= 0x7f;
    853 			}
    854 			/* POSIX says this is an invalid expression */
    855 			if (c > d)
    856 				return (NULL);
    857 		} else
    858 			d = c;
    859 		if (c == sub || (c <= sub && sub <= d))
    860 			found = 1;
    861 	} while (!(ISMAGIC(p[0]) && p[1] == ']'));
    862 
    863 	return ((found != notp) ? p+2 : NULL);
    864 }
    865 
    866 /* Look for next ) or | (if match_sep) in *(foo|bar) pattern */
    867 static const unsigned char *
    868 pat_scan(const unsigned char *p, const unsigned char *pe, bool match_sep)
    869 {
    870 	int nest = 0;
    871 
    872 	for (; p < pe; p++) {
    873 		if (!ISMAGIC(*p))
    874 			continue;
    875 		if ((*++p == /*(*/ ')' && nest-- == 0) ||
    876 		    (*p == '|' && match_sep && nest == 0))
    877 			return (p + 1);
    878 		if ((*p & 0x80) && vstrchr("*+?@! ", *p & 0x7f))
    879 			nest++;
    880 	}
    881 	return (NULL);
    882 }
    883 
    884 int
    885 xstrcmp(const void *p1, const void *p2)
    886 {
    887 	return (strcmp(*(const char * const *)p1, *(const char * const *)p2));
    888 }
    889 
    890 /* Initialise a Getopt structure */
    891 void
    892 ksh_getopt_reset(Getopt *go, int flags)
    893 {
    894 	go->optind = 1;
    895 	go->optarg = NULL;
    896 	go->p = 0;
    897 	go->flags = flags;
    898 	go->info = 0;
    899 	go->buf[1] = '\0';
    900 }
    901 
    902 
    903 /**
    904  * getopt() used for shell built-in commands, the getopts command, and
    905  * command line options.
    906  * A leading ':' in options means don't print errors, instead return '?'
    907  * or ':' and set go->optarg to the offending option character.
    908  * If GF_ERROR is set (and option doesn't start with :), errors result in
    909  * a call to bi_errorf().
    910  *
    911  * Non-standard features:
    912  *	- ';' is like ':' in options, except the argument is optional
    913  *	  (if it isn't present, optarg is set to 0).
    914  *	  Used for 'set -o'.
    915  *	- ',' is like ':' in options, except the argument always immediately
    916  *	  follows the option character (optarg is set to the null string if
    917  *	  the option is missing).
    918  *	  Used for 'read -u2', 'print -u2' and fc -40.
    919  *	- '#' is like ':' in options, expect that the argument is optional
    920  *	  and must start with a digit. If the argument doesn't start with a
    921  *	  digit, it is assumed to be missing and normal option processing
    922  *	  continues (optarg is set to 0 if the option is missing).
    923  *	  Used for 'typeset -LZ4'.
    924  *	- accepts +c as well as -c IF the GF_PLUSOPT flag is present. If an
    925  *	  option starting with + is accepted, the GI_PLUS flag will be set
    926  *	  in go->info.
    927  */
    928 int
    929 ksh_getopt(const char **argv, Getopt *go, const char *optionsp)
    930 {
    931 	char c;
    932 	const char *o;
    933 
    934 	if (go->p == 0 || (c = argv[go->optind - 1][go->p]) == '\0') {
    935 		const char *arg = argv[go->optind], flag = arg ? *arg : '\0';
    936 
    937 		go->p = 1;
    938 		if (flag == '-' && arg[1] == '-' && arg[2] == '\0') {
    939 			go->optind++;
    940 			go->p = 0;
    941 			go->info |= GI_MINUSMINUS;
    942 			return (-1);
    943 		}
    944 		if (arg == NULL ||
    945 		    ((flag != '-' ) &&
    946 		    /* neither a - nor a + (if + allowed) */
    947 		    (!(go->flags & GF_PLUSOPT) || flag != '+')) ||
    948 		    (c = arg[1]) == '\0') {
    949 			go->p = 0;
    950 			return (-1);
    951 		}
    952 		go->optind++;
    953 		go->info &= ~(GI_MINUS|GI_PLUS);
    954 		go->info |= flag == '-' ? GI_MINUS : GI_PLUS;
    955 	}
    956 	go->p++;
    957 	if (c == '?' || c == ':' || c == ';' || c == ',' || c == '#' ||
    958 	    !(o = cstrchr(optionsp, c))) {
    959 		if (optionsp[0] == ':') {
    960 			go->buf[0] = c;
    961 			go->optarg = go->buf;
    962 		} else {
    963 			warningf(true, "%s%s-%c: %s",
    964 			    (go->flags & GF_NONAME) ? "" : argv[0],
    965 			    (go->flags & GF_NONAME) ? "" : ": ", c,
    966 			    "unknown option");
    967 			if (go->flags & GF_ERROR)
    968 				bi_errorfz();
    969 		}
    970 		return ('?');
    971 	}
    972 	/**
    973 	 * : means argument must be present, may be part of option argument
    974 	 *   or the next argument
    975 	 * ; same as : but argument may be missing
    976 	 * , means argument is part of option argument, and may be null.
    977 	 */
    978 	if (*++o == ':' || *o == ';') {
    979 		if (argv[go->optind - 1][go->p])
    980 			go->optarg = argv[go->optind - 1] + go->p;
    981 		else if (argv[go->optind])
    982 			go->optarg = argv[go->optind++];
    983 		else if (*o == ';')
    984 			go->optarg = NULL;
    985 		else {
    986 			if (optionsp[0] == ':') {
    987 				go->buf[0] = c;
    988 				go->optarg = go->buf;
    989 				return (':');
    990 			}
    991 			warningf(true, "%s%s-%c: %s",
    992 			    (go->flags & GF_NONAME) ? "" : argv[0],
    993 			    (go->flags & GF_NONAME) ? "" : ": ", c,
    994 			    "requires an argument");
    995 			if (go->flags & GF_ERROR)
    996 				bi_errorfz();
    997 			return ('?');
    998 		}
    999 		go->p = 0;
   1000 	} else if (*o == ',') {
   1001 		/* argument is attached to option character, even if null */
   1002 		go->optarg = argv[go->optind - 1] + go->p;
   1003 		go->p = 0;
   1004 	} else if (*o == '#') {
   1005 		/*
   1006 		 * argument is optional and may be attached or unattached
   1007 		 * but must start with a digit. optarg is set to 0 if the
   1008 		 * argument is missing.
   1009 		 */
   1010 		if (argv[go->optind - 1][go->p]) {
   1011 			if (ksh_isdigit(argv[go->optind - 1][go->p])) {
   1012 				go->optarg = argv[go->optind - 1] + go->p;
   1013 				go->p = 0;
   1014 			} else
   1015 				go->optarg = NULL;
   1016 		} else {
   1017 			if (argv[go->optind] && ksh_isdigit(argv[go->optind][0])) {
   1018 				go->optarg = argv[go->optind++];
   1019 				go->p = 0;
   1020 			} else
   1021 				go->optarg = NULL;
   1022 		}
   1023 	}
   1024 	return (c);
   1025 }
   1026 
   1027 /*
   1028  * print variable/alias value using necessary quotes
   1029  * (POSIX says they should be suitable for re-entry...)
   1030  * No trailing newline is printed.
   1031  */
   1032 void
   1033 print_value_quoted(const char *s)
   1034 {
   1035 	const char *p;
   1036 	bool inquote = false;
   1037 
   1038 	/* first, check whether any quotes are needed */
   1039 	for (p = s; *p; p++)
   1040 		if (ctype(*p, C_QUOTE))
   1041 			break;
   1042 	if (!*p) {
   1043 		/* nope, use the shortcut */
   1044 		shf_puts(s, shl_stdout);
   1045 		return;
   1046 	}
   1047 
   1048 	/* quote via state machine */
   1049 	for (p = s; *p; p++) {
   1050 		if (*p == '\'') {
   1051 			/*
   1052 			 * multiple '''s or any ' at beginning of string
   1053 			 * look nicer this way than when simply substituting
   1054 			 */
   1055 			if (inquote) {
   1056 				shf_putc('\'', shl_stdout);
   1057 				inquote = false;
   1058 			}
   1059 			shf_putc('\\', shl_stdout);
   1060 		} else if (!inquote) {
   1061 			shf_putc('\'', shl_stdout);
   1062 			inquote = true;
   1063 		}
   1064 		shf_putc(*p, shl_stdout);
   1065 	}
   1066 	if (inquote)
   1067 		shf_putc('\'', shl_stdout);
   1068 }
   1069 
   1070 /*
   1071  * Print things in columns and rows - func() is called to format
   1072  * the i-th element
   1073  */
   1074 void
   1075 print_columns(struct shf *shf, int n,
   1076     char *(*func)(char *, size_t, int, const void *),
   1077     const void *arg, size_t max_oct, size_t max_colz, bool prefcol)
   1078 {
   1079 	int i, r, c, rows, cols, nspace, max_col;
   1080 	char *str;
   1081 
   1082 	if (n <= 0) {
   1083 #ifndef MKSH_SMALL
   1084 		internal_warningf("print_columns called with n=%d <= 0", n);
   1085 #endif
   1086 		return;
   1087 	}
   1088 
   1089 	if (max_colz > 2147483647) {
   1090 #ifndef MKSH_SMALL
   1091 		internal_warningf("print_columns called with max_col=%zu > INT_MAX",
   1092 		    max_colz);
   1093 #endif
   1094 		return;
   1095 	}
   1096 	max_col = (int)max_colz;
   1097 
   1098 	++max_oct;
   1099 	str = alloc(max_oct, ATEMP);
   1100 
   1101 	/* ensure x_cols is valid first */
   1102 	if (x_cols < MIN_COLS)
   1103 		change_winsz();
   1104 
   1105 	/*
   1106 	 * We use (max_col + 1) to consider the space separator.
   1107 	 * Note that no space is printed after the last column
   1108 	 * to avoid problems with terminals that have auto-wrap.
   1109 	 */
   1110 	cols = x_cols / (max_col + 1);
   1111 
   1112 	/* if we can only print one column anyway, skip the goo */
   1113 	if (cols < 2) {
   1114 		for (i = 0; i < n; ++i)
   1115 			shf_fprintf(shf, "%s \n",
   1116 			    (*func)(str, max_oct, i, arg));
   1117 		goto out;
   1118 	}
   1119 
   1120 	rows = (n + cols - 1) / cols;
   1121 	if (prefcol && cols > rows) {
   1122 		i = rows;
   1123 		rows = cols > n ? n : cols;
   1124 		cols = i;
   1125 	}
   1126 
   1127 	max_col = -max_col;
   1128 	nspace = (x_cols + max_col * cols) / cols;
   1129 	if (nspace <= 0)
   1130 		nspace = 1;
   1131 	for (r = 0; r < rows; r++) {
   1132 		for (c = 0; c < cols; c++) {
   1133 			i = c * rows + r;
   1134 			if (i < n) {
   1135 				shf_fprintf(shf, "%*s", max_col,
   1136 				    (*func)(str, max_oct, i, arg));
   1137 				if (c + 1 < cols)
   1138 					shf_fprintf(shf, "%*s", nspace, null);
   1139 			}
   1140 		}
   1141 		shf_putchar('\n', shf);
   1142 	}
   1143  out:
   1144 	afree(str, ATEMP);
   1145 }
   1146 
   1147 /* Strip any nul bytes from buf - returns new length (nbytes - # of nuls) */
   1148 void
   1149 strip_nuls(char *buf, int nbytes)
   1150 {
   1151 	char *dst;
   1152 
   1153 	/*
   1154 	 * nbytes check because some systems (older FreeBSDs) have a
   1155 	 * buggy memchr()
   1156 	 */
   1157 	if (nbytes && (dst = memchr(buf, '\0', nbytes))) {
   1158 		char *end = buf + nbytes;
   1159 		char *p, *q;
   1160 
   1161 		for (p = dst; p < end; p = q) {
   1162 			/* skip a block of nulls */
   1163 			while (++p < end && *p == '\0')
   1164 				;
   1165 			/* find end of non-null block */
   1166 			if (!(q = memchr(p, '\0', end - p)))
   1167 				q = end;
   1168 			memmove(dst, p, q - p);
   1169 			dst += q - p;
   1170 		}
   1171 		*dst = '\0';
   1172 	}
   1173 }
   1174 
   1175 /*
   1176  * Like read(2), but if read fails due to non-blocking flag,
   1177  * resets flag and restarts read.
   1178  */
   1179 ssize_t
   1180 blocking_read(int fd, char *buf, size_t nbytes)
   1181 {
   1182 	ssize_t ret;
   1183 	bool tried_reset = false;
   1184 
   1185 	while ((ret = read(fd, buf, nbytes)) < 0) {
   1186 		if (!tried_reset && errno == EAGAIN) {
   1187 			if (reset_nonblock(fd) > 0) {
   1188 				tried_reset = true;
   1189 				continue;
   1190 			}
   1191 			errno = EAGAIN;
   1192 		}
   1193 		break;
   1194 	}
   1195 	return (ret);
   1196 }
   1197 
   1198 /*
   1199  * Reset the non-blocking flag on the specified file descriptor.
   1200  * Returns -1 if there was an error, 0 if non-blocking wasn't set,
   1201  * 1 if it was.
   1202  */
   1203 int
   1204 reset_nonblock(int fd)
   1205 {
   1206 	int flags;
   1207 
   1208 	if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
   1209 		return (-1);
   1210 	if (!(flags & O_NONBLOCK))
   1211 		return (0);
   1212 	flags &= ~O_NONBLOCK;
   1213 	if (fcntl(fd, F_SETFL, flags) < 0)
   1214 		return (-1);
   1215 	return (1);
   1216 }
   1217 
   1218 /* getcwd(3) equivalent, allocates from ATEMP but doesn't resize */
   1219 char *
   1220 ksh_get_wd(void)
   1221 {
   1222 #ifdef NO_PATH_MAX
   1223 	char *rv, *cp;
   1224 
   1225 	if ((cp = get_current_dir_name())) {
   1226 		strdupx(rv, cp, ATEMP);
   1227 		free_gnu_gcdn(cp);
   1228 	} else
   1229 		rv = NULL;
   1230 #else
   1231 	char *rv;
   1232 
   1233 	if (!getcwd((rv = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX)) {
   1234 		afree(rv, ATEMP);
   1235 		rv = NULL;
   1236 	}
   1237 #endif
   1238 
   1239 	return (rv);
   1240 }
   1241 
   1242 char *
   1243 do_realpath(const char *upath)
   1244 {
   1245 	char *xp, *ip, *tp, *ipath, *ldest = NULL;
   1246 	XString xs;
   1247 	ptrdiff_t pos;
   1248 	size_t len;
   1249 	int llen;
   1250 	struct stat sb;
   1251 #ifdef NO_PATH_MAX
   1252 	size_t ldestlen = 0;
   1253 #define pathlen sb.st_size
   1254 #define pathcnd (ldestlen < (pathlen + 1))
   1255 #else
   1256 #define pathlen PATH_MAX
   1257 #define pathcnd (!ldest)
   1258 #endif
   1259 	/* max. recursion depth */
   1260 	int symlinks = 32;
   1261 
   1262 	if (upath[0] == '/') {
   1263 		/* upath is an absolute pathname */
   1264 		strdupx(ipath, upath, ATEMP);
   1265 	} else {
   1266 		/* upath is a relative pathname, prepend cwd */
   1267 		if ((tp = ksh_get_wd()) == NULL || tp[0] != '/')
   1268 			return (NULL);
   1269 		ipath = shf_smprintf("%s%s%s", tp, "/", upath);
   1270 		afree(tp, ATEMP);
   1271 	}
   1272 
   1273 	/* ipath and upath are in memory at the same time -> unchecked */
   1274 	Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP);
   1275 
   1276 	/* now jump into the deep of the loop */
   1277 	goto beginning_of_a_pathname;
   1278 
   1279 	while (*ip) {
   1280 		/* skip slashes in input */
   1281 		while (*ip == '/')
   1282 			++ip;
   1283 		if (!*ip)
   1284 			break;
   1285 
   1286 		/* get next pathname component from input */
   1287 		tp = ip;
   1288 		while (*ip && *ip != '/')
   1289 			++ip;
   1290 		len = ip - tp;
   1291 
   1292 		/* check input for "." and ".." */
   1293 		if (tp[0] == '.') {
   1294 			if (len == 1)
   1295 				/* just continue with the next one */
   1296 				continue;
   1297 			else if (len == 2 && tp[1] == '.') {
   1298 				/* strip off last pathname component */
   1299 				while (xp > Xstring(xs, xp))
   1300 					if (*--xp == '/')
   1301 						break;
   1302 				/* then continue with the next one */
   1303 				continue;
   1304 			}
   1305 		}
   1306 
   1307 		/* store output position away, then append slash to output */
   1308 		pos = Xsavepos(xs, xp);
   1309 		/* 1 for the '/' and len + 1 for tp and the NUL from below */
   1310 		XcheckN(xs, xp, 1 + len + 1);
   1311 		Xput(xs, xp, '/');
   1312 
   1313 		/* append next pathname component to output */
   1314 		memcpy(xp, tp, len);
   1315 		xp += len;
   1316 		*xp = '\0';
   1317 
   1318 		/* lstat the current output, see if it's a symlink */
   1319 		if (lstat(Xstring(xs, xp), &sb)) {
   1320 			/* lstat failed */
   1321 			if (errno == ENOENT) {
   1322 				/* because the pathname does not exist */
   1323 				while (*ip == '/')
   1324 					/* skip any trailing slashes */
   1325 					++ip;
   1326 				/* no more components left? */
   1327 				if (!*ip)
   1328 					/* we can still return successfully */
   1329 					break;
   1330 				/* more components left? fall through */
   1331 			}
   1332 			/* not ENOENT or not at the end of ipath */
   1333 			goto notfound;
   1334 		}
   1335 
   1336 		/* check if we encountered a symlink? */
   1337 		if (S_ISLNK(sb.st_mode)) {
   1338 			/* reached maximum recursion depth? */
   1339 			if (!symlinks--) {
   1340 				/* yep, prevent infinite loops */
   1341 				errno = ELOOP;
   1342 				goto notfound;
   1343 			}
   1344 
   1345 			/* get symlink(7) target */
   1346 			if (pathcnd) {
   1347 #ifdef NO_PATH_MAX
   1348 				if (notoktoadd(pathlen, 1)) {
   1349 					errno = ENAMETOOLONG;
   1350 					goto notfound;
   1351 				}
   1352 #endif
   1353 				ldest = aresize(ldest, pathlen + 1, ATEMP);
   1354 			}
   1355 			llen = readlink(Xstring(xs, xp), ldest, pathlen);
   1356 			if (llen < 0)
   1357 				/* oops... */
   1358 				goto notfound;
   1359 			ldest[llen] = '\0';
   1360 
   1361 			/*
   1362 			 * restart if symlink target is an absolute path,
   1363 			 * otherwise continue with currently resolved prefix
   1364 			 */
   1365 			/* append rest of current input path to link target */
   1366 			tp = shf_smprintf("%s%s%s", ldest, *ip ? "/" : "", ip);
   1367 			afree(ipath, ATEMP);
   1368 			ip = ipath = tp;
   1369 			if (ldest[0] != '/') {
   1370 				/* symlink target is a relative path */
   1371 				xp = Xrestpos(xs, xp, pos);
   1372 			} else {
   1373 				/* symlink target is an absolute path */
   1374 				xp = Xstring(xs, xp);
   1375  beginning_of_a_pathname:
   1376 				/* assert: (ip == ipath)[0] == '/' */
   1377 				/* assert: xp == xs.beg => start of path */
   1378 
   1379 				/* exactly two leading slashes? (SUSv4 3.266) */
   1380 				if (ip[1] == '/' && ip[2] != '/') {
   1381 					/* keep them, e.g. for UNC pathnames */
   1382 					Xput(xs, xp, '/');
   1383 				}
   1384 			}
   1385 		}
   1386 		/* otherwise (no symlink) merely go on */
   1387 	}
   1388 
   1389 	/*
   1390 	 * either found the target and successfully resolved it,
   1391 	 * or found its parent directory and may create it
   1392 	 */
   1393 	if (Xlength(xs, xp) == 0)
   1394 		/*
   1395 		 * if the resolved pathname is "", make it "/",
   1396 		 * otherwise do not add a trailing slash
   1397 		 */
   1398 		Xput(xs, xp, '/');
   1399 	Xput(xs, xp, '\0');
   1400 
   1401 	/*
   1402 	 * if source path had a trailing slash, check if target path
   1403 	 * is not a non-directory existing file
   1404 	 */
   1405 	if (ip > ipath && ip[-1] == '/') {
   1406 		if (stat(Xstring(xs, xp), &sb)) {
   1407 			if (errno != ENOENT)
   1408 				goto notfound;
   1409 		} else if (!S_ISDIR(sb.st_mode)) {
   1410 			errno = ENOTDIR;
   1411 			goto notfound;
   1412 		}
   1413 		/* target now either does not exist or is a directory */
   1414 	}
   1415 
   1416 	/* return target path */
   1417 	if (ldest != NULL)
   1418 		afree(ldest, ATEMP);
   1419 	afree(ipath, ATEMP);
   1420 	return (Xclose(xs, xp));
   1421 
   1422  notfound:
   1423 	/* save; freeing memory might trash it */
   1424 	llen = errno;
   1425 	if (ldest != NULL)
   1426 		afree(ldest, ATEMP);
   1427 	afree(ipath, ATEMP);
   1428 	Xfree(xs, xp);
   1429 	errno = llen;
   1430 	return (NULL);
   1431 
   1432 #undef pathlen
   1433 #undef pathcnd
   1434 }
   1435 
   1436 /**
   1437  *	Makes a filename into result using the following algorithm.
   1438  *	- make result NULL
   1439  *	- if file starts with '/', append file to result & set cdpathp to NULL
   1440  *	- if file starts with ./ or ../ append cwd and file to result
   1441  *	  and set cdpathp to NULL
   1442  *	- if the first element of cdpathp doesnt start with a '/' xx or '.' xx
   1443  *	  then cwd is appended to result.
   1444  *	- the first element of cdpathp is appended to result
   1445  *	- file is appended to result
   1446  *	- cdpathp is set to the start of the next element in cdpathp (or NULL
   1447  *	  if there are no more elements.
   1448  *	The return value indicates whether a non-null element from cdpathp
   1449  *	was appended to result.
   1450  */
   1451 static int
   1452 make_path(const char *cwd, const char *file,
   1453     /* pointer to colon-separated list */
   1454     char **cdpathp,
   1455     XString *xsp,
   1456     int *phys_pathp)
   1457 {
   1458 	int rval = 0;
   1459 	bool use_cdpath = true;
   1460 	char *plist;
   1461 	size_t len, plen = 0;
   1462 	char *xp = Xstring(*xsp, xp);
   1463 
   1464 	if (!file)
   1465 		file = null;
   1466 
   1467 	if (file[0] == '/') {
   1468 		*phys_pathp = 0;
   1469 		use_cdpath = false;
   1470 	} else {
   1471 		if (file[0] == '.') {
   1472 			char c = file[1];
   1473 
   1474 			if (c == '.')
   1475 				c = file[2];
   1476 			if (c == '/' || c == '\0')
   1477 				use_cdpath = false;
   1478 		}
   1479 
   1480 		plist = *cdpathp;
   1481 		if (!plist)
   1482 			use_cdpath = false;
   1483 		else if (use_cdpath) {
   1484 			char *pend;
   1485 
   1486 			for (pend = plist; *pend && *pend != ':'; pend++)
   1487 				;
   1488 			plen = pend - plist;
   1489 			*cdpathp = *pend ? pend + 1 : NULL;
   1490 		}
   1491 
   1492 		if ((!use_cdpath || !plen || plist[0] != '/') &&
   1493 		    (cwd && *cwd)) {
   1494 			len = strlen(cwd);
   1495 			XcheckN(*xsp, xp, len);
   1496 			memcpy(xp, cwd, len);
   1497 			xp += len;
   1498 			if (cwd[len - 1] != '/')
   1499 				Xput(*xsp, xp, '/');
   1500 		}
   1501 		*phys_pathp = Xlength(*xsp, xp);
   1502 		if (use_cdpath && plen) {
   1503 			XcheckN(*xsp, xp, plen);
   1504 			memcpy(xp, plist, plen);
   1505 			xp += plen;
   1506 			if (plist[plen - 1] != '/')
   1507 				Xput(*xsp, xp, '/');
   1508 			rval = 1;
   1509 		}
   1510 	}
   1511 
   1512 	len = strlen(file) + 1;
   1513 	XcheckN(*xsp, xp, len);
   1514 	memcpy(xp, file, len);
   1515 
   1516 	if (!use_cdpath)
   1517 		*cdpathp = NULL;
   1518 
   1519 	return (rval);
   1520 }
   1521 
   1522 /*-
   1523  * Simplify pathnames containing "." and ".." entries.
   1524  *
   1525  * simplify_path(this)			= that
   1526  * /a/b/c/./../d/..			/a/b
   1527  * //./C/foo/bar/../baz			//C/foo/baz
   1528  * /foo/				/foo
   1529  * /foo/../../bar			/bar
   1530  * /foo/./blah/..			/foo
   1531  * .					.
   1532  * ..					..
   1533  * ./foo				foo
   1534  * foo/../../../bar			../../bar
   1535  */
   1536 void
   1537 simplify_path(char *p)
   1538 {
   1539 	char *dp, *ip, *sp, *tp;
   1540 	size_t len;
   1541 	bool needslash;
   1542 
   1543 	switch (*p) {
   1544 	case 0:
   1545 		return;
   1546 	case '/':
   1547 		/* exactly two leading slashes? (SUSv4 3.266) */
   1548 		if (p[1] == '/' && p[2] != '/')
   1549 			/* keep them, e.g. for UNC pathnames */
   1550 			++p;
   1551 		needslash = true;
   1552 		break;
   1553 	default:
   1554 		needslash = false;
   1555 	}
   1556 	dp = ip = sp = p;
   1557 
   1558 	while (*ip) {
   1559 		/* skip slashes in input */
   1560 		while (*ip == '/')
   1561 			++ip;
   1562 		if (!*ip)
   1563 			break;
   1564 
   1565 		/* get next pathname component from input */
   1566 		tp = ip;
   1567 		while (*ip && *ip != '/')
   1568 			++ip;
   1569 		len = ip - tp;
   1570 
   1571 		/* check input for "." and ".." */
   1572 		if (tp[0] == '.') {
   1573 			if (len == 1)
   1574 				/* just continue with the next one */
   1575 				continue;
   1576 			else if (len == 2 && tp[1] == '.') {
   1577 				/* parent level, but how? */
   1578 				if (*p == '/')
   1579 					/* absolute path, only one way */
   1580 					goto strip_last_component;
   1581 				else if (dp > sp) {
   1582 					/* relative path, with subpaths */
   1583 					needslash = false;
   1584  strip_last_component:
   1585 					/* strip off last pathname component */
   1586 					while (dp > sp)
   1587 						if (*--dp == '/')
   1588 							break;
   1589 				} else {
   1590 					/* relative path, at its beginning */
   1591 					if (needslash)
   1592 						/* or already dotdot-slash'd */
   1593 						*dp++ = '/';
   1594 					/* keep dotdot-slash if not absolute */
   1595 					*dp++ = '.';
   1596 					*dp++ = '.';
   1597 					needslash = true;
   1598 					sp = dp;
   1599 				}
   1600 				/* then continue with the next one */
   1601 				continue;
   1602 			}
   1603 		}
   1604 
   1605 		if (needslash)
   1606 			*dp++ = '/';
   1607 
   1608 		/* append next pathname component to output */
   1609 		memmove(dp, tp, len);
   1610 		dp += len;
   1611 
   1612 		/* append slash if we continue */
   1613 		needslash = true;
   1614 		/* try next component */
   1615 	}
   1616 	if (dp == p)
   1617 		/* empty path -> dot */
   1618 		*dp++ = needslash ? '/' : '.';
   1619 	*dp = '\0';
   1620 }
   1621 
   1622 void
   1623 set_current_wd(const char *nwd)
   1624 {
   1625 	char *allocd = NULL;
   1626 
   1627 	if (nwd == NULL) {
   1628 		allocd = ksh_get_wd();
   1629 		nwd = allocd ? allocd : null;
   1630 	}
   1631 
   1632 	afree(current_wd, APERM);
   1633 	strdupx(current_wd, nwd, APERM);
   1634 
   1635 	afree(allocd, ATEMP);
   1636 }
   1637 
   1638 int
   1639 c_cd(const char **wp)
   1640 {
   1641 	int optc, rv, phys_path;
   1642 	bool physical = tobool(Flag(FPHYSICAL));
   1643 	/* was a node from cdpath added in? */
   1644 	int cdnode;
   1645 	/* show where we went?, error for $PWD */
   1646 	bool printpath = false, eflag = false;
   1647 	struct tbl *pwd_s, *oldpwd_s;
   1648 	XString xs;
   1649 	char *dir, *allocd = NULL, *tryp, *pwd, *cdpath;
   1650 
   1651 	while ((optc = ksh_getopt(wp, &builtin_opt, "eLP")) != -1)
   1652 		switch (optc) {
   1653 		case 'e':
   1654 			eflag = true;
   1655 			break;
   1656 		case 'L':
   1657 			physical = false;
   1658 			break;
   1659 		case 'P':
   1660 			physical = true;
   1661 			break;
   1662 		case '?':
   1663 			return (2);
   1664 		}
   1665 	wp += builtin_opt.optind;
   1666 
   1667 	if (Flag(FRESTRICTED)) {
   1668 		bi_errorf("restricted shell - can't cd");
   1669 		return (2);
   1670 	}
   1671 
   1672 	pwd_s = global("PWD");
   1673 	oldpwd_s = global("OLDPWD");
   1674 
   1675 	if (!wp[0]) {
   1676 		/* No arguments - go home */
   1677 		if ((dir = str_val(global("HOME"))) == null) {
   1678 			bi_errorf("no home directory (HOME not set)");
   1679 			return (2);
   1680 		}
   1681 	} else if (!wp[1]) {
   1682 		/* One argument: - or dir */
   1683 		strdupx(allocd, wp[0], ATEMP);
   1684 		if (ksh_isdash((dir = allocd))) {
   1685 			afree(allocd, ATEMP);
   1686 			allocd = NULL;
   1687 			dir = str_val(oldpwd_s);
   1688 			if (dir == null) {
   1689 				bi_errorf("no OLDPWD");
   1690 				return (2);
   1691 			}
   1692 			printpath = true;
   1693 		}
   1694 	} else if (!wp[2]) {
   1695 		/* Two arguments - substitute arg1 in PWD for arg2 */
   1696 		size_t ilen, olen, nlen, elen;
   1697 		char *cp;
   1698 
   1699 		if (!current_wd[0]) {
   1700 			bi_errorf("can't determine current directory");
   1701 			return (2);
   1702 		}
   1703 		/*
   1704 		 * substitute arg1 for arg2 in current path.
   1705 		 * if the first substitution fails because the cd fails
   1706 		 * we could try to find another substitution. For now
   1707 		 * we don't
   1708 		 */
   1709 		if ((cp = strstr(current_wd, wp[0])) == NULL) {
   1710 			bi_errorf("bad substitution");
   1711 			return (2);
   1712 		}
   1713 		/*-
   1714 		 * ilen = part of current_wd before wp[0]
   1715 		 * elen = part of current_wd after wp[0]
   1716 		 * because current_wd and wp[1] need to be in memory at the
   1717 		 * same time beforehand the addition can stay unchecked
   1718 		 */
   1719 		ilen = cp - current_wd;
   1720 		olen = strlen(wp[0]);
   1721 		nlen = strlen(wp[1]);
   1722 		elen = strlen(current_wd + ilen + olen) + 1;
   1723 		dir = allocd = alloc(ilen + nlen + elen, ATEMP);
   1724 		memcpy(dir, current_wd, ilen);
   1725 		memcpy(dir + ilen, wp[1], nlen);
   1726 		memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen);
   1727 		printpath = true;
   1728 	} else {
   1729 		bi_errorf("too many arguments");
   1730 		return (2);
   1731 	}
   1732 
   1733 #ifdef NO_PATH_MAX
   1734 	/* only a first guess; make_path will enlarge xs if necessary */
   1735 	XinitN(xs, 1024, ATEMP);
   1736 #else
   1737 	XinitN(xs, PATH_MAX, ATEMP);
   1738 #endif
   1739 
   1740 	cdpath = str_val(global("CDPATH"));
   1741 	do {
   1742 		cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path);
   1743 		if (physical)
   1744 			rv = chdir(tryp = Xstring(xs, xp) + phys_path);
   1745 		else {
   1746 			simplify_path(Xstring(xs, xp));
   1747 			rv = chdir(tryp = Xstring(xs, xp));
   1748 		}
   1749 	} while (rv < 0 && cdpath != NULL);
   1750 
   1751 	if (rv < 0) {
   1752 		if (cdnode)
   1753 			bi_errorf("%s: %s", dir, "bad directory");
   1754 		else
   1755 			bi_errorf("%s: %s", tryp, strerror(errno));
   1756 		afree(allocd, ATEMP);
   1757 		Xfree(xs, xp);
   1758 		return (2);
   1759 	}
   1760 
   1761 	rv = 0;
   1762 
   1763 	/* allocd (above) => dir, which is no longer used */
   1764 	afree(allocd, ATEMP);
   1765 	allocd = NULL;
   1766 
   1767 	/* Clear out tracked aliases with relative paths */
   1768 	flushcom(false);
   1769 
   1770 	/*
   1771 	 * Set OLDPWD (note: unsetting OLDPWD does not disable this
   1772 	 * setting in AT&T ksh)
   1773 	 */
   1774 	if (current_wd[0])
   1775 		/* Ignore failure (happens if readonly or integer) */
   1776 		setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR);
   1777 
   1778 	if (Xstring(xs, xp)[0] != '/') {
   1779 		pwd = NULL;
   1780 	} else if (!physical) {
   1781 		goto norealpath_PWD;
   1782 	} else if ((pwd = allocd = do_realpath(Xstring(xs, xp))) == NULL) {
   1783 		if (eflag)
   1784 			rv = 1;
   1785  norealpath_PWD:
   1786 		pwd = Xstring(xs, xp);
   1787 	}
   1788 
   1789 	/* Set PWD */
   1790 	if (pwd) {
   1791 		char *ptmp = pwd;
   1792 
   1793 		set_current_wd(ptmp);
   1794 		/* Ignore failure (happens if readonly or integer) */
   1795 		setstr(pwd_s, ptmp, KSH_RETURN_ERROR);
   1796 	} else {
   1797 		set_current_wd(null);
   1798 		pwd = Xstring(xs, xp);
   1799 		/* XXX unset $PWD? */
   1800 		if (eflag)
   1801 			rv = 1;
   1802 	}
   1803 	if (printpath || cdnode)
   1804 		shprintf("%s\n", pwd);
   1805 
   1806 	afree(allocd, ATEMP);
   1807 	Xfree(xs, xp);
   1808 	return (rv);
   1809 }
   1810 
   1811 
   1812 #ifdef TIOCSCTTY
   1813 extern void chvt_reinit(void);
   1814 
   1815 static void
   1816 chvt(const char *fn)
   1817 {
   1818 	char dv[20];
   1819 	struct stat sb;
   1820 	int fd;
   1821 
   1822 	if (*fn == '-') {
   1823 		memcpy(dv, "-/dev/null", sizeof("-/dev/null"));
   1824 		fn = dv + 1;
   1825 	} else {
   1826 		if (stat(fn, &sb)) {
   1827 			memcpy(dv, "/dev/ttyC", 9);
   1828 			strlcpy(dv + 9, fn, sizeof(dv) - 9);
   1829 			if (stat(dv, &sb)) {
   1830 				strlcpy(dv + 8, fn, sizeof(dv) - 8);
   1831 				if (stat(dv, &sb))
   1832 					errorf("%s: %s %s", "chvt",
   1833 					    "can't find tty", fn);
   1834 			}
   1835 			fn = dv;
   1836 		}
   1837 		if (!(sb.st_mode & S_IFCHR))
   1838 			errorf("%s %s %s", "chvt: not a char", "device", fn);
   1839 		if ((sb.st_uid != 0) && chown(fn, 0, 0))
   1840 			warningf(false, "%s: %s %s", "chvt", "can't chown root", fn);
   1841 		if (((sb.st_mode & 07777) != 0600) && chmod(fn, (mode_t)0600))
   1842 			warningf(false, "%s: %s %s", "chvt", "can't chmod 0600", fn);
   1843 #if HAVE_REVOKE
   1844 		if (revoke(fn))
   1845 #endif
   1846 			warningf(false, "%s: %s %s", "chvt",
   1847 			    "new shell is potentially insecure, can't revoke",
   1848 			    fn);
   1849 	}
   1850 	if ((fd = open(fn, O_RDWR)) == -1) {
   1851 		sleep(1);
   1852 		if ((fd = open(fn, O_RDWR)) == -1)
   1853 			errorf("%s: %s %s", "chvt", "can't open", fn);
   1854 	}
   1855 	switch (fork()) {
   1856 	case -1:
   1857 		errorf("%s: %s %s", "chvt", "fork", "failed");
   1858 	case 0:
   1859 		break;
   1860 	default:
   1861 		exit(0);
   1862 	}
   1863 	if (setsid() == -1)
   1864 		errorf("%s: %s %s", "chvt", "setsid", "failed");
   1865 	if (fn != dv + 1) {
   1866 		if (ioctl(fd, TIOCSCTTY, NULL) == -1)
   1867 			errorf("%s: %s %s", "chvt", "TIOCSCTTY", "failed");
   1868 		if (tcflush(fd, TCIOFLUSH))
   1869 			errorf("%s: %s %s", "chvt", "TCIOFLUSH", "failed");
   1870 	}
   1871 	ksh_dup2(fd, 0, false);
   1872 	ksh_dup2(fd, 1, false);
   1873 	ksh_dup2(fd, 2, false);
   1874 	if (fd > 2)
   1875 		close(fd);
   1876 	{
   1877 		register uint32_t h;
   1878 
   1879 		NZATInit(h);
   1880 		NZATUpdateMem(h, &rndsetupstate, sizeof(rndsetupstate));
   1881 		NZAATFinish(h);
   1882 		rndset((long)h);
   1883 	}
   1884 	chvt_reinit();
   1885 }
   1886 #endif
   1887 
   1888 #ifdef DEBUG
   1889 #define assert_eq(name, a, b) char name[a == b ? 1 : -1]
   1890 #define assert_ge(name, a, b) char name[a >= b ? 1 : -1]
   1891 assert_ge(intsize_is_okay, sizeof(int), 4);
   1892 assert_eq(intsizes_are_okay, sizeof(int), sizeof(unsigned int));
   1893 assert_ge(longsize_is_okay, sizeof(long), sizeof(int));
   1894 assert_eq(arisize_is_okay, sizeof(mksh_ari_t), 4);
   1895 assert_eq(uarisize_is_okay, sizeof(mksh_uari_t), 4);
   1896 assert_eq(sizesizes_are_okay, sizeof(size_t), sizeof(ssize_t));
   1897 assert_eq(ptrsizes_are_okay, sizeof(ptrdiff_t), sizeof(void *));
   1898 assert_eq(ptrsize_is_sizet, sizeof(ptrdiff_t), sizeof(size_t));
   1899 /* formatting routines assume this */
   1900 assert_ge(ptr_fits_in_long, sizeof(long), sizeof(size_t));
   1901 
   1902 char *
   1903 strchr(char *p, int ch)
   1904 {
   1905 	for (;; ++p) {
   1906 		if (*p == ch)
   1907 			return (p);
   1908 		if (!*p)
   1909 			return (NULL);
   1910 	}
   1911 	/* NOTREACHED */
   1912 }
   1913 
   1914 char *
   1915 strstr(char *b, const char *l)
   1916 {
   1917 	char first, c;
   1918 	size_t n;
   1919 
   1920 	if ((first = *l++) == '\0')
   1921 		return (b);
   1922 	n = strlen(l);
   1923  strstr_look:
   1924 	while ((c = *b++) != first)
   1925 		if (c == '\0')
   1926 			return (NULL);
   1927 	if (strncmp(b, l, n))
   1928 		goto strstr_look;
   1929 	return (b - 1);
   1930 }
   1931 #endif
   1932 
   1933 #if !HAVE_STRCASESTR
   1934 const char *
   1935 stristr(const char *b, const char *l)
   1936 {
   1937 	char first, c;
   1938 	size_t n;
   1939 
   1940 	if ((first = *l++), ((first = ksh_tolower(first)) == '\0'))
   1941 		return (b);
   1942 	n = strlen(l);
   1943  stristr_look:
   1944 	while ((c = *b++), ((c = ksh_tolower(c)) != first))
   1945 		if (c == '\0')
   1946 			return (NULL);
   1947 	if (strncasecmp(b, l, n))
   1948 		goto stristr_look;
   1949 	return (b - 1);
   1950 }
   1951 #endif
   1952 
   1953 #ifdef MKSH_SMALL
   1954 char *
   1955 strndup_(const char *src, size_t len, Area *ap)
   1956 {
   1957 	char *dst = NULL;
   1958 
   1959 	if (src != NULL) {
   1960 		dst = alloc(len + 1, ap);
   1961 		memcpy(dst, src, len);
   1962 		dst[len] = '\0';
   1963 	}
   1964 	return (dst);
   1965 }
   1966 
   1967 char *
   1968 strdup_(const char *src, Area *ap)
   1969 {
   1970 	return (src == NULL ? NULL : strndup_(src, strlen(src), ap));
   1971 }
   1972 #endif
   1973 
   1974 #if !HAVE_GETRUSAGE
   1975 #define INVTCK(r,t)	do {						\
   1976 	r.tv_usec = ((t) % (1000000 / CLK_TCK)) * (1000000 / CLK_TCK);	\
   1977 	r.tv_sec = (t) / CLK_TCK;					\
   1978 } while (/* CONSTCOND */ 0)
   1979 
   1980 int
   1981 getrusage(int what, struct rusage *ru)
   1982 {
   1983 	struct tms tms;
   1984 	clock_t u, s;
   1985 
   1986 	if (/* ru == NULL || */ times(&tms) == (clock_t)-1)
   1987 		return (-1);
   1988 
   1989 	switch (what) {
   1990 	case RUSAGE_SELF:
   1991 		u = tms.tms_utime;
   1992 		s = tms.tms_stime;
   1993 		break;
   1994 	case RUSAGE_CHILDREN:
   1995 		u = tms.tms_cutime;
   1996 		s = tms.tms_cstime;
   1997 		break;
   1998 	default:
   1999 		errno = EINVAL;
   2000 		return (-1);
   2001 	}
   2002 	INVTCK(ru->ru_utime, u);
   2003 	INVTCK(ru->ru_stime, s);
   2004 	return (0);
   2005 }
   2006 #endif
   2007 
   2008 /*
   2009  * process the string available via fg (get a char)
   2010  * and fp (put back a char) for backslash escapes,
   2011  * assuming the first call to *fg gets the char di-
   2012  * rectly after the backslash; return the character
   2013  * (0..0xFF), Unicode (wc + 0x100), or -1 if no known
   2014  * escape sequence was found
   2015  */
   2016 int
   2017 unbksl(bool cstyle, int (*fg)(void), void (*fp)(int))
   2018 {
   2019 	int wc, i, c, fc;
   2020 
   2021 	fc = (*fg)();
   2022 	switch (fc) {
   2023 	case 'a':
   2024 		/*
   2025 		 * according to the comments in pdksh, \007 seems
   2026 		 * to be more portable than \a (due to HP-UX cc,
   2027 		 * Ultrix cc, old pcc, etc.) so we avoid the escape
   2028 		 * sequence altogether in mksh and assume ASCII
   2029 		 */
   2030 		wc = 7;
   2031 		break;
   2032 	case 'b':
   2033 		wc = '\b';
   2034 		break;
   2035 	case 'c':
   2036 		if (!cstyle)
   2037 			goto unknown_escape;
   2038 		c = (*fg)();
   2039 		wc = CTRL(c);
   2040 		break;
   2041 	case 'E':
   2042 	case 'e':
   2043 		wc = 033;
   2044 		break;
   2045 	case 'f':
   2046 		wc = '\f';
   2047 		break;
   2048 	case 'n':
   2049 		wc = '\n';
   2050 		break;
   2051 	case 'r':
   2052 		wc = '\r';
   2053 		break;
   2054 	case 't':
   2055 		wc = '\t';
   2056 		break;
   2057 	case 'v':
   2058 		/* assume ASCII here as well */
   2059 		wc = 11;
   2060 		break;
   2061 	case '1':
   2062 	case '2':
   2063 	case '3':
   2064 	case '4':
   2065 	case '5':
   2066 	case '6':
   2067 	case '7':
   2068 		if (!cstyle)
   2069 			goto unknown_escape;
   2070 		/* FALLTHROUGH */
   2071 	case '0':
   2072 		if (cstyle)
   2073 			(*fp)(fc);
   2074 		/*
   2075 		 * look for an octal number with up to three
   2076 		 * digits, not counting the leading zero;
   2077 		 * convert it to a raw octet
   2078 		 */
   2079 		wc = 0;
   2080 		i = 3;
   2081 		while (i--)
   2082 			if ((c = (*fg)()) >= '0' && c <= '7')
   2083 				wc = (wc << 3) + (c - '0');
   2084 			else {
   2085 				(*fp)(c);
   2086 				break;
   2087 			}
   2088 		break;
   2089 	case 'U':
   2090 		i = 8;
   2091 		if (/* CONSTCOND */ 0)
   2092 		/* FALLTHROUGH */
   2093 	case 'u':
   2094 		i = 4;
   2095 		if (/* CONSTCOND */ 0)
   2096 		/* FALLTHROUGH */
   2097 	case 'x':
   2098 		i = cstyle ? -1 : 2;
   2099 		/**
   2100 		 * x:	look for a hexadecimal number with up to
   2101 		 *	two (C style: arbitrary) digits; convert
   2102 		 *	to raw octet (C style: Unicode if >0xFF)
   2103 		 * u/U:	look for a hexadecimal number with up to
   2104 		 *	four (U: eight) digits; convert to Unicode
   2105 		 */
   2106 		wc = 0;
   2107 		while (i--) {
   2108 			wc <<= 4;
   2109 			if ((c = (*fg)()) >= '0' && c <= '9')
   2110 				wc += c - '0';
   2111 			else if (c >= 'A' && c <= 'F')
   2112 				wc += c - 'A' + 10;
   2113 			else if (c >= 'a' && c <= 'f')
   2114 				wc += c - 'a' + 10;
   2115 			else {
   2116 				wc >>= 4;
   2117 				(*fp)(c);
   2118 				break;
   2119 			}
   2120 		}
   2121 		if ((cstyle && wc > 0xFF) || fc != 'x')
   2122 			/* Unicode marker */
   2123 			wc += 0x100;
   2124 		break;
   2125 	case '\'':
   2126 		if (!cstyle)
   2127 			goto unknown_escape;
   2128 		wc = '\'';
   2129 		break;
   2130 	case '\\':
   2131 		wc = '\\';
   2132 		break;
   2133 	default:
   2134  unknown_escape:
   2135 		(*fp)(fc);
   2136 		return (-1);
   2137 	}
   2138 
   2139 	return (wc);
   2140 }
   2141