xref: /netbsd-src/external/gpl2/rcs/dist/src/co.c (revision fa28c6faa16e0b00edee7acdcaf4899797043def)
1*fa28c6faSchristos /*	$NetBSD: co.c,v 1.2 2016/01/14 04:22:39 christos Exp $	*/
27bdc2678Schristos 
37bdc2678Schristos /* Check out working files from revisions of RCS files.  */
47bdc2678Schristos 
57bdc2678Schristos /* Copyright 1982, 1988, 1989 Walter Tichy
67bdc2678Schristos    Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
77bdc2678Schristos    Distributed under license by the Free Software Foundation, Inc.
87bdc2678Schristos 
97bdc2678Schristos This file is part of RCS.
107bdc2678Schristos 
117bdc2678Schristos RCS is free software; you can redistribute it and/or modify
127bdc2678Schristos it under the terms of the GNU General Public License as published by
137bdc2678Schristos the Free Software Foundation; either version 2, or (at your option)
147bdc2678Schristos any later version.
157bdc2678Schristos 
167bdc2678Schristos RCS is distributed in the hope that it will be useful,
177bdc2678Schristos but WITHOUT ANY WARRANTY; without even the implied warranty of
187bdc2678Schristos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
197bdc2678Schristos GNU General Public License for more details.
207bdc2678Schristos 
217bdc2678Schristos You should have received a copy of the GNU General Public License
227bdc2678Schristos along with RCS; see the file COPYING.
237bdc2678Schristos If not, write to the Free Software Foundation,
247bdc2678Schristos 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
257bdc2678Schristos 
267bdc2678Schristos Report problems and direct all questions to:
277bdc2678Schristos 
287bdc2678Schristos     rcs-bugs@cs.purdue.edu
297bdc2678Schristos 
307bdc2678Schristos */
317bdc2678Schristos 
327bdc2678Schristos /*
337bdc2678Schristos  * Log: co.c,v
347bdc2678Schristos  * Revision 5.18  1995/06/16 06:19:24  eggert
357bdc2678Schristos  * Update FSF address.
367bdc2678Schristos  *
377bdc2678Schristos  * Revision 5.17  1995/06/01 16:23:43  eggert
387bdc2678Schristos  * (main, preparejoin): Pass argument instead of using `join' static variable.
397bdc2678Schristos  * (main): Add -kb.
407bdc2678Schristos  *
417bdc2678Schristos  * Revision 5.16  1994/03/17 14:05:48  eggert
427bdc2678Schristos  * Move buffer-flushes out of critical sections, since they aren't critical.
437bdc2678Schristos  * Use ORCSerror to clean up after a fatal error.  Remove lint.
447bdc2678Schristos  * Specify subprocess input via file descriptor, not file name.
457bdc2678Schristos  *
467bdc2678Schristos  * Revision 5.15  1993/11/09 17:40:15  eggert
477bdc2678Schristos  * -V now prints version on stdout and exits.  Don't print usage twice.
487bdc2678Schristos  *
497bdc2678Schristos  * Revision 5.14  1993/11/03 17:42:27  eggert
507bdc2678Schristos  * Add -z.  Generate a value for the Name keyword.
517bdc2678Schristos  * Don't arbitrarily limit the number of joins.
527bdc2678Schristos  * Improve quality of diagnostics.
537bdc2678Schristos  *
547bdc2678Schristos  * Revision 5.13  1992/07/28  16:12:44  eggert
557bdc2678Schristos  * Add -V.  Check that working and RCS files are distinct.
567bdc2678Schristos  *
577bdc2678Schristos  * Revision 5.12  1992/02/17  23:02:08  eggert
587bdc2678Schristos  * Add -T.
597bdc2678Schristos  *
607bdc2678Schristos  * Revision 5.11  1992/01/24  18:44:19  eggert
617bdc2678Schristos  * Add support for bad_creat0.  lint -> RCS_lint
627bdc2678Schristos  *
637bdc2678Schristos  * Revision 5.10  1992/01/06  02:42:34  eggert
647bdc2678Schristos  * Update usage string.
657bdc2678Schristos  *
667bdc2678Schristos  * Revision 5.9  1991/10/07  17:32:46  eggert
677bdc2678Schristos  * -k affects just working file, not RCS file.
687bdc2678Schristos  *
697bdc2678Schristos  * Revision 5.8  1991/08/19  03:13:55  eggert
707bdc2678Schristos  * Warn before removing somebody else's file.
717bdc2678Schristos  * Add -M.  Fix co -j bugs.  Tune.
727bdc2678Schristos  *
737bdc2678Schristos  * Revision 5.7  1991/04/21  11:58:15  eggert
747bdc2678Schristos  * Ensure that working file is newer than RCS file after co -[lu].
757bdc2678Schristos  * Add -x, RCSINIT, MS-DOS support.
767bdc2678Schristos  *
777bdc2678Schristos  * Revision 5.6  1990/12/04  05:18:38  eggert
787bdc2678Schristos  * Don't checkaccesslist() unless necessary.
797bdc2678Schristos  * Use -I for prompts and -q for diagnostics.
807bdc2678Schristos  *
817bdc2678Schristos  * Revision 5.5  1990/11/01  05:03:26  eggert
827bdc2678Schristos  * Fix -j.  Add -I.
837bdc2678Schristos  *
847bdc2678Schristos  * Revision 5.4  1990/10/04  06:30:11  eggert
857bdc2678Schristos  * Accumulate exit status across files.
867bdc2678Schristos  *
877bdc2678Schristos  * Revision 5.3  1990/09/11  02:41:09  eggert
887bdc2678Schristos  * co -kv yields a readonly working file.
897bdc2678Schristos  *
907bdc2678Schristos  * Revision 5.2  1990/09/04  08:02:13  eggert
917bdc2678Schristos  * Standardize yes-or-no procedure.
927bdc2678Schristos  *
937bdc2678Schristos  * Revision 5.0  1990/08/22  08:10:02  eggert
947bdc2678Schristos  * Permit multiple locks by same user.  Add setuid support.
957bdc2678Schristos  * Remove compile-time limits; use malloc instead.
967bdc2678Schristos  * Permit dates past 1999/12/31.  Switch to GMT.
977bdc2678Schristos  * Make lock and temp files faster and safer.
987bdc2678Schristos  * Ansify and Posixate.  Add -k, -V.  Remove snooping.  Tune.
997bdc2678Schristos  *
1007bdc2678Schristos  * Revision 4.7  89/05/01  15:11:41  narten
1017bdc2678Schristos  * changed copyright header to reflect current distribution rules
1027bdc2678Schristos  *
1037bdc2678Schristos  * Revision 4.6  88/08/09  19:12:15  eggert
1047bdc2678Schristos  * Fix "co -d" core dump; rawdate wasn't always initialized.
1057bdc2678Schristos  * Use execv(), not system(); fix putchar('\0') and diagnose() botches; remove lint
1067bdc2678Schristos  *
1077bdc2678Schristos  * Revision 4.5  87/12/18  11:35:40  narten
1087bdc2678Schristos  * lint cleanups (from Guy Harris)
1097bdc2678Schristos  *
1107bdc2678Schristos  * Revision 4.4  87/10/18  10:20:53  narten
1117bdc2678Schristos  * Updating version numbers changes relative to 1.1, are actually
1127bdc2678Schristos  * relative to 4.2
1137bdc2678Schristos  *
1147bdc2678Schristos  * Revision 1.3  87/09/24  13:58:30  narten
1157bdc2678Schristos  * Sources now pass through lint (if you ignore printf/sprintf/fprintf
1167bdc2678Schristos  * warnings)
1177bdc2678Schristos  *
1187bdc2678Schristos  * Revision 1.2  87/03/27  14:21:38  jenkins
1197bdc2678Schristos  * Port to suns
1207bdc2678Schristos  *
1217bdc2678Schristos  * Revision 4.2  83/12/05  13:39:48  wft
1227bdc2678Schristos  * made rewriteflag external.
1237bdc2678Schristos  *
1247bdc2678Schristos  * Revision 4.1  83/05/10  16:52:55  wft
1257bdc2678Schristos  * Added option -u and -f.
1267bdc2678Schristos  * Added handling of default branch.
1277bdc2678Schristos  * Replaced getpwuid() with getcaller().
1287bdc2678Schristos  * Removed calls to stat(); now done by pairfilenames().
1297bdc2678Schristos  * Changed and renamed rmoldfile() to rmworkfile().
1307bdc2678Schristos  * Replaced catchints() calls with restoreints(), unlink()--link() with rename();
1317bdc2678Schristos  *
1327bdc2678Schristos  * Revision 3.7  83/02/15  15:27:07  wft
1337bdc2678Schristos  * Added call to fastcopy() to copy remainder of RCS file.
1347bdc2678Schristos  *
1357bdc2678Schristos  * Revision 3.6  83/01/15  14:37:50  wft
1367bdc2678Schristos  * Added ignoring of interrupts while RCS file is renamed; this avoids
1377bdc2678Schristos  * deletion of RCS files during the unlink/link window.
1387bdc2678Schristos  *
1397bdc2678Schristos  * Revision 3.5  82/12/08  21:40:11  wft
1407bdc2678Schristos  * changed processing of -d to use DATEFORM; removed actual from
1417bdc2678Schristos  * call to preparejoin; re-fixed printing of done at the end.
1427bdc2678Schristos  *
1437bdc2678Schristos  * Revision 3.4  82/12/04  18:40:00  wft
1447bdc2678Schristos  * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE.
1457bdc2678Schristos  * Fixed printing of "done".
1467bdc2678Schristos  *
1477bdc2678Schristos  * Revision 3.3  82/11/28  22:23:11  wft
1487bdc2678Schristos  * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
1497bdc2678Schristos  * %02d with %.2d, mode generation for working file with WORKMODE.
1507bdc2678Schristos  * Fixed nil printing. Fixed -j combined with -l and -p, and exit
1517bdc2678Schristos  * for non-existing revisions in preparejoin().
1527bdc2678Schristos  *
1537bdc2678Schristos  * Revision 3.2  82/10/18  20:47:21  wft
1547bdc2678Schristos  * Mode of working file is now maintained even for co -l, but write permission
1557bdc2678Schristos  * is removed.
1567bdc2678Schristos  * The working file inherits its mode from the RCS file, plus write permission
1577bdc2678Schristos  * for the owner. The write permission is not given if locking is strict and
1587bdc2678Schristos  * co does not lock.
1597bdc2678Schristos  * An existing working file without write permission is deleted automatically.
1607bdc2678Schristos  * Otherwise, co asks (empty answer: abort co).
1617bdc2678Schristos  * Call to getfullRCSname() added, check for write error added, call
1627bdc2678Schristos  * for getlogin() fixed.
1637bdc2678Schristos  *
1647bdc2678Schristos  * Revision 3.1  82/10/13  16:01:30  wft
1657bdc2678Schristos  * fixed type of variables receiving from getc() (char -> int).
1667bdc2678Schristos  * removed unused variables.
1677bdc2678Schristos  */
1687bdc2678Schristos 
1697bdc2678Schristos 
1707bdc2678Schristos 
1717bdc2678Schristos 
1727bdc2678Schristos #include "rcsbase.h"
1737bdc2678Schristos 
1747bdc2678Schristos static char *addjoin P((char*));
1757bdc2678Schristos static char const *getancestor P((char const*,char const*));
1767bdc2678Schristos static int buildjoin P((char const*));
1777bdc2678Schristos static int preparejoin P((char*));
1787bdc2678Schristos static int rmlock P((struct hshentry const*));
1797bdc2678Schristos static int rmworkfile P((void));
1807bdc2678Schristos static void cleanup P((void));
1817bdc2678Schristos 
1827bdc2678Schristos static char const quietarg[] = "-q";
1837bdc2678Schristos 
1847bdc2678Schristos static char const *expandarg, *suffixarg, *versionarg, *zonearg;
1857bdc2678Schristos static char const **joinlist; /* revisions to be joined */
1867bdc2678Schristos static int joinlength;
1877bdc2678Schristos static FILE *neworkptr;
1887bdc2678Schristos static int exitstatus;
1897bdc2678Schristos static int forceflag;
1907bdc2678Schristos static int lastjoin;			/* index of last element in joinlist  */
1917bdc2678Schristos static int lockflag; /* -1 -> unlock, 0 -> do nothing, 1 -> lock */
1927bdc2678Schristos static int mtimeflag;
1937bdc2678Schristos static struct hshentries *gendeltas;	/* deltas to be generated	*/
1947bdc2678Schristos static struct hshentry *targetdelta;	/* final delta to be generated	*/
1957bdc2678Schristos static struct stat workstat;
1967bdc2678Schristos 
1977bdc2678Schristos mainProg(coId, "co", "Id: co.c,v 5.18 1995/06/16 06:19:24 eggert Exp ")
1987bdc2678Schristos {
1997bdc2678Schristos 	static char const cmdusage[] =
2007bdc2678Schristos 		"\nco usage: co -{fIlMpqru}[rev] -ddate -jjoins -ksubst -sstate -T -w[who] -Vn -xsuff -zzone file ...";
2017bdc2678Schristos 
2027bdc2678Schristos 	char *a, *joinflag, **newargv;
2037bdc2678Schristos 	char const *author, *date, *rev, *state;
2047bdc2678Schristos 	char const *joinname, *newdate, *neworkname;
2057bdc2678Schristos 	int changelock;  /* 1 if a lock has been changed, -1 if error */
2067bdc2678Schristos 	int expmode, r, tostdout, workstatstat;
2077bdc2678Schristos 	int Ttimeflag;
2087bdc2678Schristos 	struct buf numericrev;	/* expanded revision number	*/
2097bdc2678Schristos 	char finaldate[datesize];
2107bdc2678Schristos #	if OPEN_O_BINARY
2117bdc2678Schristos 		int stdout_mode = 0;
2127bdc2678Schristos #	endif
2137bdc2678Schristos 
2147bdc2678Schristos 	setrid();
2157bdc2678Schristos 	author = date = rev = state = 0;
2167bdc2678Schristos 	joinflag = 0;
2177bdc2678Schristos 	bufautobegin(&numericrev);
2187bdc2678Schristos 	expmode = -1;
2197bdc2678Schristos 	suffixes = X_DEFAULT;
2207bdc2678Schristos 	tostdout = false;
2217bdc2678Schristos 	Ttimeflag = false;
2227bdc2678Schristos 
2237bdc2678Schristos 	argc = getRCSINIT(argc, argv, &newargv);
2247bdc2678Schristos 	argv = newargv;
2257bdc2678Schristos 	while (a = *++argv,  0<--argc && *a++=='-') {
2267bdc2678Schristos 		switch (*a++) {
2277bdc2678Schristos 
2287bdc2678Schristos                 case 'r':
2297bdc2678Schristos 		revno:
2307bdc2678Schristos 			if (*a) {
2317bdc2678Schristos 				if (rev) warn("redefinition of revision number");
2327bdc2678Schristos 				rev = a;
2337bdc2678Schristos                         }
2347bdc2678Schristos                         break;
2357bdc2678Schristos 
2367bdc2678Schristos 		case 'f':
2377bdc2678Schristos 			forceflag=true;
2387bdc2678Schristos 			goto revno;
2397bdc2678Schristos 
2407bdc2678Schristos                 case 'l':
2417bdc2678Schristos 			if (lockflag < 0) {
2427bdc2678Schristos 				warn("-u overridden by -l.");
2437bdc2678Schristos                         }
2447bdc2678Schristos 			lockflag = 1;
2457bdc2678Schristos                         goto revno;
2467bdc2678Schristos 
2477bdc2678Schristos                 case 'u':
2487bdc2678Schristos 			if (0 < lockflag) {
2497bdc2678Schristos 				warn("-l overridden by -u.");
2507bdc2678Schristos                         }
2517bdc2678Schristos 			lockflag = -1;
2527bdc2678Schristos                         goto revno;
2537bdc2678Schristos 
2547bdc2678Schristos                 case 'p':
2557bdc2678Schristos 			tostdout = true;
2567bdc2678Schristos                         goto revno;
2577bdc2678Schristos 
2587bdc2678Schristos 		case 'I':
2597bdc2678Schristos 			interactiveflag = true;
2607bdc2678Schristos 			goto revno;
2617bdc2678Schristos 
2627bdc2678Schristos                 case 'q':
2637bdc2678Schristos                         quietflag=true;
2647bdc2678Schristos                         goto revno;
2657bdc2678Schristos 
2667bdc2678Schristos                 case 'd':
2677bdc2678Schristos 			if (date)
2687bdc2678Schristos 				redefined('d');
2697bdc2678Schristos 			str2date(a, finaldate);
2707bdc2678Schristos                         date=finaldate;
2717bdc2678Schristos                         break;
2727bdc2678Schristos 
2737bdc2678Schristos                 case 'j':
2747bdc2678Schristos 			if (*a) {
2757bdc2678Schristos 				if (joinflag) redefined('j');
2767bdc2678Schristos 				joinflag = a;
2777bdc2678Schristos                         }
2787bdc2678Schristos                         break;
2797bdc2678Schristos 
2807bdc2678Schristos 		case 'M':
2817bdc2678Schristos 			mtimeflag = true;
2827bdc2678Schristos 			goto revno;
2837bdc2678Schristos 
2847bdc2678Schristos                 case 's':
2857bdc2678Schristos 			if (*a) {
2867bdc2678Schristos 				if (state) redefined('s');
2877bdc2678Schristos 				state = a;
2887bdc2678Schristos                         }
2897bdc2678Schristos                         break;
2907bdc2678Schristos 
2917bdc2678Schristos 		case 'T':
2927bdc2678Schristos 			if (*a)
2937bdc2678Schristos 				goto unknown;
2947bdc2678Schristos 			Ttimeflag = true;
2957bdc2678Schristos 			break;
2967bdc2678Schristos 
2977bdc2678Schristos                 case 'w':
2987bdc2678Schristos 			if (author) redefined('w');
2997bdc2678Schristos 			if (*a)
3007bdc2678Schristos 				author = a;
3017bdc2678Schristos 			else
3027bdc2678Schristos 				author = getcaller();
3037bdc2678Schristos                         break;
3047bdc2678Schristos 
3057bdc2678Schristos 		case 'x':
3067bdc2678Schristos 			suffixarg = *argv;
3077bdc2678Schristos 			suffixes = a;
3087bdc2678Schristos 			break;
3097bdc2678Schristos 
3107bdc2678Schristos 		case 'V':
3117bdc2678Schristos 			versionarg = *argv;
3127bdc2678Schristos 			setRCSversion(versionarg);
3137bdc2678Schristos 			break;
3147bdc2678Schristos 
3157bdc2678Schristos 		case 'z':
3167bdc2678Schristos 			zonearg = *argv;
3177bdc2678Schristos 			zone_set(a);
3187bdc2678Schristos 			break;
3197bdc2678Schristos 
3207bdc2678Schristos 		case 'k':    /*  set keyword expand mode  */
3217bdc2678Schristos 			expandarg = *argv;
3227bdc2678Schristos 			if (0 <= expmode) redefined('k');
3237bdc2678Schristos 			if (0 <= (expmode = str2expmode(a)))
3247bdc2678Schristos 			    break;
3257bdc2678Schristos 			/* fall into */
3267bdc2678Schristos                 default:
3277bdc2678Schristos 		unknown:
3287bdc2678Schristos 			error("unknown option: %s%s", *argv, cmdusage);
3297bdc2678Schristos 
3307bdc2678Schristos                 };
3317bdc2678Schristos         } /* end of option processing */
3327bdc2678Schristos 
3337bdc2678Schristos 	/* Now handle all pathnames.  */
3347bdc2678Schristos 	if (nerror) cleanup();
3357bdc2678Schristos 	else if (argc < 1) faterror("no input file%s", cmdusage);
3367bdc2678Schristos 	else for (;  0 < argc;  cleanup(), ++argv, --argc) {
3377bdc2678Schristos 	ffree();
3387bdc2678Schristos 
3397bdc2678Schristos 	if (pairnames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, false)  <=  0)
3407bdc2678Schristos 		continue;
3417bdc2678Schristos 
3427bdc2678Schristos 	/*
3437bdc2678Schristos 	 * RCSname contains the name of the RCS file, and finptr
3447bdc2678Schristos 	 * points at it.  workname contains the name of the working file.
3457bdc2678Schristos 	 * Also, RCSstat has been set.
3467bdc2678Schristos          */
3477bdc2678Schristos 	diagnose("%s  -->  %s\n", RCSname, tostdout?"standard output":workname);
3487bdc2678Schristos 
3497bdc2678Schristos 	workstatstat = -1;
3507bdc2678Schristos 	if (tostdout) {
3517bdc2678Schristos #		if OPEN_O_BINARY
3527bdc2678Schristos 		    int newmode = Expand==BINARY_EXPAND ? OPEN_O_BINARY : 0;
3537bdc2678Schristos 		    if (stdout_mode != newmode) {
3547bdc2678Schristos 			stdout_mode = newmode;
3557bdc2678Schristos 			oflush();
3567bdc2678Schristos 			VOID setmode(STDOUT_FILENO, newmode);
3577bdc2678Schristos 		    }
3587bdc2678Schristos #		endif
3597bdc2678Schristos 		neworkname = 0;
3607bdc2678Schristos 		neworkptr = workstdout = stdout;
3617bdc2678Schristos 	} else {
3627bdc2678Schristos 		workstatstat = stat(workname, &workstat);
3637bdc2678Schristos 		if (workstatstat == 0  &&  same_file(RCSstat, workstat, 0)) {
3647bdc2678Schristos 			rcserror("RCS file is the same as working file %s.",
3657bdc2678Schristos 				workname
3667bdc2678Schristos 			);
3677bdc2678Schristos 			continue;
3687bdc2678Schristos 		}
3697bdc2678Schristos 		neworkname = makedirtemp(1);
3707bdc2678Schristos 		if (!(neworkptr = fopenSafer(neworkname, FOPEN_W_WORK))) {
3717bdc2678Schristos 			if (errno == EACCES)
3727bdc2678Schristos 			    workerror("permission denied on parent directory");
3737bdc2678Schristos 			else
3747bdc2678Schristos 			    eerror(neworkname);
3757bdc2678Schristos 			continue;
3767bdc2678Schristos 		}
3777bdc2678Schristos 	}
3787bdc2678Schristos 
3797bdc2678Schristos         gettree();  /* reads in the delta tree */
3807bdc2678Schristos 
3817bdc2678Schristos 	if (!Head) {
3827bdc2678Schristos                 /* no revisions; create empty file */
3837bdc2678Schristos 		diagnose("no revisions present; generating empty revision 0.0\n");
3847bdc2678Schristos 		if (lockflag)
3857bdc2678Schristos 			warn(
3867bdc2678Schristos 				"no revisions, so nothing can be %slocked",
3877bdc2678Schristos 				lockflag < 0 ? "un" : ""
3887bdc2678Schristos 			);
3897bdc2678Schristos 		Ozclose(&fcopy);
3907bdc2678Schristos 		if (workstatstat == 0)
3917bdc2678Schristos 			if (!rmworkfile()) continue;
3927bdc2678Schristos 		changelock = 0;
3937bdc2678Schristos 		newdate = 0;
3947bdc2678Schristos         } else {
3957bdc2678Schristos 		int locks = lockflag ? findlock(false, &targetdelta) : 0;
3967bdc2678Schristos 		if (rev) {
3977bdc2678Schristos                         /* expand symbolic revision number */
3987bdc2678Schristos 			if (!expandsym(rev, &numericrev))
3997bdc2678Schristos                                 continue;
4007bdc2678Schristos 		} else {
4017bdc2678Schristos 			switch (locks) {
4027bdc2678Schristos 			    default:
4037bdc2678Schristos 				continue;
4047bdc2678Schristos 			    case 0:
4057bdc2678Schristos 				bufscpy(&numericrev, Dbranch?Dbranch:"");
4067bdc2678Schristos 				break;
4077bdc2678Schristos 			    case 1:
4087bdc2678Schristos 				bufscpy(&numericrev, targetdelta->num);
4097bdc2678Schristos 				break;
4107bdc2678Schristos 			}
4117bdc2678Schristos 		}
4127bdc2678Schristos                 /* get numbers of deltas to be generated */
4137bdc2678Schristos 		if (!(targetdelta=genrevs(numericrev.string,date,author,state,&gendeltas)))
4147bdc2678Schristos                         continue;
4157bdc2678Schristos                 /* check reservations */
4167bdc2678Schristos 		changelock =
4177bdc2678Schristos 			lockflag < 0 ?
4187bdc2678Schristos 				rmlock(targetdelta)
4197bdc2678Schristos 			: lockflag == 0 ?
4207bdc2678Schristos 				0
4217bdc2678Schristos 			:
4227bdc2678Schristos 				addlock(targetdelta, true);
4237bdc2678Schristos 
4247bdc2678Schristos 		if (
4257bdc2678Schristos 			changelock < 0
4267bdc2678Schristos 			|| (changelock && !checkaccesslist())
4277bdc2678Schristos 			|| dorewrite(lockflag, changelock) != 0
4287bdc2678Schristos 		)
4297bdc2678Schristos 			continue;
4307bdc2678Schristos 
4317bdc2678Schristos 		if (0 <= expmode)
4327bdc2678Schristos 			Expand = expmode;
4337bdc2678Schristos 		if (0 < lockflag  &&  Expand == VAL_EXPAND) {
4347bdc2678Schristos 			rcserror("cannot combine -kv and -l");
4357bdc2678Schristos 			continue;
4367bdc2678Schristos 		}
4377bdc2678Schristos 
4387bdc2678Schristos 		if (joinflag && !preparejoin(joinflag))
4397bdc2678Schristos 			continue;
4407bdc2678Schristos 
4417bdc2678Schristos 		diagnose("revision %s%s\n",targetdelta->num,
4427bdc2678Schristos 			 0<lockflag ? " (locked)" :
4437bdc2678Schristos 			 lockflag<0 ? " (unlocked)" : "");
4447bdc2678Schristos 
4457bdc2678Schristos 		/* Prepare to remove old working file if necessary.  */
4467bdc2678Schristos 		if (workstatstat == 0)
4477bdc2678Schristos                         if (!rmworkfile()) continue;
4487bdc2678Schristos 
4497bdc2678Schristos                 /* skip description */
4507bdc2678Schristos                 getdesc(false); /* don't echo*/
4517bdc2678Schristos 
4527bdc2678Schristos 		locker_expansion = 0 < lockflag;
4537bdc2678Schristos 		targetdelta->name = namedrev(rev, targetdelta);
4547bdc2678Schristos 		joinname = buildrevision(
4557bdc2678Schristos 			gendeltas, targetdelta,
4567bdc2678Schristos 			joinflag&&tostdout ? (FILE*)0 : neworkptr,
4577bdc2678Schristos 			Expand < MIN_UNEXPAND
4587bdc2678Schristos 		);
4597bdc2678Schristos #		if !large_memory
4607bdc2678Schristos 			if (fcopy == neworkptr)
4617bdc2678Schristos 				fcopy = 0;  /* Don't close it twice.  */
4627bdc2678Schristos #		endif
4637bdc2678Schristos 		if_advise_access(changelock && gendeltas->first!=targetdelta,
4647bdc2678Schristos 			finptr, MADV_SEQUENTIAL
4657bdc2678Schristos 		);
4667bdc2678Schristos 
4677bdc2678Schristos 		if (donerewrite(changelock,
4687bdc2678Schristos 			Ttimeflag ? RCSstat.st_mtime : (time_t)-1
4697bdc2678Schristos 		) != 0)
4707bdc2678Schristos 			continue;
4717bdc2678Schristos 
4727bdc2678Schristos 		if (changelock) {
4737bdc2678Schristos 			locks += lockflag;
4747bdc2678Schristos 			if (1 < locks)
4757bdc2678Schristos 				rcswarn("You now have %d locks.", locks);
4767bdc2678Schristos 		}
4777bdc2678Schristos 
4787bdc2678Schristos 		newdate = targetdelta->date;
4797bdc2678Schristos 		if (joinflag) {
4807bdc2678Schristos 			newdate = 0;
4817bdc2678Schristos 			if (!joinname) {
4827bdc2678Schristos 				aflush(neworkptr);
4837bdc2678Schristos 				joinname = neworkname;
4847bdc2678Schristos 			}
4857bdc2678Schristos 			if (Expand == BINARY_EXPAND)
4867bdc2678Schristos 				workerror("merging binary files");
4877bdc2678Schristos 			if (!buildjoin(joinname))
4887bdc2678Schristos 				continue;
4897bdc2678Schristos 		}
4907bdc2678Schristos         }
4917bdc2678Schristos 	if (!tostdout) {
4927bdc2678Schristos 	    mode_t m = WORKMODE(RCSstat.st_mode,
4937bdc2678Schristos 		!  (Expand==VAL_EXPAND  ||  (lockflag<=0 && StrictLocks))
4947bdc2678Schristos 	    );
4957bdc2678Schristos 	    time_t t = mtimeflag&&newdate ? date2time(newdate) : (time_t)-1;
4967bdc2678Schristos 	    aflush(neworkptr);
4977bdc2678Schristos 	    ignoreints();
4987bdc2678Schristos 	    r = chnamemod(&neworkptr, neworkname, workname, 1, m, t);
4997bdc2678Schristos 	    keepdirtemp(neworkname);
5007bdc2678Schristos 	    restoreints();
5017bdc2678Schristos 	    if (r != 0) {
5027bdc2678Schristos 		eerror(workname);
5037bdc2678Schristos 		error("see %s", neworkname);
5047bdc2678Schristos 		continue;
5057bdc2678Schristos 	    }
5067bdc2678Schristos 	    diagnose("done\n");
5077bdc2678Schristos 	}
5087bdc2678Schristos 	}
5097bdc2678Schristos 
5107bdc2678Schristos 	tempunlink();
5117bdc2678Schristos 	Ofclose(workstdout);
5127bdc2678Schristos 	exitmain(exitstatus);
5137bdc2678Schristos 
5147bdc2678Schristos }       /* end of main (co) */
5157bdc2678Schristos 
5167bdc2678Schristos 	static void
cleanup()5177bdc2678Schristos cleanup()
5187bdc2678Schristos {
5197bdc2678Schristos 	if (nerror) exitstatus = EXIT_FAILURE;
5207bdc2678Schristos 	Izclose(&finptr);
5217bdc2678Schristos 	ORCSclose();
5227bdc2678Schristos #	if !large_memory
5237bdc2678Schristos 		if (fcopy!=workstdout) Ozclose(&fcopy);
5247bdc2678Schristos #	endif
5257bdc2678Schristos 	if (neworkptr!=workstdout) Ozclose(&neworkptr);
5267bdc2678Schristos 	dirtempunlink();
5277bdc2678Schristos }
5287bdc2678Schristos 
5297bdc2678Schristos #if RCS_lint
5307bdc2678Schristos #	define exiterr coExit
5317bdc2678Schristos #endif
5327bdc2678Schristos 	void
exiterr()5337bdc2678Schristos exiterr()
5347bdc2678Schristos {
5357bdc2678Schristos 	ORCSerror();
5367bdc2678Schristos 	dirtempunlink();
5377bdc2678Schristos 	tempunlink();
5387bdc2678Schristos 	_exit(EXIT_FAILURE);
5397bdc2678Schristos }
5407bdc2678Schristos 
5417bdc2678Schristos 
5427bdc2678Schristos /*****************************************************************
5437bdc2678Schristos  * The following routines are auxiliary routines
5447bdc2678Schristos  *****************************************************************/
5457bdc2678Schristos 
5467bdc2678Schristos 	static int
rmworkfile()5477bdc2678Schristos rmworkfile()
5487bdc2678Schristos /*
5497bdc2678Schristos  * Prepare to remove workname, if it exists, and if
5507bdc2678Schristos  * it is read-only.
5517bdc2678Schristos  * Otherwise (file writable):
5527bdc2678Schristos  *   if !quietmode asks the user whether to really delete it (default: fail);
5537bdc2678Schristos  *   otherwise failure.
5547bdc2678Schristos  * Returns true if permission is gotten.
5557bdc2678Schristos  */
5567bdc2678Schristos {
5577bdc2678Schristos 	if (workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH) && !forceflag) {
5587bdc2678Schristos 	    /* File is writable */
5597bdc2678Schristos 	    if (!yesorno(false, "writable %s exists%s; remove it? [ny](n): ",
5607bdc2678Schristos 			workname,
5617bdc2678Schristos 			myself(workstat.st_uid) ? "" : ", and you do not own it"
5627bdc2678Schristos 	    )) {
5637bdc2678Schristos 		error(!quietflag && ttystdin()
5647bdc2678Schristos 			? "checkout aborted"
5657bdc2678Schristos 			: "writable %s exists; checkout aborted", workname);
5667bdc2678Schristos 		return false;
5677bdc2678Schristos             }
5687bdc2678Schristos         }
5697bdc2678Schristos 	/* Actual unlink is done later by caller. */
5707bdc2678Schristos 	return true;
5717bdc2678Schristos }
5727bdc2678Schristos 
5737bdc2678Schristos 
5747bdc2678Schristos 	static int
rmlock(delta)5757bdc2678Schristos rmlock(delta)
5767bdc2678Schristos 	struct hshentry const *delta;
5777bdc2678Schristos /* Function: removes the lock held by caller on delta.
5787bdc2678Schristos  * Returns -1 if someone else holds the lock,
5797bdc2678Schristos  * 0 if there is no lock on delta,
5807bdc2678Schristos  * and 1 if a lock was found and removed.
5817bdc2678Schristos  */
5827bdc2678Schristos {       register struct rcslock * next, * trail;
5837bdc2678Schristos 	char const *num;
5847bdc2678Schristos 	struct rcslock dummy;
5857bdc2678Schristos         int whomatch, nummatch;
5867bdc2678Schristos 
5877bdc2678Schristos         num=delta->num;
5887bdc2678Schristos         dummy.nextlock=next=Locks;
5897bdc2678Schristos         trail = &dummy;
5907bdc2678Schristos 	while (next) {
5917bdc2678Schristos 		whomatch = strcmp(getcaller(), next->login);
5927bdc2678Schristos                 nummatch=strcmp(num,next->delta->num);
5937bdc2678Schristos                 if ((whomatch==0) && (nummatch==0)) break;
5947bdc2678Schristos 			/*found a lock on delta by caller*/
5957bdc2678Schristos                 if ((whomatch!=0)&&(nummatch==0)) {
5967bdc2678Schristos                     rcserror("revision %s locked by %s; use co -r or rcs -u",
5977bdc2678Schristos 			num, next->login
5987bdc2678Schristos 		    );
5997bdc2678Schristos                     return -1;
6007bdc2678Schristos                 }
6017bdc2678Schristos                 trail=next;
6027bdc2678Schristos                 next=next->nextlock;
6037bdc2678Schristos         }
6047bdc2678Schristos 	if (next) {
6057bdc2678Schristos                 /*found one; delete it */
6067bdc2678Schristos                 trail->nextlock=next->nextlock;
6077bdc2678Schristos                 Locks=dummy.nextlock;
6087bdc2678Schristos 		next->delta->lockedby = 0;
6097bdc2678Schristos                 return 1; /*success*/
6107bdc2678Schristos         } else  return 0; /*no lock on delta*/
6117bdc2678Schristos }
6127bdc2678Schristos 
6137bdc2678Schristos 
6147bdc2678Schristos 
6157bdc2678Schristos 
6167bdc2678Schristos /*****************************************************************
6177bdc2678Schristos  * The rest of the routines are for handling joins
6187bdc2678Schristos  *****************************************************************/
6197bdc2678Schristos 
6207bdc2678Schristos 
6217bdc2678Schristos 	static char *
addjoin(joinrev)6227bdc2678Schristos addjoin(joinrev)
6237bdc2678Schristos 	char *joinrev;
6247bdc2678Schristos /* Add joinrev's number to joinlist, yielding address of char past joinrev,
6257bdc2678Schristos  * or 0 if no such revision exists.
6267bdc2678Schristos  */
6277bdc2678Schristos {
6287bdc2678Schristos 	register char *j;
6297bdc2678Schristos 	register struct hshentry *d;
6307bdc2678Schristos 	char terminator;
6317bdc2678Schristos 	struct buf numrev;
6327bdc2678Schristos 	struct hshentries *joindeltas;
6337bdc2678Schristos 
6347bdc2678Schristos 	j = joinrev;
6357bdc2678Schristos 	for (;;) {
6367bdc2678Schristos 	    switch (*j++) {
6377bdc2678Schristos 		default:
6387bdc2678Schristos 		    continue;
6397bdc2678Schristos 		case 0:
6407bdc2678Schristos 		case ' ': case '\t': case '\n':
6417bdc2678Schristos 		case ':': case ',': case ';':
6427bdc2678Schristos 		    break;
6437bdc2678Schristos 	    }
6447bdc2678Schristos 	    break;
6457bdc2678Schristos 	}
6467bdc2678Schristos 	terminator = *--j;
6477bdc2678Schristos 	*j = 0;
6487bdc2678Schristos 	bufautobegin(&numrev);
6497bdc2678Schristos 	d = 0;
6507bdc2678Schristos 	if (expandsym(joinrev, &numrev))
6517bdc2678Schristos 	    d = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&joindeltas);
6527bdc2678Schristos 	bufautoend(&numrev);
6537bdc2678Schristos 	*j = terminator;
6547bdc2678Schristos 	if (d) {
6557bdc2678Schristos 		joinlist[++lastjoin] = d->num;
6567bdc2678Schristos 		return j;
6577bdc2678Schristos 	}
6587bdc2678Schristos 	return 0;
6597bdc2678Schristos }
6607bdc2678Schristos 
6617bdc2678Schristos 	static int
preparejoin(j)6627bdc2678Schristos preparejoin(j)
6637bdc2678Schristos 	register char *j;
6647bdc2678Schristos /* Parse join list J and place pointers to the
6657bdc2678Schristos  * revision numbers into joinlist.
6667bdc2678Schristos  */
6677bdc2678Schristos {
6687bdc2678Schristos         lastjoin= -1;
6697bdc2678Schristos         for (;;) {
6707bdc2678Schristos                 while ((*j==' ')||(*j=='\t')||(*j==',')) j++;
6717bdc2678Schristos                 if (*j=='\0') break;
6727bdc2678Schristos                 if (lastjoin>=joinlength-2) {
6737bdc2678Schristos 		    joinlist =
6747bdc2678Schristos 			(joinlength *= 2) == 0
6757bdc2678Schristos 			? tnalloc(char const *, joinlength = 16)
6767bdc2678Schristos 			: trealloc(char const *, joinlist, joinlength);
6777bdc2678Schristos                 }
6787bdc2678Schristos 		if (!(j = addjoin(j))) return false;
6797bdc2678Schristos                 while ((*j==' ') || (*j=='\t')) j++;
6807bdc2678Schristos                 if (*j == ':') {
6817bdc2678Schristos                         j++;
6827bdc2678Schristos                         while((*j==' ') || (*j=='\t')) j++;
6837bdc2678Schristos                         if (*j!='\0') {
6847bdc2678Schristos 				if (!(j = addjoin(j))) return false;
6857bdc2678Schristos                         } else {
6867bdc2678Schristos 				rcsfaterror("join pair incomplete");
6877bdc2678Schristos                         }
6887bdc2678Schristos                 } else {
6897bdc2678Schristos                         if (lastjoin==0) { /* first pair */
6907bdc2678Schristos                                 /* common ancestor missing */
6917bdc2678Schristos                                 joinlist[1]=joinlist[0];
6927bdc2678Schristos                                 lastjoin=1;
6937bdc2678Schristos                                 /*derive common ancestor*/
6947bdc2678Schristos 				if (!(joinlist[0] = getancestor(targetdelta->num,joinlist[1])))
6957bdc2678Schristos                                        return false;
6967bdc2678Schristos                         } else {
6977bdc2678Schristos 				rcsfaterror("join pair incomplete");
6987bdc2678Schristos                         }
6997bdc2678Schristos                 }
7007bdc2678Schristos         }
7017bdc2678Schristos 	if (lastjoin < 1)
7027bdc2678Schristos 		rcsfaterror("empty join");
7037bdc2678Schristos 	return true;
7047bdc2678Schristos }
7057bdc2678Schristos 
7067bdc2678Schristos 
7077bdc2678Schristos 
7087bdc2678Schristos 	static char const *
getancestor(r1,r2)7097bdc2678Schristos getancestor(r1, r2)
7107bdc2678Schristos 	char const *r1, *r2;
7117bdc2678Schristos /* Yield the common ancestor of r1 and r2 if successful, 0 otherwise.
7127bdc2678Schristos  * Work reliably only if r1 and r2 are not branch numbers.
7137bdc2678Schristos  */
7147bdc2678Schristos {
7157bdc2678Schristos 	static struct buf t1, t2;
7167bdc2678Schristos 
7177bdc2678Schristos 	int l1, l2, l3;
7187bdc2678Schristos 	char const *r;
7197bdc2678Schristos 
7207bdc2678Schristos 	l1 = countnumflds(r1);
7217bdc2678Schristos 	l2 = countnumflds(r2);
7227bdc2678Schristos 	if ((2<l1 || 2<l2)  &&  cmpnum(r1,r2)!=0) {
7237bdc2678Schristos 	    /* not on main trunk or identical */
7247bdc2678Schristos 	    l3 = 0;
7257bdc2678Schristos 	    while (cmpnumfld(r1, r2, l3+1)==0 && cmpnumfld(r1, r2, l3+2)==0)
7267bdc2678Schristos 		l3 += 2;
7277bdc2678Schristos 	    /* This will terminate since r1 and r2 are not the same; see above. */
7287bdc2678Schristos 	    if (l3==0) {
7297bdc2678Schristos 		/* no common prefix; common ancestor on main trunk */
7307bdc2678Schristos 		VOID partialno(&t1, r1, l1>2 ? 2 : l1);
7317bdc2678Schristos 		VOID partialno(&t2, r2, l2>2 ? 2 : l2);
7327bdc2678Schristos 		r = cmpnum(t1.string,t2.string)<0 ? t1.string : t2.string;
7337bdc2678Schristos 		if (cmpnum(r,r1)!=0 && cmpnum(r,r2)!=0)
7347bdc2678Schristos 			return r;
7357bdc2678Schristos 	    } else if (cmpnumfld(r1, r2, l3+1)!=0)
7367bdc2678Schristos 			return partialno(&t1,r1,l3);
7377bdc2678Schristos 	}
7387bdc2678Schristos 	rcserror("common ancestor of %s and %s undefined", r1, r2);
7397bdc2678Schristos 	return 0;
7407bdc2678Schristos }
7417bdc2678Schristos 
7427bdc2678Schristos 
7437bdc2678Schristos 
7447bdc2678Schristos 	static int
buildjoin(initialfile)7457bdc2678Schristos buildjoin(initialfile)
7467bdc2678Schristos 	char const *initialfile;
7477bdc2678Schristos /* Function: merge pairs of elements in joinlist into initialfile
7487bdc2678Schristos  * If workstdout is set, copy result to stdout.
7497bdc2678Schristos  * All unlinking of initialfile, rev2, and rev3 should be done by tempunlink().
7507bdc2678Schristos  */
7517bdc2678Schristos {
7527bdc2678Schristos 	struct buf commarg;
7537bdc2678Schristos 	struct buf subs;
7547bdc2678Schristos 	char const *rev2, *rev3;
7557bdc2678Schristos         int i;
7567bdc2678Schristos 	char const *cov[10], *mergev[11];
7577bdc2678Schristos 	char const **p;
7587bdc2678Schristos 
7597bdc2678Schristos 	bufautobegin(&commarg);
7607bdc2678Schristos 	bufautobegin(&subs);
7617bdc2678Schristos 	rev2 = maketemp(0);
7627bdc2678Schristos 	rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */
7637bdc2678Schristos 
7647bdc2678Schristos 	cov[1] = CO;
7657bdc2678Schristos 	/* cov[2] setup below */
7667bdc2678Schristos 	p = &cov[3];
7677bdc2678Schristos 	if (expandarg) *p++ = expandarg;
7687bdc2678Schristos 	if (suffixarg) *p++ = suffixarg;
7697bdc2678Schristos 	if (versionarg) *p++ = versionarg;
7707bdc2678Schristos 	if (zonearg) *p++ = zonearg;
7717bdc2678Schristos 	*p++ = quietarg;
7727bdc2678Schristos 	*p++ = RCSname;
7737bdc2678Schristos 	*p = 0;
7747bdc2678Schristos 
7757bdc2678Schristos 	mergev[1] = MERGE;
7767bdc2678Schristos 	mergev[2] = mergev[4] = "-L";
7777bdc2678Schristos 	/* rest of mergev setup below */
7787bdc2678Schristos 
7797bdc2678Schristos         i=0;
7807bdc2678Schristos         while (i<lastjoin) {
7817bdc2678Schristos                 /*prepare marker for merge*/
7827bdc2678Schristos                 if (i==0)
7837bdc2678Schristos 			bufscpy(&subs, targetdelta->num);
7847bdc2678Schristos 		else {
7857bdc2678Schristos 			bufscat(&subs, ",");
7867bdc2678Schristos 			bufscat(&subs, joinlist[i-2]);
7877bdc2678Schristos 			bufscat(&subs, ":");
7887bdc2678Schristos 			bufscat(&subs, joinlist[i-1]);
7897bdc2678Schristos 		}
7907bdc2678Schristos 		diagnose("revision %s\n",joinlist[i]);
7917bdc2678Schristos 		bufscpy(&commarg, "-p");
7927bdc2678Schristos 		bufscat(&commarg, joinlist[i]);
7937bdc2678Schristos 		cov[2] = commarg.string;
7947bdc2678Schristos 		if (runv(-1, rev2, cov))
7957bdc2678Schristos 			goto badmerge;
7967bdc2678Schristos 		diagnose("revision %s\n",joinlist[i+1]);
7977bdc2678Schristos 		bufscpy(&commarg, "-p");
7987bdc2678Schristos 		bufscat(&commarg, joinlist[i+1]);
7997bdc2678Schristos 		cov[2] = commarg.string;
8007bdc2678Schristos 		if (runv(-1, rev3, cov))
8017bdc2678Schristos 			goto badmerge;
8027bdc2678Schristos 		diagnose("merging...\n");
8037bdc2678Schristos 		mergev[3] = subs.string;
8047bdc2678Schristos 		mergev[5] = joinlist[i+1];
8057bdc2678Schristos 		p = &mergev[6];
8067bdc2678Schristos 		if (quietflag) *p++ = quietarg;
8077bdc2678Schristos 		if (lastjoin<=i+2 && workstdout) *p++ = "-p";
8087bdc2678Schristos 		*p++ = initialfile;
8097bdc2678Schristos 		*p++ = rev2;
8107bdc2678Schristos 		*p++ = rev3;
8117bdc2678Schristos 		*p = 0;
8127bdc2678Schristos 		switch (runv(-1, (char*)0, mergev)) {
8137bdc2678Schristos 		    case DIFF_FAILURE: case DIFF_SUCCESS:
8147bdc2678Schristos 			break;
8157bdc2678Schristos 		    default:
8167bdc2678Schristos 			goto badmerge;
8177bdc2678Schristos 		}
8187bdc2678Schristos                 i=i+2;
8197bdc2678Schristos         }
8207bdc2678Schristos 	bufautoend(&commarg);
8217bdc2678Schristos 	bufautoend(&subs);
8227bdc2678Schristos         return true;
8237bdc2678Schristos 
8247bdc2678Schristos     badmerge:
8257bdc2678Schristos 	nerror++;
8267bdc2678Schristos 	bufautoend(&commarg);
8277bdc2678Schristos 	bufautoend(&subs);
8287bdc2678Schristos 	return false;
8297bdc2678Schristos }
830