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