Home | History | Annotate | Line # | Download | only in src
      1 /*	$NetBSD: rcsgen.c,v 1.2 2016/01/14 04:22:39 christos Exp $	*/
      2 
      3 /* Generate RCS revisions.  */
      4 
      5 /* Copyright 1982, 1988, 1989 Walter Tichy
      6    Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
      7    Distributed under license by the Free Software Foundation, Inc.
      8 
      9 This file is part of RCS.
     10 
     11 RCS is free software; you can redistribute it and/or modify
     12 it under the terms of the GNU General Public License as published by
     13 the Free Software Foundation; either version 2, or (at your option)
     14 any later version.
     15 
     16 RCS is distributed in the hope that it will be useful,
     17 but WITHOUT ANY WARRANTY; without even the implied warranty of
     18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     19 GNU General Public License for more details.
     20 
     21 You should have received a copy of the GNU General Public License
     22 along with RCS; see the file COPYING.
     23 If not, write to the Free Software Foundation,
     24 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
     25 
     26 Report problems and direct all questions to:
     27 
     28     rcs-bugs (at) cs.purdue.edu
     29 
     30 */
     31 
     32 /*
     33  * Log: rcsgen.c,v
     34  * Revision 5.16  1995/06/16 06:19:24  eggert
     35  * Update FSF address.
     36  *
     37  * Revision 5.15  1995/06/01 16:23:43  eggert
     38  * (putadmin): Open RCS file with FOPEN_WB.
     39  *
     40  * Revision 5.14  1994/03/17 14:05:48  eggert
     41  * Work around SVR4 stdio performance bug.
     42  * Flush stderr after prompt.  Remove lint.
     43  *
     44  * Revision 5.13  1993/11/03 17:42:27  eggert
     45  * Don't discard ignored phrases.  Improve quality of diagnostics.
     46  *
     47  * Revision 5.12  1992/07/28  16:12:44  eggert
     48  * Statement macro names now end in _.
     49  * Be consistent about pathnames vs filenames.
     50  *
     51  * Revision 5.11  1992/01/24  18:44:19  eggert
     52  * Move put routines here from rcssyn.c.
     53  * Add support for bad_creat0.
     54  *
     55  * Revision 5.10  1991/10/07  17:32:46  eggert
     56  * Fix log bugs, e.g. ci -t/dev/null when has_mmap.
     57  *
     58  * Revision 5.9  1991/09/10  22:15:46  eggert
     59  * Fix test for redirected stdin.
     60  *
     61  * Revision 5.8  1991/08/19  03:13:55  eggert
     62  * Add piece tables.  Tune.
     63  *
     64  * Revision 5.7  1991/04/21  11:58:24  eggert
     65  * Add MS-DOS support.
     66  *
     67  * Revision 5.6  1990/12/27  19:54:26  eggert
     68  * Fix bug: rcs -t inserted \n, making RCS file grow.
     69  *
     70  * Revision 5.5  1990/12/04  05:18:45  eggert
     71  * Use -I for prompts and -q for diagnostics.
     72  *
     73  * Revision 5.4  1990/11/01  05:03:47  eggert
     74  * Add -I and new -t behavior.  Permit arbitrary data in logs.
     75  *
     76  * Revision 5.3  1990/09/21  06:12:43  hammer
     77  * made putdesc() treat stdin the same whether or not it was from a terminal
     78  * by making it recognize that a single '.' was then end of the
     79  * description always
     80  *
     81  * Revision 5.2  1990/09/04  08:02:25  eggert
     82  * Fix `co -p1.1 -ko' bug.  Standardize yes-or-no procedure.
     83  *
     84  * Revision 5.1  1990/08/29  07:14:01  eggert
     85  * Clean old log messages too.
     86  *
     87  * Revision 5.0  1990/08/22  08:12:52  eggert
     88  * Remove compile-time limits; use malloc instead.
     89  * Ansify and Posixate.
     90  *
     91  * Revision 4.7  89/05/01  15:12:49  narten
     92  * changed copyright header to reflect current distribution rules
     93  *
     94  * Revision 4.6  88/08/28  14:59:10  eggert
     95  * Shrink stdio code size; allow cc -R; remove lint; isatty() -> ttystdin()
     96  *
     97  * Revision 4.5  87/12/18  11:43:25  narten
     98  * additional lint cleanups, and a bug fix from the 4.3BSD version that
     99  * keeps "ci" from sticking a '\377' into the description if you run it
    100  * with a zero-length file as the description. (Guy Harris)
    101  *
    102  * Revision 4.4  87/10/18  10:35:10  narten
    103  * Updating version numbers. Changes relative to 1.1 actually relative to
    104  * 4.2
    105  *
    106  * Revision 1.3  87/09/24  13:59:51  narten
    107  * Sources now pass through lint (if you ignore printf/sprintf/fprintf
    108  * warnings)
    109  *
    110  * Revision 1.2  87/03/27  14:22:27  jenkins
    111  * Port to suns
    112  *
    113  * Revision 4.2  83/12/02  23:01:39  wft
    114  * merged 4.1 and 3.3.1.1 (clearerr(stdin)).
    115  *
    116  * Revision 4.1  83/05/10  16:03:33  wft
    117  * Changed putamin() to abort if trying to reread redirected stdin.
    118  * Fixed getdesc() to output a prompt on initial newline.
    119  *
    120  * Revision 3.3.1.1  83/10/19  04:21:51  lepreau
    121  * Added clearerr(stdin) for re-reading description from stdin.
    122  *
    123  * Revision 3.3  82/11/28  21:36:49  wft
    124  * 4.2 prerelease
    125  *
    126  * Revision 3.3  82/11/28  21:36:49  wft
    127  * Replaced ferror() followed by fclose() with ffclose().
    128  * Putdesc() now suppresses the prompts if stdin
    129  * is not a terminal. A pointer to the current log message is now
    130  * inserted into the corresponding delta, rather than leaving it in a
    131  * global variable.
    132  *
    133  * Revision 3.2  82/10/18  21:11:26  wft
    134  * I added checks for write errors during editing, and improved
    135  * the prompt on putdesc().
    136  *
    137  * Revision 3.1  82/10/13  15:55:09  wft
    138  * corrected type of variables assigned to by getc (char --> int)
    139  */
    140 
    141 
    142 
    143 
    144 #include "rcsbase.h"
    145 
    146 libId(genId, "Id: rcsgen.c,v 5.16 1995/06/16 06:19:24 eggert Exp ")
    147 
    148 int interactiveflag;  /* Should we act as if stdin is a tty?  */
    149 struct buf curlogbuf;  /* buffer for current log message */
    150 
    151 enum stringwork { enter, copy, edit, expand, edit_expand };
    152 
    153 static void putdelta P((struct hshentry const*,FILE*));
    154 static void scandeltatext P((struct hshentry*,enum stringwork,int));
    155 
    156 
    157 
    158 
    159 	char const *
    160 buildrevision(deltas, target, outfile, expandflag)
    161 	struct hshentries const *deltas;
    162 	struct hshentry *target;
    163 	FILE *outfile;
    164 	int expandflag;
    165 /* Function: Generates the revision given by target
    166  * by retrieving all deltas given by parameter deltas and combining them.
    167  * If outfile is set, the revision is output to it,
    168  * otherwise written into a temporary file.
    169  * Temporary files are allocated by maketemp().
    170  * if expandflag is set, keyword expansion is performed.
    171  * Return 0 if outfile is set, the name of the temporary file otherwise.
    172  *
    173  * Algorithm: Copy initial revision unchanged.  Then edit all revisions but
    174  * the last one into it, alternating input and output files (resultname and
    175  * editname). The last revision is then edited in, performing simultaneous
    176  * keyword substitution (this saves one extra pass).
    177  * All this simplifies if only one revision needs to be generated,
    178  * or no keyword expansion is necessary, or if output goes to stdout.
    179  */
    180 {
    181 	if (deltas->first == target) {
    182                 /* only latest revision to generate */
    183 		openfcopy(outfile);
    184 		scandeltatext(target, expandflag?expand:copy, true);
    185 		if (outfile)
    186 			return 0;
    187 		else {
    188 			Ozclose(&fcopy);
    189 			return resultname;
    190 		}
    191         } else {
    192                 /* several revisions to generate */
    193 		/* Get initial revision without keyword expansion.  */
    194 		scandeltatext(deltas->first, enter, false);
    195 		while ((deltas=deltas->rest)->rest) {
    196                         /* do all deltas except last one */
    197 			scandeltatext(deltas->first, edit, false);
    198                 }
    199 		if (expandflag || outfile) {
    200                         /* first, get to beginning of file*/
    201 			finishedit((struct hshentry*)0, outfile, false);
    202                 }
    203 		scandeltatext(target, expandflag?edit_expand:edit, true);
    204 		finishedit(
    205 			expandflag ? target : (struct hshentry*)0,
    206 			outfile, true
    207 		);
    208 		if (outfile)
    209 			return 0;
    210 		Ozclose(&fcopy);
    211 		return resultname;
    212         }
    213 }
    214 
    215 
    216 
    217 	static void
    218 scandeltatext(delta, func, needlog)
    219 	struct hshentry *delta;
    220 	enum stringwork func;
    221 	int needlog;
    222 /* Function: Scans delta text nodes up to and including the one given
    223  * by delta. For the one given by delta, the log message is saved into
    224  * delta->log if needlog is set; func specifies how to handle the text.
    225  * Similarly, if needlog, delta->igtext is set to the ignored phrases.
    226  * Assumes the initial lexeme must be read in first.
    227  * Does not advance nexttok after it is finished.
    228  */
    229 {
    230 	struct hshentry const *nextdelta;
    231 	struct cbuf cb;
    232 
    233         for (;;) {
    234 		if (eoflex())
    235 		    fatserror("can't find delta for revision %s", delta->num);
    236                 nextlex();
    237                 if (!(nextdelta=getnum())) {
    238 		    fatserror("delta number corrupted");
    239                 }
    240 		getkeystring(Klog);
    241 		if (needlog && delta==nextdelta) {
    242 			cb = savestring(&curlogbuf);
    243 			delta->log = cleanlogmsg(curlogbuf.string, cb.size);
    244 			nextlex();
    245 			delta->igtext = getphrases(Ktext);
    246                 } else {readstring();
    247 			ignorephrases(Ktext);
    248                 }
    249 		getkeystring(Ktext);
    250 
    251 		if (delta==nextdelta)
    252 			break;
    253 		readstring(); /* skip over it */
    254 
    255 	}
    256 	switch (func) {
    257 		case enter: enterstring(); break;
    258 		case copy: copystring(); break;
    259 		case expand: xpandstring(delta); break;
    260 		case edit: editstring((struct hshentry *)0); break;
    261 		case edit_expand: editstring(delta); break;
    262 	}
    263 }
    264 
    265 	struct cbuf
    266 cleanlogmsg(m, s)
    267 	char *m;
    268 	size_t s;
    269 {
    270 	register char *t = m;
    271 	register char const *f = t;
    272 	struct cbuf r;
    273 	while (s) {
    274 	    --s;
    275 	    if ((*t++ = *f++) == '\n')
    276 		while (m < --t)
    277 		    if (t[-1]!=' ' && t[-1]!='\t') {
    278 			*t++ = '\n';
    279 			break;
    280 		    }
    281 	}
    282 	while (m < t  &&  (t[-1]==' ' || t[-1]=='\t' || t[-1]=='\n'))
    283 	    --t;
    284 	r.string = m;
    285 	r.size = t - m;
    286 	return r;
    287 }
    288 
    289 
    290 int ttystdin()
    291 {
    292 	static int initialized;
    293 	if (!initialized) {
    294 		if (!interactiveflag)
    295 			interactiveflag = isatty(STDIN_FILENO);
    296 		initialized = true;
    297 	}
    298 	return interactiveflag;
    299 }
    300 
    301 	int
    302 getcstdin()
    303 {
    304 	register FILE *in;
    305 	register int c;
    306 
    307 	in = stdin;
    308 	if (feof(in) && ttystdin())
    309 		clearerr(in);
    310 	c = getc(in);
    311 	if (c == EOF) {
    312 		testIerror(in);
    313 		if (feof(in) && ttystdin())
    314 			afputc('\n',stderr);
    315 	}
    316 	return c;
    317 }
    318 
    319 #if has_prototypes
    320 	int
    321 yesorno(int default_answer, char const *question, ...)
    322 #else
    323 		/*VARARGS2*/ int
    324 	yesorno(default_answer, question, va_alist)
    325 		int default_answer; char const *question; va_dcl
    326 #endif
    327 {
    328 	va_list args;
    329 	register int c, r;
    330 	if (!quietflag && ttystdin()) {
    331 		oflush();
    332 		vararg_start(args, question);
    333 		fvfprintf(stderr, question, args);
    334 		va_end(args);
    335 		eflush();
    336 		r = c = getcstdin();
    337 		while (c!='\n' && !feof(stdin))
    338 			c = getcstdin();
    339 		if (r=='y' || r=='Y')
    340 			return true;
    341 		if (r=='n' || r=='N')
    342 			return false;
    343 	}
    344 	return default_answer;
    345 }
    346 
    347 
    348 	void
    349 putdesc(textflag, textfile)
    350 	int textflag;
    351 	char *textfile;
    352 /* Function: puts the descriptive text into file frewrite.
    353  * if finptr && !textflag, the text is copied from the old description.
    354  * Otherwise, if textfile, the text is read from that
    355  * file, or from stdin, if !textfile.
    356  * A textfile with a leading '-' is treated as a string, not a pathname.
    357  * If finptr, the old descriptive text is discarded.
    358  * Always clears foutptr.
    359  */
    360 {
    361 	static struct buf desc;
    362 	static struct cbuf desclean;
    363 
    364 	register FILE *txt;
    365 	register int c;
    366 	register FILE * frew;
    367 	register char *p;
    368 	register size_t s;
    369 	char const *plim;
    370 
    371 	frew = frewrite;
    372 	if (finptr && !textflag) {
    373                 /* copy old description */
    374 		aprintf(frew, "\n\n%s%c", Kdesc, nextc);
    375 		foutptr = frewrite;
    376 		getdesc(false);
    377 		foutptr = 0;
    378         } else {
    379 		foutptr = 0;
    380                 /* get new description */
    381 		if (finptr) {
    382                         /*skip old description*/
    383 			getdesc(false);
    384                 }
    385 		aprintf(frew,"\n\n%s\n%c",Kdesc,SDELIM);
    386 		if (!textfile)
    387 			desclean = getsstdin(
    388 				"t-", "description",
    389 				"NOTE: This is NOT the log message!\n", &desc
    390 			);
    391 		else if (!desclean.string) {
    392 			if (*textfile == '-') {
    393 				p = textfile + 1;
    394 				s = strlen(p);
    395 			} else {
    396 				if (!(txt = fopenSafer(textfile, "r")))
    397 					efaterror(textfile);
    398 				bufalloc(&desc, 1);
    399 				p = desc.string;
    400 				plim = p + desc.size;
    401 				for (;;) {
    402 					if ((c=getc(txt)) == EOF) {
    403 						testIerror(txt);
    404 						if (feof(txt))
    405 							break;
    406 					}
    407 					if (plim <= p)
    408 					    p = bufenlarge(&desc, &plim);
    409 					*p++ = c;
    410 				}
    411 				if (fclose(txt) != 0)
    412 					Ierror();
    413 				s = p - desc.string;
    414 				p = desc.string;
    415 			}
    416 			desclean = cleanlogmsg(p, s);
    417 		}
    418 		putstring(frew, false, desclean, true);
    419 		aputc_('\n', frew)
    420         }
    421 }
    422 
    423 	struct cbuf
    424 getsstdin(option, name, note, buf)
    425 	char const *option, *name, *note;
    426 	struct buf *buf;
    427 {
    428 	register int c;
    429 	register char *p;
    430 	register size_t i;
    431 	register int tty = ttystdin();
    432 
    433 	if (tty) {
    434 	    aprintf(stderr,
    435 		"enter %s, terminated with single '.' or end of file:\n%s>> ",
    436 		name, note
    437 	    );
    438 	    eflush();
    439 	} else if (feof(stdin))
    440 	    rcsfaterror("can't reread redirected stdin for %s; use -%s<%s>",
    441 		name, option, name
    442 	    );
    443 
    444 	for (
    445 	   i = 0,  p = 0;
    446 	   c = getcstdin(),  !feof(stdin);
    447 	   bufrealloc(buf, i+1),  p = buf->string,  p[i++] = c
    448 	)
    449 		if (c == '\n') {
    450 			if (i  &&  p[i-1]=='.'  &&  (i==1 || p[i-2]=='\n')) {
    451 				/* Remove trailing '.'.  */
    452 				--i;
    453 				break;
    454 			} else if (tty) {
    455 				aputs(">> ", stderr);
    456 				eflush();
    457 			}
    458 		}
    459 	return cleanlogmsg(p, i);
    460 }
    461 
    462 
    463 	void
    464 putadmin()
    465 /* Output the admin node.  */
    466 {
    467 	register FILE *fout;
    468 	struct assoc const *curassoc;
    469 	struct rcslock const *curlock;
    470 	struct access const *curaccess;
    471 
    472 	if (!(fout = frewrite)) {
    473 #		if bad_creat0
    474 			ORCSclose();
    475 			fout = fopenSafer(makedirtemp(0), FOPEN_WB);
    476 #		else
    477 			int fo = fdlock;
    478 			fdlock = -1;
    479 			fout = fdopen(fo, FOPEN_WB);
    480 #		endif
    481 
    482 		if (!(frewrite = fout))
    483 			efaterror(RCSname);
    484 	}
    485 
    486 	/*
    487 	* Output the first character with putc, not printf.
    488 	* Otherwise, an SVR4 stdio bug buffers output inefficiently.
    489 	*/
    490 	aputc_(*Khead, fout)
    491 	aprintf(fout, "%s\t%s;\n", Khead + 1, Head?Head->num:"");
    492 	if (Dbranch && VERSION(4)<=RCSversion)
    493 		aprintf(fout, "%s\t%s;\n", Kbranch, Dbranch);
    494 
    495 	aputs(Kaccess, fout);
    496 	curaccess = AccessList;
    497 	while (curaccess) {
    498 	       aprintf(fout, "\n\t%s", curaccess->login);
    499 	       curaccess = curaccess->nextaccess;
    500 	}
    501 	aprintf(fout, ";\n%s", Ksymbols);
    502 	curassoc = Symbols;
    503 	while (curassoc) {
    504 	       aprintf(fout, "\n\t%s:%s", curassoc->symbol, curassoc->num);
    505 	       curassoc = curassoc->nextassoc;
    506 	}
    507 	aprintf(fout, ";\n%s", Klocks);
    508 	curlock = Locks;
    509 	while (curlock) {
    510 	       aprintf(fout, "\n\t%s:%s", curlock->login, curlock->delta->num);
    511 	       curlock = curlock->nextlock;
    512 	}
    513 	if (StrictLocks) aprintf(fout, "; %s", Kstrict);
    514 	aprintf(fout, ";\n");
    515 	if (Comment.size) {
    516 		aprintf(fout, "%s\t", Kcomment);
    517 		putstring(fout, true, Comment, false);
    518 		aprintf(fout, ";\n");
    519 	}
    520 	if (Expand != KEYVAL_EXPAND)
    521 		aprintf(fout, "%s\t%c%s%c;\n",
    522 			Kexpand, SDELIM, expand_names[Expand], SDELIM
    523 		);
    524 	awrite(Ignored.string, Ignored.size, fout);
    525 	aputc_('\n', fout)
    526 }
    527 
    528 
    529 	static void
    530 putdelta(node, fout)
    531 	register struct hshentry const *node;
    532 	register FILE * fout;
    533 /* Output the delta NODE to FOUT.  */
    534 {
    535 	struct branchhead const *nextbranch;
    536 
    537 	if (!node) return;
    538 
    539 	aprintf(fout, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches",
    540 		node->num,
    541 		Kdate, node->date,
    542 		Kauthor, node->author,
    543 		Kstate, node->state?node->state:""
    544 	);
    545 	nextbranch = node->branches;
    546 	while (nextbranch) {
    547 	       aprintf(fout, "\n\t%s", nextbranch->hsh->num);
    548 	       nextbranch = nextbranch->nextbranch;
    549 	}
    550 
    551 	aprintf(fout, ";\n%s\t%s;\n", Knext, node->next?node->next->num:"");
    552 	awrite(node->ig.string, node->ig.size, fout);
    553 }
    554 
    555 
    556 	void
    557 puttree(root, fout)
    558 	struct hshentry const *root;
    559 	register FILE *fout;
    560 /* Output the delta tree with base ROOT in preorder to FOUT.  */
    561 {
    562 	struct branchhead const *nextbranch;
    563 
    564 	if (!root) return;
    565 
    566 	if (root->selector)
    567 		putdelta(root, fout);
    568 
    569 	puttree(root->next, fout);
    570 
    571 	nextbranch = root->branches;
    572 	while (nextbranch) {
    573 	     puttree(nextbranch->hsh, fout);
    574 	     nextbranch = nextbranch->nextbranch;
    575 	}
    576 }
    577 
    578 
    579 	int
    580 putdtext(delta, srcname, fout, diffmt)
    581 	struct hshentry const *delta;
    582 	char const *srcname;
    583 	FILE *fout;
    584 	int diffmt;
    585 /*
    586  * Output a deltatext node with delta number DELTA->num, log message DELTA->log,
    587  * ignored phrases DELTA->igtext and text SRCNAME to FOUT.
    588  * Double up all SDELIMs in both the log and the text.
    589  * Make sure the log message ends in \n.
    590  * Return false on error.
    591  * If DIFFMT, also check that the text is valid diff -n output.
    592  */
    593 {
    594 	RILE *fin;
    595 	if (!(fin = Iopen(srcname, "r", (struct stat*)0))) {
    596 		eerror(srcname);
    597 		return false;
    598 	}
    599 	putdftext(delta, fin, fout, diffmt);
    600 	Ifclose(fin);
    601 	return true;
    602 }
    603 
    604 	void
    605 putstring(out, delim, s, log)
    606 	register FILE *out;
    607 	struct cbuf s;
    608 	int delim, log;
    609 /*
    610  * Output to OUT one SDELIM if DELIM, then the string S with SDELIMs doubled.
    611  * If LOG is set then S is a log string; append a newline if S is nonempty.
    612  */
    613 {
    614 	register char const *sp;
    615 	register size_t ss;
    616 
    617 	if (delim)
    618 		aputc_(SDELIM, out)
    619 	sp = s.string;
    620 	for (ss = s.size;  ss;  --ss) {
    621 		if (*sp == SDELIM)
    622 			aputc_(SDELIM, out)
    623 		aputc_(*sp++, out)
    624 	}
    625 	if (s.size && log)
    626 		aputc_('\n', out)
    627 	aputc_(SDELIM, out)
    628 }
    629 
    630 	void
    631 putdftext(delta, finfile, foutfile, diffmt)
    632 	struct hshentry const *delta;
    633 	RILE *finfile;
    634 	FILE *foutfile;
    635 	int diffmt;
    636 /* like putdtext(), except the source file is already open */
    637 {
    638 	declarecache;
    639 	register FILE *fout;
    640 	register int c;
    641 	register RILE *fin;
    642 	int ed;
    643 	struct diffcmd dc;
    644 
    645 	fout = foutfile;
    646 	aprintf(fout, DELNUMFORM, delta->num, Klog);
    647 
    648 	/* put log */
    649 	putstring(fout, true, delta->log, true);
    650 	aputc_('\n', fout)
    651 
    652 	/* put ignored phrases */
    653 	awrite(delta->igtext.string, delta->igtext.size, fout);
    654 
    655 	/* put text */
    656 	aprintf(fout, "%s\n%c", Ktext, SDELIM);
    657 
    658 	fin = finfile;
    659 	setupcache(fin);
    660 	if (!diffmt) {
    661 	    /* Copy the file */
    662 	    cache(fin);
    663 	    for (;;) {
    664 		cachegeteof_(c, break;)
    665 		if (c==SDELIM) aputc_(SDELIM, fout) /*double up SDELIM*/
    666 		aputc_(c, fout)
    667 	    }
    668 	} else {
    669 	    initdiffcmd(&dc);
    670 	    while (0  <=  (ed = getdiffcmd(fin, false, fout, &dc)))
    671 		if (ed) {
    672 		    cache(fin);
    673 		    while (dc.nlines--)
    674 			do {
    675 			    cachegeteof_(c, { if (!dc.nlines) goto OK_EOF; unexpected_EOF(); })
    676 			    if (c == SDELIM)
    677 				aputc_(SDELIM, fout)
    678 			    aputc_(c, fout)
    679 			} while (c != '\n');
    680 		    uncache(fin);
    681 		}
    682 	}
    683     OK_EOF:
    684 	aprintf(fout, "%c\n", SDELIM);
    685 }
    686