co.c revision 8858
1/* Copyright (C) 1982, 1988, 1989 Walter Tichy
2   Copyright 1990, 1991 by Paul Eggert
3   Distributed under license by the Free Software Foundation, Inc.
4
5This file is part of RCS.
6
7RCS is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2, or (at your option)
10any later version.
11
12RCS is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with RCS; see the file COPYING.  If not, write to
19the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
20
21Report problems and direct all questions to:
22
23    rcs-bugs@cs.purdue.edu
24
25*/
26
27/*
28 *                     RCS checkout operation
29 */
30/*****************************************************************************
31 *                       check out revisions from RCS files
32 *****************************************************************************
33 */
34
35
36/* $Log: co.c,v $
37 * Revision 1.2  1994/05/14  07:00:10  rgrimes
38 * Add new option -K from David Dawes that allows you to turn on and off
39 * specific keyword substitution during a rcs co command.
40 * Add the new keyword FreeBSD that is IDENTICAL in operation to $Id$.
41 *
42 * Revision 1.1.1.1  1993/06/18  04:22:11  jkh
43 * Updated GNU utilities
44 *
45 * Revision 5.9  1991/10/07  17:32:46  eggert
46 * ci -u src/RCS/co.c,v src/co.c <<\.
47 * -k affects just working file, not RCS file.
48 *
49 * Revision 5.8  1991/08/19  03:13:55  eggert
50 * Warn before removing somebody else's file.
51 * Add -M.  Fix co -j bugs.  Tune.
52 *
53 * Revision 5.7  1991/04/21  11:58:15  eggert
54 * Ensure that working file is newer than RCS file after co -[lu].
55 * Add -x, RCSINIT, MS-DOS support.
56 *
57 * Revision 5.6  1990/12/04  05:18:38  eggert
58 * Don't checkaccesslist() unless necessary.
59 * Use -I for prompts and -q for diagnostics.
60 *
61 * Revision 5.5  1990/11/01  05:03:26  eggert
62 * Fix -j.  Add -I.
63 *
64 * Revision 5.4  1990/10/04  06:30:11  eggert
65 * Accumulate exit status across files.
66 *
67 * Revision 5.3  1990/09/11  02:41:09  eggert
68 * co -kv yields a readonly working file.
69 *
70 * Revision 5.2  1990/09/04  08:02:13  eggert
71 * Standardize yes-or-no procedure.
72 *
73 * Revision 5.0  1990/08/22  08:10:02  eggert
74 * Permit multiple locks by same user.  Add setuid support.
75 * Remove compile-time limits; use malloc instead.
76 * Permit dates past 1999/12/31.  Switch to GMT.
77 * Make lock and temp files faster and safer.
78 * Ansify and Posixate.  Add -k, -V.  Remove snooping.  Tune.
79 *
80 * Revision 4.7  89/05/01  15:11:41  narten
81 * changed copyright header to reflect current distribution rules
82 *
83 * Revision 4.6  88/08/09  19:12:15  eggert
84 * Fix "co -d" core dump; rawdate wasn't always initialized.
85 * Use execv(), not system(); fix putchar('\0') and diagnose() botches; remove lint
86 *
87 * Revision 4.5  87/12/18  11:35:40  narten
88 * lint cleanups (from Guy Harris)
89 *
90 * Revision 4.4  87/10/18  10:20:53  narten
91 * Updating version numbers changes relative to 1.1, are actually
92 * relative to 4.2
93 *
94 * Revision 1.3  87/09/24  13:58:30  narten
95 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
96 * warnings)
97 *
98 * Revision 1.2  87/03/27  14:21:38  jenkins
99 * Port to suns
100 *
101 * Revision 4.2  83/12/05  13:39:48  wft
102 * made rewriteflag external.
103 *
104 * Revision 4.1  83/05/10  16:52:55  wft
105 * Added option -u and -f.
106 * Added handling of default branch.
107 * Replaced getpwuid() with getcaller().
108 * Removed calls to stat(); now done by pairfilenames().
109 * Changed and renamed rmoldfile() to rmworkfile().
110 * Replaced catchints() calls with restoreints(), unlink()--link() with rename();
111 *
112 * Revision 3.7  83/02/15  15:27:07  wft
113 * Added call to fastcopy() to copy remainder of RCS file.
114 *
115 * Revision 3.6  83/01/15  14:37:50  wft
116 * Added ignoring of interrupts while RCS file is renamed; this avoids
117 * deletion of RCS files during the unlink/link window.
118 *
119 * Revision 3.5  82/12/08  21:40:11  wft
120 * changed processing of -d to use DATEFORM; removed actual from
121 * call to preparejoin; re-fixed printing of done at the end.
122 *
123 * Revision 3.4  82/12/04  18:40:00  wft
124 * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE.
125 * Fixed printing of "done".
126 *
127 * Revision 3.3  82/11/28  22:23:11  wft
128 * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
129 * %02d with %.2d, mode generation for working file with WORKMODE.
130 * Fixed nil printing. Fixed -j combined with -l and -p, and exit
131 * for non-existing revisions in preparejoin().
132 *
133 * Revision 3.2  82/10/18  20:47:21  wft
134 * Mode of working file is now maintained even for co -l, but write permission
135 * is removed.
136 * The working file inherits its mode from the RCS file, plus write permission
137 * for the owner. The write permission is not given if locking is strict and
138 * co does not lock.
139 * An existing working file without write permission is deleted automatically.
140 * Otherwise, co asks (empty answer: abort co).
141 * Call to getfullRCSname() added, check for write error added, call
142 * for getlogin() fixed.
143 *
144 * Revision 3.1  82/10/13  16:01:30  wft
145 * fixed type of variables receiving from getc() (char -> int).
146 * removed unused variables.
147 */
148
149
150
151
152#include "rcsbase.h"
153
154static char const *getancestor P((char const*,char const*));
155static int buildjoin P((char const*));
156static int preparejoin P((void));
157static int rmlock P((struct hshentry const*));
158static int rmworkfile P((void));
159static void cleanup P((void));
160
161static char const quietarg[] = "-q";
162
163static char const *expandarg, *join, *suffixarg, *versionarg, *incexcarg;
164static char const *joinlist[joinlength]; /* revisions to be joined */
165static FILE *neworkptr;
166static int exitstatus;
167static int forceflag;
168static int lastjoin;			/* index of last element in joinlist  */
169static int lockflag; /* -1 -> unlock, 0 -> do nothing, 1 -> lock */
170static int mtimeflag;
171static struct hshentries *gendeltas;	/* deltas to be generated	*/
172static struct hshentry *targetdelta;	/* final delta to be generated	*/
173static struct stat workstat;
174
175mainProg(coId, "co", "$Id: co.c,v 1.2 1994/05/14 07:00:10 rgrimes Exp $")
176{
177	static char const cmdusage[] =
178		"\nco usage: co -{flpqru}[rev] -ddate -jjoinlist -sstate -w[login] -Vn file ...";
179
180	char *a, **newargv;
181	char const *author, *date, *rev, *state;
182	char const *joinfilename, *newdate, *neworkfilename;
183	int changelock;  /* 1 if a lock has been changed, -1 if error */
184	int expmode, r, tostdout, workstatstat;
185	struct buf numericrev;	/* expanded revision number	*/
186	char finaldate[datesize];
187
188	setrid();
189	author = date = rev = state = nil;
190	bufautobegin(&numericrev);
191	expmode = -1;
192	suffixes = X_DEFAULT;
193	tostdout = false;
194
195	argc = getRCSINIT(argc, argv, &newargv);
196	argv = newargv;
197	while (a = *++argv,  0<--argc && *a++=='-') {
198		switch (*a++) {
199
200                case 'r':
201		revno:
202			if (*a) {
203				if (rev) warn("redefinition of revision number");
204				rev = a;
205                        }
206                        break;
207
208		case 'f':
209			forceflag=true;
210			goto revno;
211
212                case 'l':
213			if (lockflag < 0) {
214                                warn("-l overrides -u.");
215                        }
216			lockflag = 1;
217                        goto revno;
218
219                case 'u':
220			if (0 < lockflag) {
221                                warn("-l overrides -u.");
222                        }
223			lockflag = -1;
224                        goto revno;
225
226                case 'p':
227			tostdout = true;
228                        goto revno;
229
230		case 'I':
231			interactiveflag = true;
232			goto revno;
233
234                case 'q':
235                        quietflag=true;
236                        goto revno;
237
238                case 'd':
239			if (date)
240				redefined('d');
241			str2date(a, finaldate);
242                        date=finaldate;
243                        break;
244
245                case 'j':
246			if (*a) {
247				if (join) redefined('j');
248				join = a;
249                        }
250                        break;
251
252		case 'M':
253			mtimeflag = true;
254			goto revno;
255
256                case 's':
257			if (*a) {
258				if (state) redefined('s');
259				state = a;
260                        }
261                        break;
262
263                case 'w':
264			if (author) redefined('w');
265			if (*a)
266				author = a;
267			else
268				author = getcaller();
269                        break;
270
271		case 'x':
272			suffixarg = *argv;
273			suffixes = a;
274			break;
275
276		case 'V':
277			versionarg = *argv;
278			setRCSversion(versionarg);
279			break;
280
281		case 'K':    /*  set keyword inclusions/exclusions  */
282			incexcarg = *argv;
283			setIncExc(incexcarg);
284			break;
285		case 'k':    /*  set keyword expand mode  */
286			expandarg = *argv;
287			if (0 <= expmode) redefined('k');
288			if (0 <= (expmode = str2expmode(a)))
289			    break;
290			/* fall into */
291                default:
292			faterror("unknown option: %s%s", *argv, cmdusage);
293
294                };
295        } /* end of option processing */
296
297	if (argc<1) faterror("no input file%s", cmdusage);
298	if (tostdout)
299#	    if text_equals_binary_stdio || text_work_stdio
300		workstdout = stdout;
301#	    else
302		if (!(workstdout = fdopen(STDOUT_FILENO, FOPEN_W_WORK)))
303		    efaterror("stdout");
304#	    endif
305
306        /* now handle all filenames */
307        do {
308	ffree();
309
310	if (pairfilenames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, false)  <=  0)
311		continue;
312
313        /* now RCSfilename contains the name of the RCS file, and finptr
314	 * points at it.  workfilename contains the name of the working file.
315	 * Also, RCSstat has been set.
316         */
317	diagnose("%s  -->  %s\n", RCSfilename,tostdout?"stdout":workfilename);
318
319	workstatstat = -1;
320	if (tostdout) {
321		neworkfilename = 0;
322		neworkptr = workstdout;
323	} else {
324		workstatstat = stat(workfilename, &workstat);
325		neworkfilename = makedirtemp(workfilename, 1);
326		if (!(neworkptr = fopen(neworkfilename, FOPEN_W_WORK))) {
327			if (errno == EACCES)
328				error("%s: parent directory isn't writable",
329					workfilename
330				);
331			else
332				eerror(neworkfilename);
333			continue;
334		}
335	}
336
337        gettree();  /* reads in the delta tree */
338
339        if (Head==nil) {
340                /* no revisions; create empty file */
341		diagnose("no revisions present; generating empty revision 0.0\n");
342		Ozclose(&fcopy);
343		if (workstatstat == 0)
344			if (!rmworkfile()) continue;
345		changelock = 0;
346		newdate = 0;
347                /* Can't reserve a delta, so don't call addlock */
348        } else {
349                if (rev!=nil) {
350                        /* expand symbolic revision number */
351			if (!expandsym(rev, &numericrev))
352                                continue;
353		} else
354			switch (lockflag<0 ? findlock(false,&targetdelta) : 0) {
355			    default:
356				continue;
357			    case 0:
358				bufscpy(&numericrev, Dbranch?Dbranch:"");
359				break;
360			    case 1:
361				bufscpy(&numericrev, targetdelta->num);
362				break;
363			}
364                /* get numbers of deltas to be generated */
365		if (!(targetdelta=genrevs(numericrev.string,date,author,state,&gendeltas)))
366                        continue;
367                /* check reservations */
368		changelock =
369			lockflag < 0 ?
370				rmlock(targetdelta)
371			: lockflag == 0 ?
372				0
373			:
374				addlock(targetdelta);
375
376		if (
377			changelock < 0 ||
378			changelock && !checkaccesslist() ||
379			!dorewrite(lockflag, changelock)
380		)
381			continue;
382
383		if (0 <= expmode)
384			Expand = expmode;
385		if (0 < lockflag  &&  Expand == VAL_EXPAND) {
386			error("cannot combine -kv and -l");
387			continue;
388		}
389
390                if (join && !preparejoin()) continue;
391
392		diagnose("revision %s%s\n",targetdelta->num,
393			 0<lockflag ? " (locked)" :
394			 lockflag<0 ? " (unlocked)" : "");
395
396		/* Prepare to remove old working file if necessary.  */
397		if (workstatstat == 0)
398                        if (!rmworkfile()) continue;
399
400                /* skip description */
401                getdesc(false); /* don't echo*/
402
403		locker_expansion = 0 < lockflag;
404		joinfilename = buildrevision(
405			gendeltas, targetdelta,
406			join&&tostdout ? (FILE*)0 : neworkptr,
407			Expand!=OLD_EXPAND
408		);
409#		if !large_memory
410			if (fcopy == neworkptr)
411				fcopy = 0;  /* Don't close it twice.  */
412#		endif
413		if_advise_access(changelock && gendeltas->first!=targetdelta,
414			finptr, MADV_SEQUENTIAL
415		);
416
417		if (!donerewrite(changelock))
418			continue;
419
420		newdate = targetdelta->date;
421		if (join) {
422			newdate = 0;
423			if (!joinfilename) {
424				aflush(neworkptr);
425				joinfilename = neworkfilename;
426			}
427			if (!buildjoin(joinfilename))
428				continue;
429		}
430        }
431	if (!tostdout) {
432	    r = 0;
433	    if (mtimeflag && newdate) {
434		if (!join)
435		    aflush(neworkptr);
436		r = setfiledate(neworkfilename, newdate);
437	    }
438	    if (r == 0) {
439		ignoreints();
440		r = chnamemod(&neworkptr, neworkfilename, workfilename,
441		  WORKMODE(RCSstat.st_mode,
442		    !(Expand==VAL_EXPAND || lockflag<=0&&StrictLocks)
443		  )
444		);
445		keepdirtemp(neworkfilename);
446		restoreints();
447	    }
448	    if (r != 0) {
449		eerror(workfilename);
450		error("see %s", neworkfilename);
451		continue;
452	    }
453	    diagnose("done\n");
454	}
455        } while (cleanup(),
456                 ++argv, --argc >=1);
457
458	tempunlink();
459	Ofclose(workstdout);
460	exitmain(exitstatus);
461
462}       /* end of main (co) */
463
464	static void
465cleanup()
466{
467	if (nerror) exitstatus = EXIT_FAILURE;
468	Izclose(&finptr);
469	Ozclose(&frewrite);
470#	if !large_memory
471		if (fcopy!=workstdout) Ozclose(&fcopy);
472#	endif
473	if (neworkptr!=workstdout) Ozclose(&neworkptr);
474	dirtempunlink();
475}
476
477#if lint
478#	define exiterr coExit
479#endif
480	exiting void
481exiterr()
482{
483	dirtempunlink();
484	tempunlink();
485	_exit(EXIT_FAILURE);
486}
487
488
489/*****************************************************************
490 * The following routines are auxiliary routines
491 *****************************************************************/
492
493	static int
494rmworkfile()
495/* Function: prepares to remove workfilename, if it exists, and if
496 * it is read-only.
497 * Otherwise (file writable):
498 *   if !quietmode asks the user whether to really delete it (default: fail);
499 *   otherwise failure.
500 * Returns true if permission is gotten.
501 */
502{
503	if (workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH) && !forceflag) {
504	    /* File is writable */
505	    if (!yesorno(false, "writable %s exists%s; remove it? [ny](n): ",
506			workfilename,
507			myself(workstat.st_uid) ? "" : ", and you do not own it"
508	    )) {
509		error(!quietflag && ttystdin()
510			? "checkout aborted"
511			: "writable %s exists; checkout aborted", workfilename);
512		return false;
513            }
514        }
515	/* Actual unlink is done later by caller. */
516	return true;
517}
518
519
520	static int
521rmlock(delta)
522	struct hshentry const *delta;
523/* Function: removes the lock held by caller on delta.
524 * Returns -1 if someone else holds the lock,
525 * 0 if there is no lock on delta,
526 * and 1 if a lock was found and removed.
527 */
528{       register struct lock * next, * trail;
529	char const *num;
530        struct lock dummy;
531        int whomatch, nummatch;
532
533        num=delta->num;
534        dummy.nextlock=next=Locks;
535        trail = &dummy;
536        while (next!=nil) {
537		whomatch = strcmp(getcaller(), next->login);
538                nummatch=strcmp(num,next->delta->num);
539                if ((whomatch==0) && (nummatch==0)) break;
540			/*found a lock on delta by caller*/
541                if ((whomatch!=0)&&(nummatch==0)) {
542                    error("revision %s locked by %s; use co -r or rcs -u",num,next->login);
543                    return -1;
544                }
545                trail=next;
546                next=next->nextlock;
547        }
548        if (next!=nil) {
549                /*found one; delete it */
550                trail->nextlock=next->nextlock;
551                Locks=dummy.nextlock;
552                next->delta->lockedby=nil; /* reset locked-by */
553                return 1; /*success*/
554        } else  return 0; /*no lock on delta*/
555}
556
557
558
559
560/*****************************************************************
561 * The rest of the routines are for handling joins
562 *****************************************************************/
563
564
565	static char const *
566addjoin(joinrev)
567	char *joinrev;
568/* Add joinrev's number to joinlist, yielding address of char past joinrev,
569 * or nil if no such revision exists.
570 */
571{
572	register char *j;
573	register struct hshentry const *d;
574	char terminator;
575	struct buf numrev;
576	struct hshentries *joindeltas;
577
578	j = joinrev;
579	for (;;) {
580	    switch (*j++) {
581		default:
582		    continue;
583		case 0:
584		case ' ': case '\t': case '\n':
585		case ':': case ',': case ';':
586		    break;
587	    }
588	    break;
589	}
590	terminator = *--j;
591	*j = 0;
592	bufautobegin(&numrev);
593	d = 0;
594	if (expandsym(joinrev, &numrev))
595	    d = genrevs(numrev.string,(char*)nil,(char*)nil,(char*)nil,&joindeltas);
596	bufautoend(&numrev);
597	*j = terminator;
598	if (d) {
599		joinlist[++lastjoin] = d->num;
600		return j;
601	}
602	return nil;
603}
604
605	static int
606preparejoin()
607/* Function: Parses a join list pointed to by join and places pointers to the
608 * revision numbers into joinlist.
609 */
610{
611	register char const *j;
612
613        j=join;
614        lastjoin= -1;
615        for (;;) {
616                while ((*j==' ')||(*j=='\t')||(*j==',')) j++;
617                if (*j=='\0') break;
618                if (lastjoin>=joinlength-2) {
619                        error("too many joins");
620                        return(false);
621                }
622		if (!(j = addjoin(j))) return false;
623                while ((*j==' ') || (*j=='\t')) j++;
624                if (*j == ':') {
625                        j++;
626                        while((*j==' ') || (*j=='\t')) j++;
627                        if (*j!='\0') {
628				if (!(j = addjoin(j))) return false;
629                        } else {
630                                error("join pair incomplete");
631                                return false;
632                        }
633                } else {
634                        if (lastjoin==0) { /* first pair */
635                                /* common ancestor missing */
636                                joinlist[1]=joinlist[0];
637                                lastjoin=1;
638                                /*derive common ancestor*/
639				if (!(joinlist[0] = getancestor(targetdelta->num,joinlist[1])))
640                                       return false;
641                        } else {
642                                error("join pair incomplete");
643                                return false;
644                        }
645                }
646        }
647        if (lastjoin<1) {
648                error("empty join");
649                return false;
650        } else  return true;
651}
652
653
654
655	static char const *
656getancestor(r1, r2)
657	char const *r1, *r2;
658/* Yield the common ancestor of r1 and r2 if successful, nil otherwise.
659 * Work reliably only if r1 and r2 are not branch numbers.
660 */
661{
662	static struct buf t1, t2;
663
664	unsigned l1, l2, l3;
665	char const *r;
666
667	l1 = countnumflds(r1);
668	l2 = countnumflds(r2);
669	if ((2<l1 || 2<l2)  &&  cmpnum(r1,r2)!=0) {
670	    /* not on main trunk or identical */
671	    l3 = 0;
672	    while (cmpnumfld(r1, r2, l3+1)==0 && cmpnumfld(r1, r2, l3+2)==0)
673		l3 += 2;
674	    /* This will terminate since r1 and r2 are not the same; see above. */
675	    if (l3==0) {
676		/* no common prefix; common ancestor on main trunk */
677		VOID partialno(&t1, r1, l1>2 ? (unsigned)2 : l1);
678		VOID partialno(&t2, r2, l2>2 ? (unsigned)2 : l2);
679		r = cmpnum(t1.string,t2.string)<0 ? t1.string : t2.string;
680		if (cmpnum(r,r1)!=0 && cmpnum(r,r2)!=0)
681			return r;
682	    } else if (cmpnumfld(r1, r2, l3+1)!=0)
683			return partialno(&t1,r1,l3);
684	}
685	error("common ancestor of %s and %s undefined", r1, r2);
686	return nil;
687}
688
689
690
691	static int
692buildjoin(initialfile)
693	char const *initialfile;
694/* Function: merge pairs of elements in joinlist into initialfile
695 * If workstdout is set, copy result to stdout.
696 * All unlinking of initialfile, rev2, and rev3 should be done by tempunlink().
697 */
698{
699	struct buf commarg;
700	struct buf subs;
701	char const *rev2, *rev3;
702        int i;
703	char const *cov[10], *mergev[12];
704	char const **p;
705
706	bufautobegin(&commarg);
707	bufautobegin(&subs);
708	rev2 = maketemp(0);
709	rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */
710
711	cov[0] = nil;
712	/* cov[1] setup below */
713	cov[2] = CO;
714	/* cov[3] setup below */
715	p = &cov[4];
716	if (expandarg) *p++ = expandarg;
717	if (suffixarg) *p++ = suffixarg;
718	if (versionarg) *p++ = versionarg;
719	*p++ = quietarg;
720	*p++ = RCSfilename;
721	*p = nil;
722
723	mergev[0] = nil;
724	mergev[1] = nil;
725	mergev[2] = MERGE;
726	mergev[3] = mergev[5] = "-L";
727	/* rest of mergev setup below */
728
729        i=0;
730        while (i<lastjoin) {
731                /*prepare marker for merge*/
732                if (i==0)
733			bufscpy(&subs, targetdelta->num);
734		else {
735			bufscat(&subs, ",");
736			bufscat(&subs, joinlist[i-2]);
737			bufscat(&subs, ":");
738			bufscat(&subs, joinlist[i-1]);
739		}
740		diagnose("revision %s\n",joinlist[i]);
741		bufscpy(&commarg, "-p");
742		bufscat(&commarg, joinlist[i]);
743		cov[1] = rev2;
744		cov[3] = commarg.string;
745		if (runv(cov))
746			goto badmerge;
747		diagnose("revision %s\n",joinlist[i+1]);
748		bufscpy(&commarg, "-p");
749		bufscat(&commarg, joinlist[i+1]);
750		cov[1] = rev3;
751		cov[3] = commarg.string;
752		if (runv(cov))
753			goto badmerge;
754		diagnose("merging...\n");
755		mergev[4] = subs.string;
756		mergev[6] = joinlist[i+1];
757		p = &mergev[7];
758		if (quietflag) *p++ = quietarg;
759		if (lastjoin<=i+2 && workstdout) *p++ = "-p";
760		*p++ = initialfile;
761		*p++ = rev2;
762		*p++ = rev3;
763		*p = nil;
764		switch (runv(mergev)) {
765		    case DIFF_FAILURE: case DIFF_SUCCESS:
766			break;
767		    default:
768			goto badmerge;
769		}
770                i=i+2;
771        }
772	bufautoend(&commarg);
773	bufautoend(&subs);
774        return true;
775
776    badmerge:
777	nerror++;
778	bufautoend(&commarg);
779	bufautoend(&subs);
780	return false;
781}
782