1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1992 Diomidis Spinellis.
5 * Copyright (c) 1992, 1993, 1994
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * This code is derived from software contributed to Berkeley by
9 * Diomidis Spinellis of Imperial College, University of London.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include <sys/cdefs.h>
37__FBSDID("$FreeBSD$");
38
39#ifndef lint
40static const char sccsid[] = "@(#)process.c	8.6 (Berkeley) 4/20/94";
41#endif
42
43#include <sys/types.h>
44#include <sys/stat.h>
45#include <sys/ioctl.h>
46#include <sys/uio.h>
47
48#include <ctype.h>
49#include <err.h>
50#include <errno.h>
51#include <fcntl.h>
52#include <limits.h>
53#include <regex.h>
54#include <stdio.h>
55#include <stdlib.h>
56#include <string.h>
57#include <unistd.h>
58#include <wchar.h>
59#include <wctype.h>
60
61#include "defs.h"
62#include "extern.h"
63
64static SPACE HS, PS, SS, YS;
65#define	pd		PS.deleted
66#define	ps		PS.space
67#define	psl		PS.len
68#define	psanl		PS.append_newline
69#define	hs		HS.space
70#define	hsl		HS.len
71
72static inline int	 applies(struct s_command *);
73static void		 do_tr(struct s_tr *);
74static void		 flush_appends(void);
75static void		 lputs(char *, size_t);
76static int		 regexec_e(regex_t *, const char *, int, int, size_t,
77			     size_t);
78static void		 regsub(SPACE *, char *, char *);
79static int		 substitute(struct s_command *);
80
81struct s_appends *appends;	/* Array of pointers to strings to append. */
82static unsigned int appendx;	/* Index into appends array. */
83unsigned int appendnum;		/* Size of appends array. */
84
85static int lastaddr;		/* Set by applies if last address of a range. */
86static int sdone;		/* If any substitutes since last line input. */
87				/* Iov structure for 'w' commands. */
88static regex_t *defpreg;
89size_t maxnsub;
90regmatch_t *match;
91
92#define OUT() do {							\
93	fwrite(ps, 1, psl, outfile);					\
94	if (psanl) fputc('\n', outfile);				\
95} while (0)
96
97void
98process(void)
99{
100	struct s_command *cp;
101	SPACE tspace;
102	size_t oldpsl;
103	char *p;
104	int oldpsanl;
105
106	p = NULL;
107	oldpsanl = oldpsl = 0;
108
109	for (linenum = 0; mf_fgets(&PS, REPLACE);) {
110		pd = 0;
111top:
112		cp = prog;
113redirect:
114		while (cp != NULL) {
115			if (!applies(cp)) {
116				cp = cp->next;
117				continue;
118			}
119			switch (cp->code) {
120			case '{':
121				cp = cp->u.c;
122				goto redirect;
123			case 'a':
124				if (appendx >= appendnum)
125					if ((appends = realloc(appends,
126					    sizeof(struct s_appends) *
127					    (appendnum *= 2))) == NULL)
128						err(1, "realloc");
129				appends[appendx].type = AP_STRING;
130				appends[appendx].s = cp->t;
131				appends[appendx].len = strlen(cp->t);
132				appendx++;
133				break;
134			case 'b':
135				cp = cp->u.c;
136				goto redirect;
137			case 'c':
138				pd = 1;
139				psl = 0;
140				if (cp->a2 == NULL || lastaddr || lastline())
141					(void)fprintf(outfile, "%s", cp->t);
142				break;
143			case 'd':
144				pd = 1;
145				goto new;
146			case 'D':
147				if (pd)
148					goto new;
149				if (psl == 0 ||
150				    (p = memchr(ps, '\n', psl)) == NULL) {
151					pd = 1;
152					goto new;
153				} else {
154					psl -= (p + 1) - ps;
155					memmove(ps, p + 1, psl);
156					goto top;
157				}
158			case 'g':
159				cspace(&PS, hs, hsl, REPLACE);
160				break;
161			case 'G':
162				cspace(&PS, "\n", 1, APPEND);
163				cspace(&PS, hs, hsl, APPEND);
164				break;
165			case 'h':
166				cspace(&HS, ps, psl, REPLACE);
167				break;
168			case 'H':
169				cspace(&HS, "\n", 1, APPEND);
170				cspace(&HS, ps, psl, APPEND);
171				break;
172			case 'i':
173				(void)fprintf(outfile, "%s", cp->t);
174				break;
175			case 'l':
176				lputs(ps, psl);
177				break;
178			case 'n':
179				if (!nflag && !pd)
180					OUT();
181				flush_appends();
182				if (!mf_fgets(&PS, REPLACE))
183					exit(0);
184				pd = 0;
185				break;
186			case 'N':
187				flush_appends();
188				cspace(&PS, "\n", 1, APPEND);
189				if (!mf_fgets(&PS, APPEND))
190					exit(0);
191				break;
192			case 'p':
193				if (pd)
194					break;
195				OUT();
196				break;
197			case 'P':
198				if (pd)
199					break;
200				if ((p = memchr(ps, '\n', psl)) != NULL) {
201					oldpsl = psl;
202					oldpsanl = psanl;
203					psl = p - ps;
204					psanl = 1;
205				}
206				OUT();
207				if (p != NULL) {
208					psl = oldpsl;
209					psanl = oldpsanl;
210				}
211				break;
212			case 'q':
213				if (inplace == NULL) {
214					if (!nflag && !pd)
215						OUT();
216					flush_appends();
217					exit(0);
218				}
219				quit = 1;
220				break;
221			case 'r':
222				if (appendx >= appendnum)
223					if ((appends = realloc(appends,
224					    sizeof(struct s_appends) *
225					    (appendnum *= 2))) == NULL)
226						err(1, "realloc");
227				appends[appendx].type = AP_FILE;
228				appends[appendx].s = cp->t;
229				appends[appendx].len = strlen(cp->t);
230				appendx++;
231				break;
232			case 's':
233				sdone |= substitute(cp);
234				break;
235			case 't':
236				if (sdone) {
237					sdone = 0;
238					cp = cp->u.c;
239					goto redirect;
240				}
241				break;
242			case 'w':
243				if (pd)
244					break;
245				if (cp->u.fd == -1 && (cp->u.fd = open(cp->t,
246				    O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
247				    DEFFILEMODE)) == -1)
248					err(1, "%s", cp->t);
249				if (write(cp->u.fd, ps, psl) != (ssize_t)psl ||
250				    write(cp->u.fd, "\n", 1) != 1)
251					err(1, "%s", cp->t);
252				break;
253			case 'x':
254				/*
255				 * If the hold space is null, make it empty
256				 * but not null.  Otherwise the pattern space
257				 * will become null after the swap, which is
258				 * an abnormal condition.
259				 */
260				if (hs == NULL)
261					cspace(&HS, "", 0, REPLACE);
262				tspace = PS;
263				PS = HS;
264				psanl = tspace.append_newline;
265				HS = tspace;
266				break;
267			case 'y':
268				if (pd || psl == 0)
269					break;
270				do_tr(cp->u.y);
271				break;
272			case ':':
273			case '}':
274				break;
275			case '=':
276				(void)fprintf(outfile, "%lu\n", linenum);
277			}
278			cp = cp->next;
279		} /* for all cp */
280
281new:		if (!nflag && !pd)
282			OUT();
283		flush_appends();
284	} /* for all lines */
285}
286
287/*
288 * TRUE if the address passed matches the current program state
289 * (lastline, linenumber, ps).
290 */
291#define	MATCH(a)							\
292	((a)->type == AT_RE ? regexec_e((a)->u.r, ps, 0, 1, 0, psl) :	\
293	    (a)->type == AT_LINE ? linenum == (a)->u.l : lastline())
294
295/*
296 * Return TRUE if the command applies to the current line.  Sets the start
297 * line for process ranges.  Interprets the non-select (``!'') flag.
298 */
299static inline int
300applies(struct s_command *cp)
301{
302	int r;
303
304	lastaddr = 0;
305	if (cp->a1 == NULL && cp->a2 == NULL)
306		r = 1;
307	else if (cp->a2)
308		if (cp->startline > 0) {
309                        switch (cp->a2->type) {
310                        case AT_RELLINE:
311                                if (linenum - cp->startline <= cp->a2->u.l)
312                                        r = 1;
313                                else {
314				        cp->startline = 0;
315				        r = 0;
316                                }
317                                break;
318                        default:
319                                if (MATCH(cp->a2)) {
320                                        cp->startline = 0;
321                                        lastaddr = 1;
322                                        r = 1;
323                                } else if (cp->a2->type == AT_LINE &&
324                                            linenum > cp->a2->u.l) {
325                                        /*
326                                         * We missed the 2nd address due to a
327                                         * branch, so just close the range and
328                                         * return false.
329                                         */
330                                        cp->startline = 0;
331                                        r = 0;
332                                } else
333                                        r = 1;
334                        }
335		} else if (cp->a1 && MATCH(cp->a1)) {
336			/*
337			 * If the second address is a number less than or
338			 * equal to the line number first selected, only
339			 * one line shall be selected.
340			 *	-- POSIX 1003.2
341			 * Likewise if the relative second line address is zero.
342			 */
343			if ((cp->a2->type == AT_LINE &&
344			    linenum >= cp->a2->u.l) ||
345			    (cp->a2->type == AT_RELLINE && cp->a2->u.l == 0))
346				lastaddr = 1;
347			else {
348				cp->startline = linenum;
349			}
350			r = 1;
351		} else
352			r = 0;
353	else
354		r = MATCH(cp->a1);
355	return (cp->nonsel ? ! r : r);
356}
357
358/*
359 * Reset the sed processor to its initial state.
360 */
361void
362resetstate(void)
363{
364	struct s_command *cp;
365
366	/*
367	 * Reset all in-range markers.
368	 */
369	for (cp = prog; cp; cp = cp->code == '{' ? cp->u.c : cp->next)
370		if (cp->a2)
371			cp->startline = 0;
372
373	/*
374	 * Clear out the hold space.
375	 */
376	cspace(&HS, "", 0, REPLACE);
377}
378
379/*
380 * substitute --
381 *	Do substitutions in the pattern space.  Currently, we build a
382 *	copy of the new pattern space in the substitute space structure
383 *	and then swap them.
384 */
385static int
386substitute(struct s_command *cp)
387{
388	SPACE tspace;
389	regex_t *re;
390	regoff_t slen;
391	int lastempty, n;
392	regoff_t le = 0;
393	char *s;
394
395	s = ps;
396	re = cp->u.s->re;
397	if (re == NULL) {
398		if (defpreg != NULL && cp->u.s->maxbref > defpreg->re_nsub) {
399			linenum = cp->u.s->linenum;
400			errx(1, "%lu: %s: \\%u not defined in the RE",
401					linenum, fname, cp->u.s->maxbref);
402		}
403	}
404	if (!regexec_e(re, ps, 0, 0, 0, psl))
405		return (0);
406
407	SS.len = 0;				/* Clean substitute space. */
408	slen = psl;
409	n = cp->u.s->n;
410	lastempty = 1;
411
412	do {
413		/* Copy the leading retained string. */
414		if (n <= 1 && (match[0].rm_so > le))
415			cspace(&SS, s, match[0].rm_so - le, APPEND);
416
417		/* Skip zero-length matches right after other matches. */
418		if (lastempty || (match[0].rm_so - le) ||
419		    match[0].rm_so != match[0].rm_eo) {
420			if (n <= 1) {
421				/* Want this match: append replacement. */
422				regsub(&SS, ps, cp->u.s->new);
423				if (n == 1)
424					n = -1;
425			} else {
426				/* Want a later match: append original. */
427				if (match[0].rm_eo - le)
428					cspace(&SS, s, match[0].rm_eo - le,
429					    APPEND);
430				n--;
431			}
432		}
433
434		/* Move past this match. */
435		s = ps + match[0].rm_eo;
436		slen = psl - match[0].rm_eo;
437		le = match[0].rm_eo;
438
439		/*
440		 * After a zero-length match, advance one byte,
441		 * and at the end of the line, terminate.
442		 */
443		if (match[0].rm_so == match[0].rm_eo) {
444			if (*s == '\0' || *s == '\n')
445				slen = -1;
446			else
447				slen--;
448			if (*s != '\0') {
449			 	cspace(&SS, s++, 1, APPEND);
450				le++;
451			}
452			lastempty = 1;
453		} else
454			lastempty = 0;
455
456	} while (n >= 0 && slen >= 0 &&
457	    regexec_e(re, ps, REG_NOTBOL, 0, le, psl));
458
459	/* Did not find the requested number of matches. */
460	if (n > 0)
461		return (0);
462
463	/* Copy the trailing retained string. */
464	if (slen > 0)
465		cspace(&SS, s, slen, APPEND);
466
467	/*
468	 * Swap the substitute space and the pattern space, and make sure
469	 * that any leftover pointers into stdio memory get lost.
470	 */
471	tspace = PS;
472	PS = SS;
473	psanl = tspace.append_newline;
474	SS = tspace;
475	SS.space = SS.back;
476
477	/* Handle the 'p' flag. */
478	if (cp->u.s->p)
479		OUT();
480
481	/* Handle the 'w' flag. */
482	if (cp->u.s->wfile && !pd) {
483		if (cp->u.s->wfd == -1 && (cp->u.s->wfd = open(cp->u.s->wfile,
484		    O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, DEFFILEMODE)) == -1)
485			err(1, "%s", cp->u.s->wfile);
486		if (write(cp->u.s->wfd, ps, psl) != (ssize_t)psl ||
487		    write(cp->u.s->wfd, "\n", 1) != 1)
488			err(1, "%s", cp->u.s->wfile);
489	}
490	return (1);
491}
492
493/*
494 * do_tr --
495 *	Perform translation ('y' command) in the pattern space.
496 */
497static void
498do_tr(struct s_tr *y)
499{
500	SPACE tmp;
501	char c, *p;
502	size_t clen, left;
503	int i;
504
505	if (MB_CUR_MAX == 1) {
506		/*
507		 * Single-byte encoding: perform in-place translation
508		 * of the pattern space.
509		 */
510		for (p = ps; p < &ps[psl]; p++)
511			*p = y->bytetab[(u_char)*p];
512	} else {
513		/*
514		 * Multi-byte encoding: perform translation into the
515		 * translation space, then swap the translation and
516		 * pattern spaces.
517		 */
518		/* Clean translation space. */
519		YS.len = 0;
520		for (p = ps, left = psl; left > 0; p += clen, left -= clen) {
521			if ((c = y->bytetab[(u_char)*p]) != '\0') {
522				cspace(&YS, &c, 1, APPEND);
523				clen = 1;
524				continue;
525			}
526			for (i = 0; i < y->nmultis; i++)
527				if (left >= y->multis[i].fromlen &&
528				    memcmp(p, y->multis[i].from,
529				    y->multis[i].fromlen) == 0)
530					break;
531			if (i < y->nmultis) {
532				cspace(&YS, y->multis[i].to,
533				    y->multis[i].tolen, APPEND);
534				clen = y->multis[i].fromlen;
535			} else {
536				cspace(&YS, p, 1, APPEND);
537				clen = 1;
538			}
539		}
540		/* Swap the translation space and the pattern space. */
541		tmp = PS;
542		PS = YS;
543		psanl = tmp.append_newline;
544		YS = tmp;
545		YS.space = YS.back;
546	}
547}
548
549/*
550 * Flush append requests.  Always called before reading a line,
551 * therefore it also resets the substitution done (sdone) flag.
552 */
553static void
554flush_appends(void)
555{
556	FILE *f;
557	unsigned int count, idx;
558	char buf[8 * 1024];
559
560	for (idx = 0; idx < appendx; idx++)
561		switch (appends[idx].type) {
562		case AP_STRING:
563			fwrite(appends[idx].s, sizeof(char), appends[idx].len,
564			    outfile);
565			break;
566		case AP_FILE:
567			/*
568			 * Read files probably shouldn't be cached.  Since
569			 * it's not an error to read a non-existent file,
570			 * it's possible that another program is interacting
571			 * with the sed script through the filesystem.  It
572			 * would be truly bizarre, but possible.  It's probably
573			 * not that big a performance win, anyhow.
574			 */
575			if ((f = fopen(appends[idx].s, "r")) == NULL)
576				break;
577			while ((count = fread(buf, sizeof(char), sizeof(buf), f)))
578				(void)fwrite(buf, sizeof(char), count, outfile);
579			(void)fclose(f);
580			break;
581		}
582	if (ferror(outfile))
583		errx(1, "%s: %s", outfname, strerror(errno ? errno : EIO));
584	appendx = sdone = 0;
585}
586
587static void
588lputs(char *s, size_t len)
589{
590	static const char escapes[] = "\\\a\b\f\r\t\v";
591	int c, col, width;
592	const char *p;
593	struct winsize win;
594	static int termwidth = -1;
595	size_t clen, i;
596	wchar_t wc;
597	mbstate_t mbs;
598
599	if (outfile != stdout)
600		termwidth = 60;
601	if (termwidth == -1) {
602		if ((p = getenv("COLUMNS")) && *p != '\0')
603			termwidth = atoi(p);
604		else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 &&
605		    win.ws_col > 0)
606			termwidth = win.ws_col;
607		else
608			termwidth = 60;
609	}
610	if (termwidth <= 0)
611		termwidth = 1;
612
613	memset(&mbs, 0, sizeof(mbs));
614	col = 0;
615	while (len != 0) {
616		clen = mbrtowc(&wc, s, len, &mbs);
617		if (clen == 0)
618			clen = 1;
619		if (clen == (size_t)-1 || clen == (size_t)-2) {
620			wc = (unsigned char)*s;
621			clen = 1;
622			memset(&mbs, 0, sizeof(mbs));
623		}
624		if (wc == '\n') {
625			if (col + 1 >= termwidth)
626				fprintf(outfile, "\\\n");
627			fputc('$', outfile);
628			fputc('\n', outfile);
629			col = 0;
630		} else if (iswprint(wc)) {
631			width = wcwidth(wc);
632			if (col + width >= termwidth) {
633				fprintf(outfile, "\\\n");
634				col = 0;
635			}
636			fwrite(s, 1, clen, outfile);
637			col += width;
638		} else if (wc != L'\0' && (c = wctob(wc)) != EOF &&
639		    (p = strchr(escapes, c)) != NULL) {
640			if (col + 2 >= termwidth) {
641				fprintf(outfile, "\\\n");
642				col = 0;
643			}
644			fprintf(outfile, "\\%c", "\\abfrtv"[p - escapes]);
645			col += 2;
646		} else {
647			if (col + 4 * clen >= (unsigned)termwidth) {
648				fprintf(outfile, "\\\n");
649				col = 0;
650			}
651			for (i = 0; i < clen; i++)
652				fprintf(outfile, "\\%03o",
653				    (int)(unsigned char)s[i]);
654			col += 4 * clen;
655		}
656		s += clen;
657		len -= clen;
658	}
659	if (col + 1 >= termwidth)
660		fprintf(outfile, "\\\n");
661	(void)fputc('$', outfile);
662	(void)fputc('\n', outfile);
663	if (ferror(outfile))
664		errx(1, "%s: %s", outfname, strerror(errno ? errno : EIO));
665}
666
667static int
668regexec_e(regex_t *preg, const char *string, int eflags, int nomatch,
669	size_t start, size_t stop)
670{
671	int eval;
672
673	if (preg == NULL) {
674		if (defpreg == NULL)
675			errx(1, "first RE may not be empty");
676	} else
677		defpreg = preg;
678
679	/* Set anchors */
680	match[0].rm_so = start;
681	match[0].rm_eo = stop;
682
683	eval = regexec(defpreg, string,
684	    nomatch ? 0 : maxnsub + 1, match, eflags | REG_STARTEND);
685	switch(eval) {
686	case 0:
687		return (1);
688	case REG_NOMATCH:
689		return (0);
690	}
691	errx(1, "RE error: %s", strregerror(eval, defpreg));
692	/* NOTREACHED */
693}
694
695/*
696 * regsub - perform substitutions after a regexp match
697 * Based on a routine by Henry Spencer
698 */
699static void
700regsub(SPACE *sp, char *string, char *src)
701{
702	int len, no;
703	char c, *dst;
704
705#define	NEEDSP(reqlen)							\
706	/* XXX What is the +1 for? */					\
707	if (sp->len + (reqlen) + 1 >= sp->blen) {			\
708		sp->blen += (reqlen) + 1024;				\
709		if ((sp->space = sp->back = realloc(sp->back, sp->blen)) \
710		    == NULL)						\
711			err(1, "realloc");				\
712		dst = sp->space + sp->len;				\
713	}
714
715	dst = sp->space + sp->len;
716	while ((c = *src++) != '\0') {
717		if (c == '&')
718			no = 0;
719		else if (c == '\\' && isdigit((unsigned char)*src))
720			no = *src++ - '0';
721		else
722			no = -1;
723		if (no < 0) {		/* Ordinary character. */
724			if (c == '\\' && (*src == '\\' || *src == '&'))
725				c = *src++;
726			NEEDSP(1);
727			*dst++ = c;
728			++sp->len;
729		} else if (match[no].rm_so != -1 && match[no].rm_eo != -1) {
730			len = match[no].rm_eo - match[no].rm_so;
731			NEEDSP(len);
732			memmove(dst, string + match[no].rm_so, len);
733			dst += len;
734			sp->len += len;
735		}
736	}
737	NEEDSP(1);
738	*dst = '\0';
739}
740
741/*
742 * cspace --
743 *	Concatenate space: append the source space to the destination space,
744 *	allocating new space as necessary.
745 */
746void
747cspace(SPACE *sp, const char *p, size_t len, enum e_spflag spflag)
748{
749	size_t tlen;
750
751	/* Make sure SPACE has enough memory and ramp up quickly. */
752	tlen = sp->len + len + 1;
753	if (tlen > sp->blen) {
754		sp->blen = tlen + 1024;
755		if ((sp->space = sp->back = realloc(sp->back, sp->blen)) ==
756		    NULL)
757			err(1, "realloc");
758	}
759
760	if (spflag == REPLACE)
761		sp->len = 0;
762
763	memmove(sp->space + sp->len, p, len);
764
765	sp->space[sp->len += len] = '\0';
766}
767
768/*
769 * Close all cached opened files and report any errors
770 */
771void
772cfclose(struct s_command *cp, struct s_command *end)
773{
774
775	for (; cp != end; cp = cp->next)
776		switch(cp->code) {
777		case 's':
778			if (cp->u.s->wfd != -1 && close(cp->u.s->wfd))
779				err(1, "%s", cp->u.s->wfile);
780			cp->u.s->wfd = -1;
781			break;
782		case 'w':
783			if (cp->u.fd != -1 && close(cp->u.fd))
784				err(1, "%s", cp->t);
785			cp->u.fd = -1;
786			break;
787		case '{':
788			cfclose(cp->u.c, cp->next);
789			break;
790		}
791}
792