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