1/*	$NetBSD: rcsdiff.c,v 1.7 2011/05/15 14:33:12 christos Exp $	*/
2
3/* Compare RCS revisions.  */
4
5/* Copyright 1982, 1988, 1989 Walter Tichy
6   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
7   Distributed under license by the Free Software Foundation, Inc.
8
9This file is part of RCS.
10
11RCS is free software; you can redistribute it and/or modify
12it under the terms of the GNU General Public License as published by
13the Free Software Foundation; either version 2, or (at your option)
14any later version.
15
16RCS is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU General Public License for more details.
20
21You should have received a copy of the GNU General Public License
22along with RCS; see the file COPYING.
23If not, write to the Free Software Foundation,
2459 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26Report problems and direct all questions to:
27
28    rcs-bugs@cs.purdue.edu
29
30*/
31
32/*
33 * $Log: rcsdiff.c,v $
34 * Revision 1.8  2012/01/06 15:16:03  joerg
35 * Don't use dangling elses.
36 *
37 * Revision 1.7  2011/05/15 14:33:12  christos
38 * register c -> int c
39 *
40 * Revision 1.6  2009/11/06 22:02:35  enami
41 * Accept -U num.  Nowadays, diff(1) rejects -u0 etc by default.
42 *
43 * Revision 1.5  1999/07/22 01:48:09  hubertf
44 * Allow -L on both files, not only one.
45 *
46 * Reported in PR 1947 by Niklas Hallqvist <niklas@filippa.appli.se> and
47 * also fed back to the GNU RCS maintainers.
48 *
49 * Revision 1.4  1996/10/15 07:00:40  veego
50 * Merge rcs 5.7.
51 *
52 * Revision 5.19  1995/06/16 06:19:24  eggert
53 * Update FSF address.
54 *
55 * Revision 5.18  1995/06/01 16:23:43  eggert
56 * (main): Pass "--binary" if -kb and if --binary makes a difference.
57 * Don't treat + options specially.
58 *
59 * Revision 5.17  1994/03/17 14:05:48  eggert
60 * Specify subprocess input via file descriptor, not file name.  Remove lint.
61 *
62 * Revision 5.16  1993/11/09 17:40:15  eggert
63 * -V now prints version on stdout and exits.  Don't print usage twice.
64 *
65 * Revision 5.15  1993/11/03 17:42:27  eggert
66 * Add -z.  Ignore -T.  Pass -Vn to `co'.  Add Name keyword.
67 * Put revision numbers in -c output.  Improve quality of diagnostics.
68 *
69 * Revision 5.14  1992/07/28  16:12:44  eggert
70 * Add -V.  Use co -M for better dates with traditional diff -c.
71 *
72 * Revision 5.13  1992/02/17  23:02:23  eggert
73 * Output more readable context diff headers.
74 * Suppress needless checkout and comparison of identical revisions.
75 *
76 * Revision 5.12  1992/01/24  18:44:19  eggert
77 * Add GNU diff 1.15.2's new options.  lint -> RCS_lint
78 *
79 * Revision 5.11  1992/01/06  02:42:34  eggert
80 * Update usage string.
81 *
82 * Revision 5.10  1991/10/07  17:32:46  eggert
83 * Remove lint.
84 *
85 * Revision 5.9  1991/08/19  03:13:55  eggert
86 * Add RCSINIT, -r$.  Tune.
87 *
88 * Revision 5.8  1991/04/21  11:58:21  eggert
89 * Add -x, RCSINIT, MS-DOS support.
90 *
91 * Revision 5.7  1990/12/13  06:54:07  eggert
92 * GNU diff 1.15 has -u.
93 *
94 * Revision 5.6  1990/11/01  05:03:39  eggert
95 * Remove unneeded setid check.
96 *
97 * Revision 5.5  1990/10/04  06:30:19  eggert
98 * Accumulate exit status across files.
99 *
100 * Revision 5.4  1990/09/27  01:31:43  eggert
101 * Yield 1, not EXIT_FAILURE, when diffs are found.
102 *
103 * Revision 5.3  1990/09/11  02:41:11  eggert
104 * Simplify -kkvl test.
105 *
106 * Revision 5.2  1990/09/04  17:07:19  eggert
107 * Diff's argv was too small by 1.
108 *
109 * Revision 5.1  1990/08/29  07:13:55  eggert
110 * Add -kkvl.
111 *
112 * Revision 5.0  1990/08/22  08:12:46  eggert
113 * Add -k, -V.  Don't use access().  Add setuid support.
114 * Remove compile-time limits; use malloc instead.
115 * Don't pass arguments with leading '+' to diff; GNU DIFF treats them as options.
116 * Add GNU diff's flags.  Make lock and temp files faster and safer.
117 * Ansify and Posixate.
118 *
119 * Revision 4.6  89/05/01  15:12:27  narten
120 * changed copyright header to reflect current distribution rules
121 *
122 * Revision 4.5  88/08/09  19:12:41  eggert
123 * Use execv(), not system(); yield exit status like diff(1)s; allow cc -R.
124 *
125 * Revision 4.4  87/12/18  11:37:46  narten
126 * changes Jay Lepreau made in the 4.3 BSD version, to add support for
127 * "-i", "-w", and "-t" flags and to permit flags to be bundled together,
128 * merged in.
129 *
130 * Revision 4.3  87/10/18  10:31:42  narten
131 * Updating version numbers. Changes relative to 1.1 actually
132 * relative to 4.1
133 *
134 * Revision 1.3  87/09/24  13:59:21  narten
135 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
136 * warnings)
137 *
138 * Revision 1.2  87/03/27  14:22:15  jenkins
139 * Port to suns
140 *
141 * Revision 4.1  83/05/03  22:13:19  wft
142 * Added default branch, option -q, exit status like diff.
143 * Added fterror() to replace faterror().
144 *
145 * Revision 3.6  83/01/15  17:52:40  wft
146 * Expanded mainprogram to handle multiple RCS files.
147 *
148 * Revision 3.5  83/01/06  09:33:45  wft
149 * Fixed passing of -c (context) option to diff.
150 *
151 * Revision 3.4  82/12/24  15:28:38  wft
152 * Added call to catchsig().
153 *
154 * Revision 3.3  82/12/10  16:08:17  wft
155 * Corrected checking of return code from diff; improved error msgs.
156 *
157 * Revision 3.2  82/12/04  13:20:09  wft
158 * replaced getdelta() with gettree(). Changed diagnostics.
159 *
160 * Revision 3.1  82/11/28  19:25:04  wft
161 * Initial revision.
162 *
163 */
164#include "rcsbase.h"
165
166#if DIFF_L
167static char const *setup_label P((struct buf*,char const*,char const[datesize]));
168#endif
169static void cleanup P((void));
170
171static int exitstatus;
172static RILE *workptr;
173static struct stat workstat;
174
175mainProg(rcsdiffId, "rcsdiff", "Id: rcsdiff.c,v 5.19 1995/06/16 06:19:24 eggert Exp")
176{
177    static char const cmdusage[] =
178	    "\nrcsdiff usage: rcsdiff -ksubst -q -rrev1 [-rrev2] -Vn -xsuff -zzone [diff options] file ...";
179
180    int  revnums;                 /* counter for revision numbers given */
181    char const *rev1, *rev2;	/* revision numbers from command line */
182    char const *xrev1, *xrev2;	/* expanded revision numbers */
183    char const *expandarg, *lexpandarg, *suffixarg, *versionarg, *zonearg;
184#if DIFF_L
185    static struct buf labelbuf[2];
186    int file_labels;
187    char const **diff_label1, **diff_label2;
188    char date2[datesize];
189#endif
190    char const *cov[10 + !DIFF_L];
191    char const **diffv, **diffp, **diffpend;	/* argv for subsidiary diff */
192    char const **pp, *p, *diffvstr;
193    struct buf commarg;
194    struct buf numericrev;	/* expanded revision number */
195    struct hshentries *gendeltas;	/* deltas to be generated */
196    struct hshentry * target;
197    char *a, *dcp, **newargv;
198    int no_diff_means_no_output;
199    int c;
200
201    exitstatus = DIFF_SUCCESS;
202
203    bufautobegin(&commarg);
204    bufautobegin(&numericrev);
205    revnums = 0;
206    rev1 = rev2 = xrev2 = 0;
207#if DIFF_L
208    file_labels = 0;
209#endif
210    expandarg = suffixarg = versionarg = zonearg = 0;
211    no_diff_means_no_output = true;
212    suffixes = X_DEFAULT;
213
214    /*
215    * Room for runv extra + args [+ --binary] [+ 2 labels]
216    * + 1 file + 1 trailing null.
217    */
218    diffv = tnalloc(char const*, 1 + argc + !!OPEN_O_BINARY + 2*DIFF_L + 2);
219    diffp = diffv + 1;
220    *diffp++ = DIFF;
221
222    argc = getRCSINIT(argc, argv, &newargv);
223    argv = newargv;
224    while (a = *++argv,  0<--argc && *a++=='-') {
225	dcp = a;
226	while ((c = *a++)) switch (c) {
227	    case 'r':
228		    switch (++revnums) {
229			case 1: rev1=a; break;
230			case 2: rev2=a; break;
231			default: error("too many revision numbers");
232		    }
233		    goto option_handled;
234	    case '-': case 'D':
235		    no_diff_means_no_output = false;
236		    /* fall into */
237	    case 'C': case 'F': case 'I': case 'L': case 'W': case 'U':
238#if DIFF_L
239		    if (c == 'L'  &&  file_labels++ == 2)
240			faterror("too many -L options");
241#endif
242		    *dcp++ = c;
243		    if (*a)
244			do *dcp++ = *a++;
245			while (*a);
246		    else {
247			if (!--argc)
248			    faterror("-%c needs following argument%s",
249				    c, cmdusage
250			    );
251			*diffp++ = *argv++;
252		    }
253		    break;
254	    case 'y':
255		    no_diff_means_no_output = false;
256		    /* fall into */
257	    case 'B': case 'H':
258	    case '0': case '1': case '2': case '3': case '4':
259	    case '5': case '6': case '7': case '8': case '9':
260	    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
261	    case 'h': case 'i': case 'n': case 'p':
262	    case 't': case 'u': case 'w':
263		    *dcp++ = c;
264		    break;
265	    case 'q':
266		    quietflag=true;
267		    break;
268	    case 'x':
269		    suffixarg = *argv;
270		    suffixes = *argv + 2;
271		    goto option_handled;
272	    case 'z':
273		    zonearg = *argv;
274		    zone_set(*argv + 2);
275		    goto option_handled;
276	    case 'T':
277		    /* Ignore -T, so that RCSINIT can contain -T.  */
278		    if (*a)
279			    goto unknown;
280		    break;
281	    case 'V':
282		    versionarg = *argv;
283		    setRCSversion(versionarg);
284		    goto option_handled;
285	    case 'k':
286		    expandarg = *argv;
287		    if (0 <= str2expmode(expandarg+2))
288			goto option_handled;
289		    /* fall into */
290	    default:
291	    unknown:
292		    error("unknown option: %s%s", *argv, cmdusage);
293	    };
294      option_handled:
295	if (dcp != *argv+1) {
296	    *dcp = 0;
297	    *diffp++ = *argv;
298	}
299    } /* end of option processing */
300
301    for (pp = diffv+2, c = 0;  pp<diffp;  )
302	    c += strlen(*pp++) + 1;
303    diffvstr = a = tnalloc(char, c + 1);
304    for (pp = diffv+2;  pp<diffp;  ) {
305	    p = *pp++;
306	    *a++ = ' ';
307	    while ((*a = *p++))
308		    a++;
309    }
310    *a = 0;
311
312#if DIFF_L
313    diff_label1 = diff_label2 = 0;
314    if (file_labels < 2) {
315	    if (!file_labels)
316		    diff_label1 = diffp++;
317	    diff_label2 = diffp++;
318    }
319#endif
320    diffpend = diffp;
321
322    cov[1] = CO;
323    cov[2] = "-q";
324#   if !DIFF_L
325	cov[3] = "-M";
326#   endif
327
328    /* Now handle all pathnames.  */
329    if (nerror)
330	cleanup();
331    else if (argc < 1)
332	faterror("no input file%s", cmdusage);
333    else
334	for (;  0 < argc;  cleanup(), ++argv, --argc) {
335	    ffree();
336
337	    if (pairnames(argc, argv, rcsreadopen, true, false)  <=  0)
338		    continue;
339	    diagnose("===================================================================\nRCS file: %s\n",RCSname);
340	    if (!rev2) {
341		/* Make sure work file is readable, and get its status.  */
342		if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) {
343		    eerror(workname);
344		    continue;
345		}
346	    }
347
348
349	    gettree(); /* reads in the delta tree */
350
351	    if (!Head) {
352		    rcserror("no revisions present");
353		    continue;
354	    }
355	    if (revnums==0  ||  !*rev1)
356		    rev1  =  Dbranch ? Dbranch : Head->num;
357
358	    if (!fexpandsym(rev1, &numericrev, workptr)) continue;
359	    if (!(target=genrevs(numericrev.string,(char *)0,(char *)0,(char *)0,&gendeltas))) continue;
360	    xrev1=target->num;
361#if DIFF_L
362	    if (diff_label1)
363		*diff_label1 = setup_label(&labelbuf[0], target->num, target->date);
364#endif
365
366	    lexpandarg = expandarg;
367	    if (revnums==2) {
368		    if (!fexpandsym(
369			    *rev2 ? rev2  : Dbranch ? Dbranch  : Head->num,
370			    &numericrev,
371			    workptr
372		    ))
373			continue;
374		    if (!(target=genrevs(numericrev.string,(char *)0,(char *)0,(char *)0,&gendeltas))) continue;
375		    xrev2=target->num;
376		    if (no_diff_means_no_output  &&  xrev1 == xrev2)
377			continue;
378	    } else if (
379			target->lockedby
380		&&	!lexpandarg
381		&&	Expand == KEYVAL_EXPAND
382		&&	WORKMODE(RCSstat.st_mode,true) == workstat.st_mode
383	    )
384		    lexpandarg = "-kkvl";
385	    Izclose(&workptr);
386#if DIFF_L
387	    if (diff_label2) {
388		if (revnums == 2)
389		    *diff_label2 = setup_label(&labelbuf[1], target->num, target->date);
390		else {
391		    time2date(workstat.st_mtime, date2);
392		    *diff_label2 = setup_label(&labelbuf[1], (char*)0, date2);
393		}
394	    }
395#endif
396
397	    diagnose("retrieving revision %s\n", xrev1);
398	    bufscpy(&commarg, "-p");
399	    bufscat(&commarg, rev1); /* not xrev1, for $Name's sake */
400
401	    pp = &cov[3 + !DIFF_L];
402	    *pp++ = commarg.string;
403	    if (lexpandarg) *pp++ = lexpandarg;
404	    if (suffixarg) *pp++ = suffixarg;
405	    if (versionarg) *pp++ = versionarg;
406	    if (zonearg) *pp++ = zonearg;
407	    *pp++ = RCSname;
408	    *pp = 0;
409
410	    diffp = diffpend;
411#	    if OPEN_O_BINARY
412		    if (Expand == BINARY_EXPAND)
413			    *diffp++ = "--binary";
414#	    endif
415	    diffp[0] = maketemp(0);
416	    if (runv(-1, diffp[0], cov)) {
417		    rcserror("co failed");
418		    continue;
419	    }
420	    if (!rev2) {
421		    diffp[1] = workname;
422		    if (*workname == '-') {
423			char *dp = ftnalloc(char, strlen(workname)+3);
424			diffp[1] = dp;
425			*dp++ = '.';
426			*dp++ = SLASH;
427			VOID strcpy(dp, workname);
428		    }
429	    } else {
430		    diagnose("retrieving revision %s\n",xrev2);
431		    bufscpy(&commarg, "-p");
432		    bufscat(&commarg, rev2); /* not xrev2, for $Name's sake */
433		    cov[3 + !DIFF_L] = commarg.string;
434		    diffp[1] = maketemp(1);
435		    if (runv(-1, diffp[1], cov)) {
436			    rcserror("co failed");
437			    continue;
438		    }
439	    }
440	    if (!rev2)
441		    diagnose("diff%s -r%s %s\n", diffvstr, xrev1, workname);
442	    else
443		    diagnose("diff%s -r%s -r%s\n", diffvstr, xrev1, xrev2);
444
445	    diffp[2] = 0;
446	    switch (runv(-1, (char*)0, diffv)) {
447		    case DIFF_SUCCESS:
448			    break;
449		    case DIFF_FAILURE:
450			    if (exitstatus == DIFF_SUCCESS)
451				    exitstatus = DIFF_FAILURE;
452			    break;
453		    default:
454			    workerror("diff failed");
455	    }
456	}
457
458    tempunlink();
459    exitmain(exitstatus);
460}
461
462    static void
463cleanup()
464{
465    if (nerror) exitstatus = DIFF_TROUBLE;
466    Izclose(&finptr);
467    Izclose(&workptr);
468}
469
470#if RCS_lint
471#	define exiterr rdiffExit
472#endif
473    void
474exiterr()
475{
476    tempunlink();
477    _exit(DIFF_TROUBLE);
478}
479
480#if DIFF_L
481	static char const *
482setup_label(b, num, date)
483	struct buf *b;
484	char const *num;
485	char const date[datesize];
486{
487	char *p;
488	char datestr[datesize + zonelenmax];
489	VOID date2str(date, datestr);
490	bufalloc(b,
491		strlen(workname)
492		+ sizeof datestr + 4
493		+ (num ? strlen(num) : 0)
494	);
495	p = b->string;
496	if (num)
497		VOID sprintf(p, "-L%s\t%s\t%s", workname, datestr, num);
498	else
499		VOID sprintf(p, "-L%s\t%s", workname, datestr);
500	return p;
501}
502#endif
503