view usr/src/cmd/sgs/m4/common/m4.c @ 12967:1e73c8f5a88b

4691240 m4 -H0 causes m4 to crash
author Rich Burridge <rich.burridge@oracle.com>
date Thu, 29 Jul 2010 08:59:51 -0700
parents 59445bec7ef4
children 4f5bb85e2547
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved  	*/

#include	<signal.h>
#include	<unistd.h>
#include	<fcntl.h>
#include	"m4.h"

#if defined(__lint)
extern int yydebug;
#endif

#define	match(c, s)	(c == *s && (!s[1] || inpmatch(s+1)))

static char	tmp_name[] = "/tmp/m4aXXXXX";
static wchar_t	prev_char;
static int mb_cur_max;

static void getflags(int *, char ***, int *);
static void initalloc(void);
static void expand(wchar_t **, int);
static void lnsync(FILE *);
static void fpath(FILE *);
static void puttok(wchar_t *);
static void error3(void);
static wchar_t itochr(int);
/*LINTED: E_STATIC_UNUSED*/
static wchar_t *chkbltin(wchar_t *);
static wchar_t *inpmatch(wchar_t *);
static void chkspace(char **, int *, char ***);
static void catchsig(int);
static FILE *m4open(char ***, char *, int *);
static void showwrap(void);
static void sputchr(wchar_t, FILE *);
static void putchr(wchar_t);
static void *xcalloc(size_t, size_t);
static wint_t myfgetwc(FILE *, int);
static wint_t myfputwc(wchar_t, FILE *);
static int myfeof(int);

int
main(int argc, char **argv)
{
	wchar_t t;
	int i, opt_end = 0;
	int sigs[] = {SIGHUP, SIGINT, SIGPIPE, 0};

#if defined(__lint)
	yydebug = 0;
#endif

	for (i = 0; sigs[i]; ++i) {
		if (signal(sigs[i], SIG_IGN) != SIG_IGN)
			(void) signal(sigs[i], catchsig);
	}
	tempfile = mktemp(tmp_name);
	(void) close(creat(tempfile, 0));

	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	if ((mb_cur_max = MB_CUR_MAX) > 1)
		wide = 1;

	procnam = argv[0];
	getflags(&argc, &argv, &opt_end);
	initalloc();

	setfname("-");
	if (argc > 1) {
		--argc;
		++argv;
		if (strcmp(argv[0], "-")) {
			ifile[ifx] = m4open(&argv, "r", &argc);
			setfname(argv[0]);
		}
	}

	for (;;) {
		token[0] = t = getchr();
		token[1] = EOS;

		if (t == WEOF) {
			if (ifx > 0) {
				(void) fclose(ifile[ifx]);
				ipflr = ipstk[--ifx];
				continue;
			}

			getflags(&argc, &argv, &opt_end);

			if (argc <= 1)
				/*
				 * If dowrap() has been called, the m4wrap
				 * macro has been processed, and a linked
				 * list of m4wrap strings has been created.
				 * The list starts at wrapstart.
				 */
				if (wrapstart) {
					/*
					 * Now that EOF has been processed,
					 * display the m4wrap strings.
					 */
					showwrap();
					continue;
				} else
					break;
			--argc;
			++argv;

			if (ifile[ifx] != stdin)
				(void) fclose(ifile[ifx]);

			if (strcmp(argv[0], "-"))
				ifile[ifx] = m4open(&argv, "r", &argc);
			else
				ifile[ifx] = stdin;

			setfname(argv[0]);
			continue;
		}

		if (is_alpha(t) || t == '_') {
			wchar_t	*tp = token+1;
			int tlim = toksize;
			struct nlist	*macadd;  /* temp variable */

			while ((*tp = getchr()) != WEOF &&
			    (is_alnum(*tp) || *tp == '_')) {
				tp++;
				if (--tlim <= 0)
					error2(gettext(
					    "more than %d chars in word"),
					    toksize);
			}
			putbak(*tp);
			*tp = EOS;

			macadd = lookup(token);
			*Ap = (wchar_t *)macadd;
			if (macadd->def) {
				if ((wchar_t *)(++Ap) >= astklm) {
					--Ap;
					error2(gettext(
					    "more than %d items on "
					    "argument stack"),
					    stksize);
				}

				if (Cp++ == NULL)
					Cp = callst;

				Cp->argp = Ap;
				*Ap++ = op;
				puttok(token);
				stkchr(EOS);
				t = getchr();
				putbak(t);

				if (t != '(')
					pbstr(L"()");
				else	/* try to fix arg count */
					*Ap++ = op;

				Cp->plev = 0;
			} else {
				puttok(token);
			}
		} else if (match(t, lquote)) {
			int	qlev = 1;

			for (;;) {
				token[0] = t = getchr();
				token[1] = EOS;

				if (match(t, rquote)) {
					if (--qlev > 0)
						puttok(token);
					else
						break;
				} else if (match(t, lquote)) {
					++qlev;
					puttok(token);
				} else {
					if (t == WEOF)
						error(gettext(
						"EOF in quote"));
					putchr(t);
				}
			}
		} else if (match(t, lcom) &&
		    ((lcom[0] != L'#' || lcom[1] != L'\0') ||
		    prev_char != '$')) {

			/*
			 * Don't expand commented macro (between lcom and
			 * rcom).
			 * What we know so far is that we have found the
			 * left comment char (lcom).
			 * Make sure we haven't found '#' (lcom) immediately
			 * preceded by '$' because we want to expand "$#".
			 */

			puttok(token);
			for (;;) {
				token[0] = t = getchr();
				token[1] = EOS;
				if (match(t, rcom)) {
					puttok(token);
					break;
				} else {
					if (t == WEOF)
						error(gettext(
						"EOF in comment"));
					putchr(t);
				}
			}
		} else if (Cp == NULL) {
			putchr(t);
		} else if (t == '(') {
			if (Cp->plev)
				stkchr(t);
			else {
				/* skip white before arg */
				while ((t = getchr()) != WEOF && is_space(t))
					;

				putbak(t);
			}

			++Cp->plev;
		} else if (t == ')') {
			--Cp->plev;

			if (Cp->plev == 0) {
				stkchr(EOS);
				expand(Cp->argp, Ap-Cp->argp-1);
				op = *Cp->argp;
				Ap = Cp->argp-1;

				if (--Cp < callst)
					Cp = NULL;
			} else
				stkchr(t);
		} else if (t == ',' && Cp->plev <= 1) {
			stkchr(EOS);
			*Ap = op;

			if ((wchar_t *)(++Ap) >= astklm) {
				--Ap;
				error2(gettext(
				    "more than %d items on argument stack"),
				    stksize);
			}

			while ((t = getchr()) != WEOF && is_space(t))
				;

			putbak(t);
		} else {
			stkchr(t);
		}
	}

	if (Cp != NULL)
		error(gettext(
		"EOF in argument list"));

	delexit(exitstat, 1);
	return (0);
}

static wchar_t *
inpmatch(wchar_t *s)
{
	wchar_t	*tp = token+1;

	while (*s) {
		*tp = getchr();

		if (*tp++ != *s++) {
			*tp = EOS;
			pbstr(token+1);
			return (0);
		}
	}

	*tp = EOS;
	return (token);
}

static void
getflags(int *xargc, char ***xargv, int *option_end)
{
	char	*arg;
	char *t;
	wchar_t *s[3];

	while (*xargc > 1) {
		arg = (*xargv)[1]; /* point arg to current argument */

		/*
		 * This argument is not an option if it equals "-" or if
		 * "--" has already been parsed.
		 */
		if (arg[0] != '-' || arg[1] == EOS || *option_end)
			break;
		if (arg[0] == '-' && arg[1] == '-' && arg[2] == '\0') {
			*option_end = 1;
		} else {
			switch (arg[1]) {
			case 'B':
				chkspace(&arg, xargc, xargv);
				bufsize = atoi(&arg[2]);
				if (bufsize <= 0) {
					bufsize = DEF_BUFSIZE;
				}
				break;
			case 'D':
				initalloc();
				chkspace(&arg, xargc, xargv);
				for (t = &arg[2]; *t; t++) {
					if (*t == '=') {
						*t++ = EOS;
						break;
					}
				}
				s[1] = str2wstr(&arg[2], 1);
				s[2] = str2wstr(t, 1);
				dodef(&s[0], 2);
				free(s[1]);
				free(s[2]);
				break;
			case 'H':
				chkspace(&arg, xargc, xargv);
				hshsize = atoi(&arg[2]);
				if (hshsize <= 0) {
					hshsize = DEF_HSHSIZE;
				}
				break;
			case 'S':
				chkspace(&arg, xargc, xargv);
				stksize = atoi(&arg[2]);
				if (stksize <= 0) {
					stksize = DEF_STKSIZE;
				}
				break;
			case 'T':
				chkspace(&arg, xargc, xargv);
				toksize = atoi(&arg[2]);
				if (toksize <= 0) {
					toksize = DEF_TOKSIZE;
				}
				break;
			case 'U':
				initalloc();
				chkspace(&arg, xargc, xargv);
				s[1] = str2wstr(&arg[2], 1);
				doundef(&s[0], 1);
				free(s[1]);
				break;
			case 'e':
				setbuf(stdout, NULL);
				(void) signal(SIGINT, SIG_IGN);
				break;
			case 's':
				/* turn on line sync */
				sflag = 1;
				break;
			default:
				(void) fprintf(stderr,
				    gettext("%s: bad option: %s\n"),
				    procnam, arg);
				delexit(NOT_OK, 0);
			}
		} /* end else not "--" */

		(*xargv)++;
		--(*xargc);
	} /* end while options to process */
}

/*
 * Function: chkspace
 *
 * If there is a space between the option and its argument,
 * adjust argptr so that &arg[2] will point to beginning of the option argument.
 * This will ensure that processing in getflags() will work, because &arg[2]
 * will point to the beginning of the option argument whether or not we have
 * a space between the option and its argument.  If there is a space between
 * the option and its argument, also adjust xargv and xargc because we are
 * processing the next argument.
 */
static void
chkspace(char **argptr, int *xargc, char ***xargv)
{
	if ((*argptr)[2] == EOS) {
		/* there is a space between the option and its argument */
		(*xargv)++; /* look at the next argument */
		--(*xargc);
		/*
		 * Adjust argptr if the option is followed by an
		 * option argument.
		 */
		if (*xargc > 1) {
			*argptr = (*xargv)[1];
			/* point &arg[2] to beginning of option argument */
			*argptr -= 2;
		}
	}
}

static void
initalloc(void)
{
	static int done = 0;
	int	t;

	if (done++)
		return;

	hshtab = xcalloc(hshsize, sizeof (struct nlist *));
	callst = xcalloc(stksize/3+1, sizeof (struct call));
	Ap = argstk = xcalloc(stksize+3, sizeof (wchar_t *));
	ipstk[0] = ipflr = ip = ibuf = xcalloc(bufsize+1, sizeof (wchar_t));
	op = obuf = xcalloc(bufsize+1, sizeof (wchar_t));
	token = xcalloc(toksize+1, sizeof (wchar_t));

	astklm = (wchar_t *)(&argstk[stksize]);
	ibuflm = &ibuf[bufsize];
	obuflm = &obuf[bufsize];
	toklm = &token[toksize];

	for (t = 0; barray[t].bname; ++t) {
		wchar_t	p[2] = {0, EOS};

		p[0] = builtin(t);
		install(barray[t].bname, p, NOPUSH);
	}
	install(L"unix", nullstr, NOPUSH);
}

void
install(wchar_t *nam, wchar_t *val, int mode)
{
	struct nlist *np;
	wchar_t	*cp;
	int		l;

	if (mode == PUSH)
		(void) lookup(nam);	/* lookup sets hshval */
	else
		while (undef(nam))	/* undef calls lookup */
			;

	np = xcalloc(1, sizeof (*np));
	np->name = wstrdup(nam);
	np->next = hshtab[hshval];
	hshtab[hshval] = np;

	cp = xcalloc((l = wcslen(val))+1, sizeof (*val));
	np->def = cp;
	cp = &cp[l];

	while (*val)
		*--cp = *val++;
}

struct nlist *
lookup(wchar_t *str)
{
	wchar_t	*s1;
	struct nlist	*np;
	static struct nlist	nodef;

	s1 = str;

	for (hshval = 0; *s1; )
		hshval += *s1++;

	hshval %= hshsize;

	for (np = hshtab[hshval]; np != NULL; np = np->next) {
		if (*str == *np->name && wcscmp(str, np->name) == 0)
			return (np);
	}
	return (&nodef);
}

static void
expand(wchar_t **a1, int c)
{
	wchar_t	*dp;
	struct nlist	*sp;

	sp = (struct nlist *)a1[-1];

	if (sp->tflag || trace) {
#if !defined(__lint)	/* lint doesn't grok "%ws" */
		int	i;

		(void) fprintf(stderr,
		    "Trace(%d): %ws", Cp-callst, a1[0]);
#endif

		if (c > 0) {
#if !defined(__lint)	/* lint doesn't grok "%ws" */
			(void) fprintf(stderr, "(%ws", chkbltin(a1[1]));
			for (i = 2; i <= c; ++i)
				(void) fprintf(stderr, ",%ws", chkbltin(a1[i]));
#endif
			(void) fprintf(stderr, ")");
		}
		(void) fprintf(stderr, "\n");
	}

	dp = sp->def;

	for (; *dp; ++dp) {
		if (is_builtin(*dp)) {
			(*barray[builtin_idx(*dp)].bfunc)(a1, c);
		} else if (dp[1] == '$') {
			if (is_digit(*dp)) {
				int	n;
				if ((n = *dp-'0') <= c)
					pbstr(a1[n]);
				++dp;
			} else if (*dp == '#') {
				pbnum((long)c);
				++dp;
			} else if (*dp == '*' || *dp == '@') {
				int i = c;
				wchar_t **a = a1;

				if (i > 0)
					for (;;) {
						if (*dp == '@')
							pbstr(rquote);

						pbstr(a[i--]);

						if (*dp == '@')
							pbstr(lquote);

						if (i <= 0)
						break;

						pbstr(L",");
					}
				++dp;
			} else
				putbak(*dp);
		} else
			putbak(*dp);
	}
}

void
setfname(char *s)
{
	if (fname[ifx])
		free(fname[ifx]);
	if ((fname[ifx] = strdup(s)) == NULL)
		error(gettext("out of storage"));
	fline[ifx] = 1;
	nflag = 1;
	lnsync(stdout);
}

static void
lnsync(FILE *iop)
{
	static int cline = 0;
	static int cfile = 0;

	if (!sflag || iop != stdout)
		return;

	if (nflag || ifx != cfile) {
		nflag = 0;
		cfile = ifx;
		(void) fprintf(iop, "#line %d \"", cline = fline[ifx]);
		fpath(iop);
		(void) fprintf(iop, "\"\n");
	} else if (++cline != fline[ifx])
		(void) fprintf(iop, "#line %d\n", cline = fline[ifx]);
}

static void
fpath(FILE *iop)
{
	int	i;

	if (fname[0] == NULL)
		return;

	(void) fprintf(iop, "%s", fname[0]);

	for (i = 1; i <= ifx; ++i)
		(void) fprintf(iop, ":%s", fname[i]);
}

/* ARGSUSED */
static void
catchsig(int i)
{
	(void) signal(SIGHUP, SIG_IGN);
	(void) signal(SIGINT, SIG_IGN);
	delexit(NOT_OK, 0);
}

void
delexit(int code, int flushio)
{
	int i;

	cf = stdout;

/*
 *	if (ofx != 0) {
 *		ofx = 0;
 *		code = NOT_OK;
 *	}
 */
	ofx = 0;	/* ensure that everything comes out */
	for (i = 1; i < 10; i++)
		undiv(i, code);

	tempfile[7] = 'a';
	(void) unlink(tempfile);

	/* flush standard I/O buffers, ie: call exit() not _exit() */
	if (flushio)
		exit(code);

	_exit(code);
}

static void
puttok(wchar_t *tp)
{
	if (Cp) {
		while (*tp)
			stkchr(*tp++);
	} else if (cf) {
		while (*tp) {
			sputchr(*tp++, cf);
		}
	}
}

void
pbstr(wchar_t *str)
{
	wchar_t *p;

	for (p = str + wcslen(str); --p >= str; )
		putbak(*p);
}

void
undiv(int i, int code)
{
	FILE *fp;
	wint_t c;

	if (i < 1 || i > 9 || i == ofx || !ofile[i])
		return;

	(void) fclose(ofile[i]);
	tempfile[7] = 'a'+i;

	if (code == OK && cf) {
		fp = xfopen(tempfile, "r");

		if (wide) {
			while ((c = myfgetwc(fp, -1)) != WEOF)
				sputchr((wchar_t)c, cf);
		} else {
			while ((c = (wint_t)getc(fp)) != WEOF)
				sputchr((wchar_t)c, cf);
		}

		(void) fclose(fp);
	}

	(void) unlink(tempfile);
	ofile[i] = NULL;
}

void
pbnum(long num)
{
	pbnbr(num, 10, 1);
}

void
pbnbr(long nbr, int base, int len)
{
	int	neg = 0;

	if (base <= 0)
		return;

	if (nbr < 0)
		neg = 1;
	else
		nbr = -nbr;

	while (nbr < 0) {
		int	i;
		if (base > 1) {
			i = nbr%base;
			nbr /= base;
#if (-3 % 2) != -1
			while (i > 0) {
				i -= base;
				++nbr;
			}
#endif
			i = -i;
		} else {
			i = 1;
			++nbr;
		}
		putbak(itochr(i));
		--len;
	}

	while (--len >= 0)
		putbak('0');

	if (neg)
		putbak('-');
}

static wchar_t
itochr(int i)
{
	if (i > 9)
		return ((wchar_t)(i-10+'A'));
	else
		return ((wchar_t)(i+'0'));
}

long
ctol(wchar_t *str)
{
	int sign;
	long num;

	while (is_space(*str))
		++str;
	num = 0;
	if (*str == '-') {
		sign = -1;
		++str;
	} else
		sign = 1;
	while (is_digit(*str))
		num = num*10 + *str++ - '0';
	return (sign * num);
}

int
min(int a, int b)
{
	if (a > b)
		return (b);
	return (a);
}

FILE *
xfopen(char *name, char *mode)
{
	FILE	*fp;

	if ((fp = fopen(name, mode)) == NULL)
		error(gettext("can't open file"));

	return (fp);
}

/*
 * m4open
 *
 * Continue processing files when unable to open the given file argument.
 */
FILE *
m4open(char ***argvec, char *mode, int *argcnt)
{
	FILE	*fp;
	char *arg;

	while (*argcnt > 0) {
		arg = (*argvec)[0]; /* point arg to current file name */
		if (arg[0] == '-' && arg[1] == EOS)
			return (stdin);
		else {
			if ((fp = fopen(arg, mode)) == NULL) {
				(void) fprintf(stderr, gettext(
				"m4: cannot open %s: "), arg);
				perror("");
				if (*argcnt == 1) {
					/* last arg therefore exit */
					error3();
				} else {
					exitstat = 1;
					(*argvec)++; /* try next arg */
					(*argcnt)--;
				}
			} else
				break;
		}
	}
	return (fp);
}

void *
xmalloc(size_t size)
{
	void *ptr;

	if ((ptr = malloc(size)) == NULL)
		error(gettext("out of storage"));
	return (ptr);
}

static void *
xcalloc(size_t nbr, size_t size)
{
	void	*ptr;

	ptr = xmalloc(nbr * size);
	(void) memset(ptr, '\0', nbr * size);
	return (ptr);
}

/* PRINTFLIKE1 */
void
error2(char *str, int num)
{
	char buf[500];

	(void) snprintf(buf, sizeof (buf), str, num);
	error(buf);
}

void
error(char *str)
{
	(void) fprintf(stderr, "\n%s:", procnam);
	fpath(stderr);
	(void) fprintf(stderr, ":%d %s\n", fline[ifx], str);
	error3();
}

static void
error3()
{
	if (Cp) {
		struct call	*mptr;

		/* fix limit */
		*op = EOS;
		(Cp+1)->argp = Ap+1;

		for (mptr = callst; mptr <= Cp; ++mptr) {
			wchar_t	**aptr, **lim;

			aptr = mptr->argp;
			lim = (mptr+1)->argp-1;
			if (mptr == callst)
				(void) fputws(*aptr, stderr);
			++aptr;
			(void) fputs("(", stderr);
			if (aptr < lim)
				for (;;) {
					(void) fputws(*aptr++, stderr);
					if (aptr >= lim)
						break;
					(void) fputs(",", stderr);
				}
		}
		while (--mptr >= callst)
			(void) fputs(")", stderr);

		(void) fputs("\n", stderr);
	}
	delexit(NOT_OK, 1);
}

static wchar_t *
chkbltin(wchar_t *s)
{
	static wchar_t buf[24];

	if (is_builtin(*s)) {
		(void) swprintf(buf, sizeof (buf)/sizeof (wchar_t), L"<%ls>",
		    barray[builtin_idx(*s)].bname);
		return (buf);
	}
	return (s);
}

wchar_t
getchr()
{
	static wchar_t C;

	prev_char = C;
	if (ip > ipflr)
		return (*--ip);
	if (wide) {
		C = (wchar_t)(myfeof(ifx) ? WEOF : myfgetwc(NULL, ifx));
	} else {
		C = (wchar_t)(feof(ifile[ifx]) ?
		    WEOF : (wint_t)getc(ifile[ifx]));
	}
	if (C == '\n')
		fline[ifx]++;
	return (C);
}

/*
 * showwrap
 *
 * Loop through the list of m4wrap strings.  Call pbstr() so that the
 * string will be displayed, then delete the list entry and free the memory
 * allocated for it.
 */
static void
showwrap()
{
	struct Wrap *prev;

	while (wrapstart) {
		pbstr(wrapstart->wrapstr);
		free(wrapstart->wrapstr);
		prev = wrapstart;
		wrapstart = wrapstart->nxt;
		free(prev);
	}
}

static void
sputchr(wchar_t c, FILE *f)
{
	wint_t ret;

	if (is_builtin(c))
		return;
	if (wide)
		ret = myfputwc(c, f);
	else
		ret = (wint_t)putc((int)c, f);
	if (ret == WEOF)
		error(gettext("output error"));
	if (ret == '\n')
		lnsync(f);
}

static void
putchr(wchar_t c)
{
	wint_t ret;

	if (Cp)
		stkchr(c);
	else if (cf) {
		if (sflag)
			sputchr(c, cf);
		else {
			if (is_builtin(c))
				return;
			if (wide)
				ret = myfputwc(c, cf);
			else
				ret = (wint_t)putc((int)c, cf);
			if (ret == WEOF) {
				error(gettext("output error"));
			}
		}
	}
}

wchar_t *
wstrdup(wchar_t *p)
{
	size_t len = wcslen(p);
	wchar_t *ret;

	ret = xmalloc((len + 1) * sizeof (wchar_t));
	(void) wcscpy(ret, p);
	return (ret);
}

int
wstoi(wchar_t *p)
{
	return ((int)wcstol(p, NULL, 10));
}

char *
wstr2str(wchar_t *from, int alloc)
{
	static char *retbuf;
	static size_t bsiz;
	char *p, *ret;

	if (alloc) {
		ret = p = xmalloc(wcslen(from) * mb_cur_max + 1);
	} else {
		while (bsiz < (wcslen(from) * mb_cur_max + 1)) {
			if ((p = realloc(retbuf, bsiz + 256)) == NULL)
				error(gettext("out of storage"));
			bsiz += 256;
			retbuf = p;
		}
		ret = p = retbuf;
	}

	if (wide) {
		while (*from) {
			int len;

			if (*from & INVALID_CHAR) {
				*p = (char)(*from & ~INVALID_CHAR);
				len = 1;
			} else {
				if ((len = wctomb(p, *from)) == -1) {
					*p = (char)*from;
					len = 1;
				}
			}
			p += len;
			from++;
		}
	} else {
		while (*from)
			*p++ = (char)*from++;
	}
	*p = '\0';

	return (ret);
}

wchar_t *
str2wstr(char *from, int alloc)
{
	static wchar_t *retbuf;
	static size_t bsiz;
	wchar_t *p, *ret;

	if (alloc) {
		ret = p = xmalloc((strlen(from) + 1) * sizeof (wchar_t));
	} else {
		while (bsiz < (strlen(from) + 1)) {
			if ((p = realloc(retbuf,
			    (bsiz + 256) * sizeof (wchar_t))) == NULL) {
				error(gettext("out of storage"));
			}
			bsiz += 256;
			retbuf = p;
		}
		ret = p = retbuf;
	}

	if (wide) {
		while (*from) {
			int len;
			wchar_t wc;

			if ((len = mbtowc(&wc, from, mb_cur_max)) <= 0) {
				wc = *from | INVALID_CHAR;
				len = 1;
			}
			*p++ = wc;
			from += len;
		}
	} else {
		while (*from)
			*p++ = (unsigned char) *from++;
	}
	*p = 0;

	return (ret);
}

static wint_t
myfgetwc(FILE *fp, int idx)
{
	int i, c, len, nb;
	wchar_t wc;
	unsigned char *buf;

	if (fp == NULL)
		fp = ifile[idx];
	else
		idx = 10; /* extra slot */
	buf = ibuffer[idx].buffer;
	nb = ibuffer[idx].nbytes;
	len = 0;
	for (i = 1; i <= mb_cur_max; i++) {
		if (nb < i) {
			c = getc(fp);
			if (c == EOF) {
				if (nb == 0)
					return (WEOF);
				else
					break;
			}
			buf[nb++] = (unsigned char)c;
		}
		if ((len = mbtowc(&wc, (char *)buf, i)) >= 0)
			break;
	}
	if (len <= 0) {
		wc = buf[0] | INVALID_CHAR;
		len = 1;
	}
	nb -= len;
	if (nb > 0) {
		for (i = 0; i < nb; i++)
			buf[i] = buf[i + len];
	}
	ibuffer[idx].nbytes = nb;
	return (wc);
}

static wint_t
myfputwc(wchar_t wc, FILE *fp)
{
	if (wc & INVALID_CHAR) {
		wc &= ~INVALID_CHAR;
		return (fputc((int)wc, fp));
	}
	return (fputwc(wc, fp));
}

static int
myfeof(int idx)
{
	return (ibuffer[idx].nbytes == 0 && feof(ifile[idx]));
}