Beispiel #1
0
/* Used by built-in utilities to prefix shell and utility name to message
 * (also unwinds environments for special builtins).
 */
void
bi_errorf(const char *fmt, ...)
{
	va_list va;

	shl_stdout_ok = 0;	/* debugging: note that stdout not valid */
	exstat = 1;
	if (fmt != NULL && *fmt != '\0') {
		error_prefix(true);
		/* not set when main() calls parse_args() */
		if (builtin_argv0)
			shf_fprintf(shl_out, "%s: ", builtin_argv0);
		va_start(va, fmt);
		shf_vfprintf(shl_out, fmt, va);
		va_end(va);
		shf_putchar('\n', shl_out);
	}
	shf_flush(shl_out);
	/* POSIX special builtins and ksh special builtins cause
	 * non-interactive shells to exit.
	 * XXX odd use of KEEPASN; also may not want LERROR here
	 */
	if ((builtin_flag & SPEC_BI) ||
	    (Flag(FPOSIX) && (builtin_flag & KEEPASN))) {
		builtin_argv0 = NULL;
		unwind(LERROR);
	}
}
Beispiel #2
0
void
set_prompt(int to, Source *s)
{
	cur_prompt = (uint8_t)to;

	switch (to) {
	/* command */
	case PS1:
		/*
		 * Substitute ! and !! here, before substitutions are done
		 * so ! in expanded variables are not expanded.
		 * NOTE: this is not what AT&T ksh does (it does it after
		 * substitutions, POSIX doesn't say which is to be done.
		 */
		{
			struct shf *shf;
			char * volatile ps1;
			Area *saved_atemp;
			int saved_lineno;

			ps1 = str_val(global("PS1"));
			shf = shf_sopen(NULL, strlen(ps1) * 2,
			    SHF_WR | SHF_DYNAMIC, NULL);
			while (*ps1)
				if (*ps1 != '!' || *++ps1 == '!')
					shf_putchar(*ps1++, shf);
				else
					shf_fprintf(shf, "%lu", s ?
					    (unsigned long)s->line + 1 : 0UL);
			ps1 = shf_sclose(shf);
			saved_lineno = current_lineno;
			if (s)
				current_lineno = s->line + 1;
			saved_atemp = ATEMP;
			newenv(E_ERRH);
			if (kshsetjmp(e->jbuf)) {
				prompt = safe_prompt;
				/*
				 * Don't print an error - assume it has already
				 * been printed. Reason is we may have forked
				 * to run a command and the child may be
				 * unwinding its stack through this code as it
				 * exits.
				 */
			} else {
				char *cp = substitute(ps1, 0);
				strdupx(prompt, cp, saved_atemp);
			}
			current_lineno = saved_lineno;
			quitenv(NULL);
		}
		break;
	/* command continuation */
	case PS2:
		prompt = str_val(global("PS2"));
		break;
	}
}
Beispiel #3
0
/* like errorf(), but no unwind is done */
void
warningf(bool show_lineno, const char *fmt, ...)
{
	va_list va;

	error_prefix(show_lineno);
	va_start(va, fmt);
	shf_vfprintf(shl_out, fmt, va);
	va_end(va);
	shf_putchar('\n', shl_out);
	shf_flush(shl_out);
}
Beispiel #4
0
/* Print things in columns and rows - func() is called to format the ith
 * element
 */
void
print_columns(struct shf *shf, int n, char *(*func) (void *, int, char *, int),
    void *arg, int max_width, int prefcol)
{
	char *str = (char *) alloc(max_width + 1, ATEMP);
	int i;
	int r, c;
	int rows, cols;
	int nspace;
	int col_width;

	/* max_width + 1 for the space.  Note that no space
	 * is printed after the last column to avoid problems
	 * with terminals that have auto-wrap.
	 */
	cols = x_cols / (max_width + 1);
	if (!cols)
		cols = 1;
	rows = (n + cols - 1) / cols;
	if (prefcol && n && cols > rows) {
		int tmp = rows;

		rows = cols;
		cols = tmp;
		if (rows > n)
			rows = n;
	}

	col_width = max_width;
	if (cols == 1)
		col_width = 0; /* Don't pad entries in single column output. */
	nspace = (x_cols - max_width * cols) / cols;
	if (nspace <= 0)
		nspace = 1;
	for (r = 0; r < rows; r++) {
		for (c = 0; c < cols; c++) {
			i = c * rows + r;
			if (i < n) {
				shf_fprintf(shf, "%-*s",
				    col_width,
				    (*func)(arg, i, str, max_width + 1));
				if (c + 1 < cols)
					shf_fprintf(shf, "%*s", nspace, null);
			}
		}
		shf_putchar('\n', shf);
	}
	afree(str, ATEMP);
}
Beispiel #5
0
/* Called when something that shouldn't happen does */
void
internal_errorf(int jump, const char *fmt, ...)
{
	va_list va;

	error_prefix(true);
	shf_fprintf(shl_out, "internal error: ");
	va_start(va, fmt);
	shf_vfprintf(shl_out, fmt, va);
	va_end(va);
	shf_putchar('\n', shl_out);
	shf_flush(shl_out);
	if (jump)
		unwind(LERROR);
}
Beispiel #6
0
/* A shell error occurred (eg, syntax error, etc.) */
void
errorf(const char *fmt, ...)
{
	va_list va;

	shl_stdout_ok = 0;	/* debugging: note that stdout not valid */
	exstat = 1;
	if (fmt != NULL && *fmt != '\0') {
		error_prefix(true);
		va_start(va, fmt);
		shf_vfprintf(shl_out, fmt, va);
		va_end(va);
		shf_putchar('\n', shl_out);
	}
	shf_flush(shl_out);
	unwind(LERROR);
}
Beispiel #7
0
static void
vwarningf(unsigned int flags, const char *fmt, va_list ap)
{
	if (fmt) {
		if (flags & VWARNINGF_INTERNAL)
			shf_fprintf(shl_out, Tf_sD_, "internal error");
		if (flags & VWARNINGF_ERRORPREFIX)
			error_prefix(tobool(flags & VWARNINGF_FILELINE));
		if ((flags & VWARNINGF_BUILTIN) &&
		    /* not set when main() calls parse_args() */
		    builtin_argv0 && builtin_argv0 != kshname)
			shf_fprintf(shl_out, Tf_sD_, builtin_argv0);
		shf_vfprintf(shl_out, fmt, ap);
		shf_putchar('\n', shl_out);
	}
	shf_flush(shl_out);
}
Beispiel #8
0
/* variant of fputs for ptreef and wdstrip */
static const char *
wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
{
	int c;
	const char *cs;

	/*-
	 * problems:
	 *	`...` -> $(...)
	 *	'foo' -> "foo"
	 *	x${foo:-"hi"} -> x${foo:-hi} unless WDS_TPUTS
	 *	x${foo:-'hi'} -> x${foo:-hi} unless WDS_KEEPQ
	 * could change encoding to:
	 *	OQUOTE ["'] ... CQUOTE ["']
	 *	COMSUB [(`] ...\0	(handle $ ` \ and maybe " in `...` case)
	 */
	while (/* CONSTCOND */ 1)
		switch (*wp++) {
		case EOS:
			return (--wp);
		case ADELIM:
		case CHAR:
			c = *wp++;
			if ((opmode & WDS_MAGIC) &&
			    (ISMAGIC(c) || c == '[' || c == '!' ||
			    c == '-' || c == ']' || c == '*' || c == '?'))
				shf_putc(MAGIC, shf);
			shf_putc(c, shf);
			break;
		case QCHAR: {
			bool doq;

			c = *wp++;
			doq = (c == '"' || c == '`' || c == '$' || c == '\\');
			if (opmode & WDS_TPUTS) {
				if (quotelevel == 0)
					doq = true;
			} else {
				if (!(opmode & WDS_KEEPQ))
					doq = false;
			}
			if (doq)
				shf_putc('\\', shf);
			shf_putc(c, shf);
			break;
		}
		case COMSUB:
			shf_puts("$(", shf);
			cs = ")";
 pSUB:
			while ((c = *wp++) != 0)
				shf_putc(c, shf);
			shf_puts(cs, shf);
			break;
		case FUNSUB:
			c = ' ';
			if (0)
				/* FALLTHROUGH */
		case VALSUB:
			  c = '|';
			shf_putc('$', shf);
			shf_putc('{', shf);
			shf_putc(c, shf);
			cs = ";}";
			goto pSUB;
		case EXPRSUB:
			shf_puts("$((", shf);
			cs = "))";
			goto pSUB;
		case OQUOTE:
			if (opmode & WDS_TPUTS) {
				quotelevel++;
				shf_putc('"', shf);
			}
			break;
		case CQUOTE:
			if (opmode & WDS_TPUTS) {
				if (quotelevel)
					quotelevel--;
				shf_putc('"', shf);
			}
			break;
		case OSUBST:
			shf_putc('$', shf);
			if (*wp++ == '{')
				shf_putc('{', shf);
			while ((c = *wp++) != 0)
				shf_putc(c, shf);
			wp = wdvarput(shf, wp, 0, opmode);
			break;
		case CSUBST:
			if (*wp++ == '}')
				shf_putc('}', shf);
			return (wp);
		case OPAT:
			if (opmode & WDS_MAGIC) {
				shf_putc(MAGIC, shf);
				shf_putchar(*wp++ | 0x80, shf);
			} else {
				shf_putchar(*wp++, shf);
				shf_putc('(', shf);
			}
			break;
		case SPAT:
			c = '|';
			if (0)
		case CPAT:
				c = /*(*/ ')';
			if (opmode & WDS_MAGIC)
				shf_putc(MAGIC, shf);
			shf_putc(c, shf);
			break;
		}
}
Beispiel #9
0
/* execute tree in child subprocess */
int
exchild(struct op *t, int flags, volatile int *xerrok,
    int close_fd)	/* used if XPCLOSE or XCCLOSE */
{
	static Proc	*last_proc;	/* for pipelines */

	int		i;
	sigset_t	omask;
	Proc		*p;
	Job		*j;
	int		rv = 0;
	int		forksleep;
	int		ischild;

	if (flags & XEXEC)
		/* Clear XFORK|XPCLOSE|XCCLOSE|XCOPROC|XPIPEO|XPIPEI|XXCOM|XBGND
		 * (also done in another execute() below)
		 */
		return execute(t, flags & (XEXEC | XERROK), xerrok);

	/* no SIGCHLD's while messing with job and process lists */
	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);

	p = new_proc();
	p->next = (Proc *) 0;
	p->state = PRUNNING;
	p->status = 0;
	p->pid = 0;

	/* link process into jobs list */
	if (flags&XPIPEI) {	/* continuing with a pipe */
		if (!last_job)
			internal_errorf(1,
			    "exchild: XPIPEI and no last_job - pid %d",
			    (int) procpid);
		j = last_job;
		last_proc->next = p;
		last_proc = p;
	} else {
		j = new_job(); /* fills in j->job */
		/* we don't consider XXCOM's foreground since they don't get
		 * tty process group and we don't save or restore tty modes.
		 */
		j->flags = (flags & XXCOM) ? JF_XXCOM :
		    ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE));
		timerclear(&j->usrtime);
		timerclear(&j->systime);
		j->state = PRUNNING;
		j->pgrp = 0;
		j->ppid = procpid;
		j->age = ++njobs;
		j->proc_list = p;
		j->coproc_id = 0;
		last_job = j;
		last_proc = p;
		put_job(j, PJ_PAST_STOPPED);
	}

	snptreef(p->command, sizeof(p->command), "%T", t);

	/* create child process */
	forksleep = 1;
	while ((i = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
		if (intrsig)	 /* allow user to ^C out... */
			break;
		sleep(forksleep);
		forksleep <<= 1;
	}
	if (i < 0) {
		kill_job(j, SIGKILL);
		remove_job(j, "fork failed");
		sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
		errorf("cannot fork - try again");
	}
	ischild = i == 0;
	if (ischild)
		p->pid = procpid = getpid();
	else
		p->pid = i;

#ifdef JOBS
	/* job control set up */
	if (Flag(FMONITOR) && !(flags&XXCOM)) {
		int	dotty = 0;
		if (j->pgrp == 0) {	/* First process */
			j->pgrp = p->pid;
			dotty = 1;
		}

		/* set pgrp in both parent and child to deal with race
		 * condition
		 */
		setpgid(p->pid, j->pgrp);
		/* YYY: should this be
		   if (ttypgrp_ok && ischild && !(flags&XBGND))
			tcsetpgrp(tty_fd, j->pgrp);
		   instead? (see also YYY below)
		 */
		if (ttypgrp_ok && dotty && !(flags & XBGND))
			tcsetpgrp(tty_fd, j->pgrp);
	}
#endif /* JOBS */

	/* used to close pipe input fd */
	if (close_fd >= 0 && (((flags & XPCLOSE) && !ischild) ||
	    ((flags & XCCLOSE) && ischild)))
		close(close_fd);
	if (ischild) {		/* child */
		/* Do this before restoring signal */
		if (flags & XCOPROC)
			coproc_cleanup(false);
		sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
		cleanup_parents_env();
#ifdef JOBS
		/* If FMONITOR or FTALKING is set, these signals are ignored,
		 * if neither FMONITOR nor FTALKING are set, the signals have
		 * their inherited values.
		 */
		if (Flag(FMONITOR) && !(flags & XXCOM)) {
			for (i = NELEM(tt_sigs); --i >= 0; )
				setsig(&sigtraps[tt_sigs[i]], SIG_DFL,
				    SS_RESTORE_DFL|SS_FORCE);
		}
#endif /* JOBS */
		if (Flag(FBGNICE) && (flags & XBGND))
			nice(4);
		if ((flags & XBGND) && !Flag(FMONITOR)) {
			setsig(&sigtraps[SIGINT], SIG_IGN,
			    SS_RESTORE_IGN|SS_FORCE);
			setsig(&sigtraps[SIGQUIT], SIG_IGN,
			    SS_RESTORE_IGN|SS_FORCE);
			if (!(flags & (XPIPEI | XCOPROC))) {
				int fd = open("/dev/null", 0);
				if (fd != 0) {
					(void) ksh_dup2(fd, 0, true);
					close(fd);
				}
			}
		}
		remove_job(j, "child");	/* in case of `jobs` command */
		nzombie = 0;
#ifdef JOBS
		ttypgrp_ok = 0;
		Flag(FMONITOR) = 0;
#endif /* JOBS */
		Flag(FTALKING) = 0;
		tty_close();
		cleartraps();
		execute(t, (flags & XERROK) | XEXEC, NULL); /* no return */
		internal_errorf(0, "exchild: execute() returned");
		unwind(LLEAVE);
		/* NOTREACHED */
	}

	/* shell (parent) stuff */
	/* Ensure next child gets a (slightly) different $RANDOM sequence */
	change_random();
	if (!(flags & XPIPEO)) {	/* last process in a job */
#ifdef JOBS
		/* YYY: Is this needed? (see also YYY above)
		   if (Flag(FMONITOR) && !(flags&(XXCOM|XBGND)))
			tcsetpgrp(tty_fd, j->pgrp);
		*/
#endif /* JOBS */
		j_startjob(j);
		if (flags & XCOPROC) {
			j->coproc_id = coproc.id;
			coproc.njobs++; /* n jobs using co-process output */
			coproc.job = (void *) j; /* j using co-process input */
		}
		if (flags & XBGND) {
			j_set_async(j);
			if (Flag(FTALKING)) {
				shf_fprintf(shl_out, "[%d]", j->job);
				for (p = j->proc_list; p; p = p->next)
					shf_fprintf(shl_out, " %d", p->pid);
				shf_putchar('\n', shl_out);
				shf_flush(shl_out);
			}
		} else
			rv = j_waitj(j, JW_NONE, "jw:last proc");
	}

	sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);

	return rv;
}
Beispiel #10
0
int
exchild(struct op *t, int flags,
        int close_fd)       /* used if XPCLOSE or XCCLOSE */
{
        static Proc     *last_proc;     /* for pipelines */

        int             i;
        sigset_t        omask;
        Proc            *p;
        Job             *j;
        int             rv = 0;
        int             forksleep;
        int             ischild;

        if (flags & XEXEC)
                /* Clear XFORK|XPCLOSE|XCCLOSE|XCOPROC|XPIPEO|XPIPEI|XXCOM|XBGND
                 * (also done in another execute() below)
                 */
                return execute(t, flags & (XEXEC | XERROK));

        p = new_proc();
        p->next = (Proc *) 0;
        p->state = PRUNNING;
        p->status = 0;
        p->pid = 0;

        /* link process into jobs list */
        if (flags&XPIPEI) {     /* continuing with a pipe */
                if (!last_job)
                        internal_errorf(1,
                                "exchild: XPIPEI and no last_job - pid %d",
                                (int) procpid);
                j = last_job;
                last_proc->next = p;
                last_proc = p;
        } else {
                j = new_job(); /* fills in j->job */
                /* we don't consider XXCOM's foreground since they don't get
                 * tty process group and we don't save or restore tty modes.
                 */
                j->flags = (flags & XXCOM) ? JF_XXCOM :
                        ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE));
                j->usrtime = j->systime = 0;
                j->state = PRUNNING;
                j->pgrp = 0;
                j->ppid = procpid;
                j->age = ++njobs;
                j->proc_list = p;
                j->coproc_id = 0;
                last_job = j;
                last_proc = p;
                put_job(j, PJ_PAST_STOPPED);
        }

        snptreef(p->command, sizeof(p->command), "%T", t);

        /* create child process */
        forksleep = 1;
        while ((i = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
                if (intrsig)     /* allow user to ^C out... */
                        break;
                sleep(forksleep);
                forksleep <<= 1;
        }
        if (i < 0) {
                kill_job(j, SIGKILL);
                remove_job(j, "fork failed");
                sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
                errorf("cannot fork - try again");
        }
        ischild = i == 0;
        if (ischild)
                p->pid = procpid = getpid();
        else
                p->pid = i;

        /* used to close pipe input fd */
        if (close_fd >= 0 && (((flags & XPCLOSE) && !ischild) ||
                ((flags & XCCLOSE) && ischild)))
                        close(close_fd);
        if (ischild) {          /* child */
                /* Do this before restoring signal */
                if (flags & XCOPROC)
                        coproc_cleanup(false);
                sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
		cleanup_parents_env();
                if ((flags & XBGND)) {
                        setsig(&sigtraps[SIGINT], SIG_IGN,
                                SS_RESTORE_IGN|SS_FORCE);
                        setsig(&sigtraps[SIGQUIT], SIG_IGN,
                                SS_RESTORE_IGN|SS_FORCE);
                        if (!(flags & (XPIPEI | XCOPROC))) {
                                int fd = open("/dev/null", 0);
                                if (fd != 0) {
				        (void) ksh_dup2(fd, 0, true);
                                        close(fd);
                                }

                        }
                }
                remove_job(j, "child"); /* in case of `jobs` command */
                nzombie = 0;
                Flag(FTALKING) = 0;
                tty_close();
                cleartraps();
                execute(t, (flags & XERROK) | XEXEC); /* no return */
                internal_errorf(0, "exchild: execute() returned");
                unwind(LLEAVE);
                /* NOTREACHED */
        }

        /* shell (parent) stuff */
        /* Ensure next child gets a (slightly) different $RANDOM sequence */
        change_random();
        if (!(flags & XPIPEO)) {        /* last process in a job */
                j_startjob(j);
                if (flags & XCOPROC) {
                        j->coproc_id = coproc.id;
                        coproc.njobs++; /* n jobs using co-process output */
                        coproc.job = (void *) j; /* j using co-process input */
                }
                if (flags & XBGND) {
                        j_set_async(j);
                        if (Flag(FTALKING)) {
                                shf_fprintf(shl_out, "[%d]", j->job);
                                for (p = j->proc_list; p; p = p->next)
                                        shf_fprintf(shl_out, " %d", p->pid);
                                shf_putchar('\n', shl_out);
                                shf_flush(shl_out);
                        }
                } else
                        rv = j_waitj(j, JW_NONE, "jw:last proc");
        }

        sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);

        return rv;
}
Beispiel #11
0
/*
 * Print things in columns and rows - func() is called to format
 * the i-th element
 */
void
print_columns(struct shf *shf, int n,
    char *(*func)(char *, int, int, const void *),
    const void *arg, int max_oct, int max_col, bool prefcol)
{
	int i, r, c, rows, cols, nspace;
	char *str;

	if (n <= 0) {
#ifndef MKSH_SMALL
		internal_warningf("print_columns called with n=%d <= 0", n);
#endif
		return;
	}

	++max_oct;
	str = alloc(max_oct, ATEMP);

	/* ensure x_cols is valid first */
	if (x_cols < MIN_COLS)
		change_winsz();

	/*
	 * We use (max_col + 1) to consider the space separator.
	 * Note that no space is printed after the last column
	 * to avoid problems with terminals that have auto-wrap.
	 */
	cols = x_cols / (max_col + 1);

	/* if we can only print one column anyway, skip the goo */
	if (cols < 2) {
		for (i = 0; i < n; ++i)
			shf_fprintf(shf, "%s \n",
			    (*func)(str, max_oct, i, arg));
		goto out;
	}

	rows = (n + cols - 1) / cols;
	if (prefcol && cols > rows) {
		i = rows;
		rows = cols > n ? n : cols;
		cols = i;
	}

	max_col = -max_col;
	nspace = (x_cols + max_col * cols) / cols;
	if (nspace <= 0)
		nspace = 1;
	for (r = 0; r < rows; r++) {
		for (c = 0; c < cols; c++) {
			i = c * rows + r;
			if (i < n) {
				shf_fprintf(shf, "%*s", max_col,
				    (*func)(str, max_oct, i, arg));
				if (c + 1 < cols)
					shf_fprintf(shf, "%*s", nspace, null);
			}
		}
		shf_putchar('\n', shf);
	}
 out:
	afree(str, ATEMP);
}
Beispiel #12
0
/*
 * Print things in columns and rows - func() is called to format
 * the i-th element
 */
void
print_columns(struct shf *shf, unsigned int n,
    void (*func)(char *, size_t, unsigned int, const void *),
    const void *arg, size_t max_oct, size_t max_colz, bool prefcol)
{
	unsigned int i, r, c, rows, cols, nspace, max_col;
	char *str;

	if (!n)
		return;

	if (max_colz > 2147483646) {
#ifndef MKSH_SMALL
		internal_warningf("print_columns called with %s=%zu >= INT_MAX",
		    "max_col", max_colz);
#endif
		return;
	}
	max_col = (unsigned int)max_colz;

	if (max_oct > 2147483646) {
#ifndef MKSH_SMALL
		internal_warningf("print_columns called with %s=%zu >= INT_MAX",
		    "max_oct", max_oct);
#endif
		return;
	}
	++max_oct;
	str = alloc(max_oct, ATEMP);

	/*
	 * We use (max_col + 2) to consider the separator space.
	 * Note that no spaces are printed after the last column
	 * to avoid problems with terminals that have auto-wrap,
	 * but we need to also take this into account in x_cols.
	 */
	cols = (x_cols + 1) / (max_col + 2);

	/* if we can only print one column anyway, skip the goo */
	if (cols < 2) {
		for (i = 0; i < n; ++i) {
			(*func)(str, max_oct, i, arg);
			shf_puts(str, shf);
			shf_putc('\n', shf);
		}
		goto out;
	}

	rows = (n + cols - 1) / cols;
	if (prefcol && cols > rows) {
		cols = rows;
		rows = (n + cols - 1) / cols;
	}

	nspace = (x_cols - max_col * cols) / cols;
	if (nspace < 2)
		nspace = 2;
	max_col = -max_col;
	for (r = 0; r < rows; r++) {
		for (c = 0; c < cols; c++) {
			if ((i = c * rows + r) >= n)
				break;
			(*func)(str, max_oct, i, arg);
			if (i + rows >= n)
				shf_puts(str, shf);
			else
				shf_fprintf(shf, "%*s%*s",
				    max_col, str, nspace, null);
		}
		shf_putchar('\n', shf);
	}
 out:
	afree(str, ATEMP);
}
Beispiel #13
0
/* variant of fputs for ptreef and wdstrip */
static const char *
wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
{
	int c;
	const char *cs;

	/*-
	 * problems:
	 *	`...` -> $(...)
	 *	'foo' -> "foo"
	 *	x${foo:-"hi"} -> x${foo:-hi} unless WDS_TPUTS
	 *	x${foo:-'hi'} -> x${foo:-hi}
	 * could change encoding to:
	 *	OQUOTE ["'] ... CQUOTE ["']
	 *	COMSUB [(`] ...\0	(handle $ ` \ and maybe " in `...` case)
	 */
	while (/* CONSTCOND */ 1)
		switch (*wp++) {
		case EOS:
			return (--wp);
		case ADELIM:
			if (ord(*wp) == ORD(/*{*/ '}')) {
				++wp;
				goto wdvarput_csubst;
			}
			/* FALLTHROUGH */
		case CHAR:
			c = ord(*wp++);
			shf_putc(c, shf);
			break;
		case QCHAR:
			c = ord(*wp++);
			if (opmode & WDS_TPUTS)
				switch (c) {
				case ORD('\n'):
					if (quotelevel == 0) {
						c = ORD('\'');
						shf_putc(c, shf);
						shf_putc(ORD('\n'), shf);
					}
					break;
				default:
					if (quotelevel == 0)
						/* FALLTHROUGH */
				case ORD('"'):
				case ORD('`'):
				case ORD('$'):
				case ORD('\\'):
					  shf_putc(ORD('\\'), shf);
					break;
				}
			shf_putc(c, shf);
			break;
		case COMASUB:
		case COMSUB:
			shf_puts("$(", shf);
			cs = ")";
			if (ord(*wp) == ORD('(' /*)*/))
				shf_putc(' ', shf);
 pSUB:
			while ((c = *wp++) != 0)
				shf_putc(c, shf);
			shf_puts(cs, shf);
			break;
		case FUNASUB:
		case FUNSUB:
			c = ORD(' ');
			if (0)
				/* FALLTHROUGH */
		case VALSUB:
			  c = ORD('|');
			shf_putc('$', shf);
			shf_putc('{', shf);
			shf_putc(c, shf);
			cs = ";}";
			goto pSUB;
		case EXPRSUB:
			shf_puts("$((", shf);
			cs = "))";
			goto pSUB;
		case OQUOTE:
			if (opmode & WDS_TPUTS) {
				quotelevel++;
				shf_putc('"', shf);
			}
			break;
		case CQUOTE:
			if (opmode & WDS_TPUTS) {
				if (quotelevel)
					quotelevel--;
				shf_putc('"', shf);
			}
			break;
		case OSUBST:
			shf_putc('$', shf);
			if (ord(*wp++) == ORD('{'))
				shf_putc('{', shf);
			while ((c = *wp++) != 0)
				shf_putc(c, shf);
			wp = wdvarput(shf, wp, 0, opmode);
			break;
		case CSUBST:
			if (ord(*wp++) == ORD('}')) {
 wdvarput_csubst:
				shf_putc('}', shf);
			}
			return (wp);
		case OPAT:
			shf_putchar(*wp++, shf);
			shf_putc('(', shf);
			break;
		case SPAT:
			c = ORD('|');
			if (0)
				/* FALLTHROUGH */
		case CPAT:
			  c = ORD(/*(*/ ')');
			shf_putc(c, shf);
			break;
		}
}