Add -Mvc option.
[users/jgh/exim.git] / test / src / cf.c
1 /* $Cambridge: exim/test/src/cf.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
2
3 /************************************************
4 *                  PH-Compare                   *
5 ************************************************/
6
7 /* A program to compare two files line by line.
8
9 History:
10
11 It was originally written in C, but the C under
12 Panos is still a shambles (1986). Translated therefore
13 to BCPL -- this explains some of the odd style.
14
15 Modified to run on Archimedes, August 1987.
16 Modified to run under MSDOS, March 1989.
17 Modified to run under CINTERP interpreter, July 1989.
18 Modified to run under Unix, October 1989.
19
20 Translated back into C, March 1990! */
21
22 /* Copyright (c) 1986, 1987, 1989, 1990, 1994, 2001 by Philip Hazel */
23
24 /* Previously modified: October 1994*/
25 /* Last modified: September 2001 - a long-lived bug fixed! */
26
27
28 #include <stdio.h>
29 #include <errno.h>
30
31 #ifdef __STDC__
32 #include <string.h>
33 #include <stdlib.h>
34 #endif
35
36 /* ----- parameters ----- */
37
38 #define version            8
39 #define defaultstore  100000     /* default recovery buffer size */
40 #define minstore         500     /* minimum recovery buffer size */
41
42 /* ----- misc defines ----- */
43
44 #define FALSE 0
45 #define TRUE  1
46
47 #ifdef __STDC__
48 #define pvoid     void
49 #else
50 #define pvoid
51 #endif
52
53 #define EqString(s, t)   (strcmp(s, t) == 0)
54
55 /* ----- line structure ----- */
56
57 typedef struct line {
58   struct line *next;
59   int  number;
60   char text[999999];
61 } line;
62
63
64 /* ----- global variables ----- */
65
66 FILE *f_one;           /* files */
67 FILE *f_two;
68 FILE *f_out;
69
70 int lines_one = 0;            /* line counts */
71 int lines_two = 0;
72 int return_code = 0;
73 int eof_one = FALSE;          /* eof flags */
74 int eof_two = FALSE;
75 int exact = FALSE;            /* TRUE => no strip spaces */
76 int echo = TRUE;              /* TRUE => show mismatched lines */
77 int sync_count = 3;           /* resync count */
78 int storesize = defaultstore; /* size of each buffer */
79
80 char *name_one = NULL;        /* file names */
81 char *name_two = NULL;
82 char *to_name = NULL;
83
84 char *bufbase_one;            /* start buffer */
85 char *bufbase_two;
86 char *bufnext_one;            /* next free byte */
87 char *bufnext_two;
88 char *buftop_one;             /* end buffer */
89 char *buftop_two;
90
91 line *rootline_one;           /* mis-match point */
92 line *rootline_two;
93 line *lastline_one;           /* last in store */
94 line *lastline_two;
95 line *pline_one;              /* working line */
96 line *pline_two;
97
98
99 /*************************************************
100 *             Help Information                   *
101 *************************************************/
102
103 void givehelp(pvoid)
104 {
105 printf("PH's CMP v%d\n", version);
106 printf("Keywords:\n");
107 printf("       <file>    ) files to compare\n");
108 printf("       <file>    ) no keywords used\n");
109 printf("-to    <file>      output destination\n");
110 printf("-exact             include trailing spaces & match tabs\n");
111 printf("-noecho            don't echo differences (just give line numbers)\n");
112 printf("-s, -sync  <n>     set re-sync count, default 3\n");
113 printf("-buffer <n>        buffer size (for each file) default 100000\n");
114 printf("-id                give program version id\n");
115 printf("-h, -help          give this help\n");
116 printf("\nExamples:\n");
117 printf("cmp old.f77 new.f77\n");
118 printf("cmp first second -noecho -sync 1\n");
119 printf("cmp large1 large2 -buffer 200000 -noecho -to diffs\n");
120 }
121
122
123
124 /************************************************
125 *               Errors -- all serious           *
126 ************************************************/
127
128 void moan(code, text)
129 int code;
130 char *text;
131 {
132 fprintf(stderr, "\n** ");
133 switch (code)
134   {
135   case 1:
136   fprintf(stderr, "Unable to open file \"%s\"", text);
137   if (errno)
138     {
139     fprintf(stderr, " - ");
140     perror(NULL);
141     }
142   else fprintf(stderr, "\n");
143   break;
144
145   case 2:
146   fprintf(stderr, "Buffer overflow for file \"%s\"\n", text);
147   break;
148
149   case 3:
150   fprintf(stderr, "Two file names must be given\n");
151   break;
152
153   default:
154   fprintf(stderr, "Unknown error %d\n", code);
155   break;
156   }
157
158 fprintf(stderr, "** CMP abandoned\n");
159 exit(99);
160 }
161
162
163
164 /*************************************************
165 *         Write line identification              *
166 *************************************************/
167
168 void write_id(n1, n2, c, name, p1, p2)
169 int n1, n2, c;
170 char *name, *p1, *p2;
171 {
172 if (n2 < 0) n2 = -n2;
173 n2 -= 1;
174 fprintf(f_out, "%cine", c);
175 if (n1 == n2) fprintf(f_out, " %d of \"%s\"%s", n1, name, p1);
176   else fprintf(f_out, "s %d-%d of \"%s\"%s", n1, n2, name, p2);
177 }
178
179
180 /*************************************************
181 *           Write sequence of lines              *
182 *************************************************/
183
184 void write_lines(s, t)
185 line *s, *t;
186 {
187 while (s != t)
188   {
189   char *p = s->text;
190   while (*p != '\n') fputc(*p++, f_out);
191   fputc('\n', f_out);
192   s = s->next;
193   }
194 }
195
196
197
198 /*************************************************
199 *           Write separator rule                 *
200 *************************************************/
201
202 void rule(s, l)
203 int s, l;
204 {
205 while (l-- > 0) fprintf(f_out, "%c", s);
206 fprintf(f_out, "\n");
207 }
208
209
210
211 /*************************************************
212 *          Write message on re-sync or eof       *
213 *************************************************/
214
215 void write_message(tline_one, tline_two)
216 line *tline_one, *tline_two;
217 {
218 int s1 = rootline_one->number;
219 int t1 = tline_one->number;
220 int s2 = rootline_two->number;
221 int t2 = tline_two->number;
222 if (echo) rule('=', 15);
223
224 if (s1 == t1)
225   {
226   write_id(s2, t2, 'L', name_two, " occurs ", " occur ");
227   if (s1 < 0) fprintf(f_out, "at the end");
228     else fprintf(f_out, "before line %d", s1);
229   fprintf(f_out, " of \"%s\".\n", name_one);
230   if (echo)
231     {
232     rule('-', 10);
233     write_lines(rootline_two, tline_two);
234     }
235   }
236
237 else if (s2 == t2)
238   {
239   write_id(s1, t1, 'L', name_one, " occurs ", " occur ");
240   if (s2 < 0) fprintf(f_out, "at the end");
241     else fprintf(f_out, "before line %d", s2);
242   fprintf(f_out, " of \"%s\".\n", name_two);
243   if (echo)
244     {
245     rule('-', 10);
246     write_lines(rootline_one, tline_one);
247     }
248   }
249
250 else if (t1 < 0 && t2 < 0)
251   {
252   fprintf(f_out, "From line %d of \"%s\" and line %d of \"%s\" ",
253     rootline_one->number, name_one, rootline_two->number, name_two);
254   fprintf(f_out, "the files are different.\n");
255   if (echo)
256     {
257     rule('-', 10);
258     if (-t1-s1 < 21) write_lines(rootline_one, tline_one);
259       else fprintf(f_out, "... <more than 20 lines> ...\n");
260     rule('-', 10);
261     if (-t2-s2 < 21) write_lines(rootline_two, tline_two);
262       else fprintf(f_out, "... <more than 20 lines> ...\n");
263     }
264   }
265
266 else
267   {
268   write_id(s1, t1, 'L', name_one, " does ", " do ");
269   fprintf(f_out, "not match ");
270   write_id(s2, t2, 'l', name_two, ".\n", ".\n");
271   if (echo)
272     {
273     rule('-', 10);
274     write_lines(rootline_one, tline_one);
275     rule('-', 10);
276     write_lines(rootline_two, tline_two);
277     }
278   }
279 }
280
281
282
283
284 /*************************************************
285 *           Advance to next line in store        *
286 *************************************************/
287
288 /* A separate procedure exists for each file, for
289 simplicity and efficiency. */
290
291 int nextline_one(pvoid)
292 {
293 if (pline_one == NULL || pline_one->next == NULL) return FALSE;
294 pline_one = pline_one->next;
295 return TRUE;
296 }
297
298 int nextline_two(pvoid)
299 {
300 if (pline_two == NULL || pline_two->next == NULL) return FALSE;
301 pline_two = pline_two->next;
302 return TRUE;
303 }
304
305
306 /*************************************************
307 *             Read a line into store             *
308 *************************************************/
309
310 /* A separate procedure exists for each file, for
311 simplicity and efficiency. */
312
313 void readline_one(pvoid)
314 {
315 int count = 0;
316 int c = fgetc(f_one);
317 line *nextline = (line *)bufnext_one;
318
319 bufnext_one = nextline->text;
320 if (bufnext_one >= buftop_one) moan(2, name_one);
321
322 nextline->next = NULL;
323
324 lines_one ++;
325 if (c == EOF)
326   {
327   eof_one = TRUE;
328   nextline->number = -lines_one;
329   }
330 else
331   {
332   nextline->number = lines_one;
333   for (;;)
334     {
335     if (c == EOF) c = '\n';
336     if (c == '\n')
337       {
338       if (!exact)
339         while (bufnext_one > nextline->text)
340           { if (bufnext_one[-1] == ' ') bufnext_one--; else break; }
341       *(bufnext_one++) = '\n';
342       if (bufnext_one >= buftop_one) moan(2, name_one);
343       break;
344       }
345     if (c == '\t' && !exact)
346       do { *(bufnext_one++) = ' '; count++; } while ((count & 7) != 0);
347     else { *(bufnext_one++) = c; count++; }
348     if (bufnext_one >= buftop_one) moan(2, name_one);
349     c = fgetc(f_one);
350     }
351   }
352
353 if (lastline_one != NULL) lastline_one->next = nextline;
354 lastline_one = nextline;
355 pline_one = nextline;
356
357 bufnext_one = (char *) (((int)bufnext_one+3) & (-4));
358 }
359
360
361
362 void readline_two(pvoid)
363 {
364 int count = 0;
365 int c = fgetc(f_two);
366 line *nextline = (line *)bufnext_two;
367
368 bufnext_two = nextline->text;
369 if (bufnext_two >= buftop_two) moan(2, name_two);
370
371 nextline->next = NULL;
372
373 lines_two ++;
374 if (c == EOF)
375   {
376   eof_two = TRUE;
377   nextline->number = -lines_two;
378   }
379 else
380   {
381   nextline->number = lines_two;
382   for (;;)
383     {
384     if (c == EOF) c = '\n';
385     if (c == '\n')
386       {
387       if (!exact)
388         while (bufnext_two > nextline->text)
389           { if (bufnext_two[-1] == ' ') bufnext_two--; else break; }
390       *(bufnext_two++) = '\n';
391       if (bufnext_two >= buftop_two) moan(2, name_two);
392       break;
393       }
394     if (c == '\t' && !exact)
395       do { *(bufnext_two++) = ' '; count++; } while ((count & 7) != 0);
396     else { *(bufnext_two++) = c; count++; }
397     if (bufnext_two >= buftop_two) moan(2, name_two);
398     c = fgetc(f_two);
399     }
400   }
401
402 if (lastline_two != NULL) lastline_two->next = nextline;
403 lastline_two = nextline;
404 pline_two = nextline;
405
406 bufnext_two = (char *) (((int)bufnext_two+3) & (-4));
407 }
408
409
410
411 /**************************************************
412 *              Compare two lines                  *
413 **************************************************/
414
415 int compare_lines(a, b)
416 line *a, *b;
417 {
418 int n1 = a->number;
419 int n2 = b->number;
420 char *s = a->text;
421 char *t = b->text;
422
423 if (n1 < 0  &&  n2 < 0) return TRUE;
424 if (n1 < 0  ||  n2 < 0) return FALSE;
425
426 while (*s == *t)
427   {
428   if (*s == '\n') return TRUE;
429   s++; t++;
430   }
431
432 return FALSE;
433 }
434
435
436 /*************************************************
437 *             Re-synchronizing code              *
438 *************************************************/
439
440 int resync(pvoid)
441 {
442 int i;
443 int matched = TRUE;
444 line *tline_one = pline_one;
445 line *tline_two = pline_two;
446
447 if (eof_one || eof_two) matched = FALSE; else
448   {
449   for (i = 1; i < sync_count; i++)
450     {
451     if (!nextline_one()) readline_one();
452     if (!nextline_two()) readline_two();
453     if (!compare_lines(pline_one, pline_two)) { matched = FALSE; break; }
454     if (eof_one || eof_two) { matched = FALSE; break; }
455     }
456   }
457
458 if (matched) write_message(tline_one, tline_two); else
459   {
460   pline_one = tline_one;
461   pline_two = tline_two;
462   }
463
464 return matched;
465 }
466
467
468
469 /*************************************************
470 *                 Main compare code              *
471 *************************************************/
472
473 void compare(pvoid)
474 {
475 int matched = TRUE;
476
477 /* Big main loop - exit by return or unmatched at eof */
478
479 while (matched)
480   {
481   /* First minor loop, while in step */
482
483   while (matched && !eof_one && !eof_two)
484     {
485     /* Advance or read next lines */
486
487     if (!nextline_one())
488       {
489       bufnext_one = bufbase_one;
490       lastline_one = NULL;
491       readline_one();
492       }
493
494     if (!nextline_two())
495       {
496       bufnext_two = bufbase_two;
497       lastline_two = NULL;
498       readline_two();
499       }
500
501     /* Compare and check for end of file */
502
503     matched = compare_lines(pline_one, pline_two);
504
505     } /* End first minor loop */
506
507   if (matched) return;    /* successful end of file */
508
509   /* There has been a mis-match */
510
511   return_code++;
512   rootline_one = pline_one;   /* Fail point */
513   rootline_two = pline_two;
514
515   /* Second minor loop, trying to regain sync */
516
517   while (!eof_one || !eof_two)
518     {
519     /* Advance one and scan all of two */
520
521     if (!eof_one)
522       {
523       line *zline = pline_two;
524       if (!nextline_one()) readline_one();
525       pline_two = rootline_two;
526       for (;;)
527         {
528         if (compare_lines(pline_one, pline_two))
529           {
530           matched = resync();
531           if (matched) break;
532           }
533         if (pline_two == zline) break;
534         pline_two = pline_two->next;
535         }
536       if (matched) break;
537       }
538
539     /* Advance two and scan all of one */
540
541     if (!eof_two)
542       {
543       line *zline = pline_one;
544       if (!nextline_two()) readline_two();
545       pline_one = rootline_one;
546       for (;;)
547         {
548         if (compare_lines(pline_one, pline_two))
549           {
550           matched = resync();
551           if (matched) break;
552           }
553         if (pline_one == zline) break;
554         pline_one = pline_one->next;
555         }
556       if (matched) break;
557       }
558
559     } /* End second minor loop */
560
561   } /* End of major loop */
562
563 write_message(lastline_one, lastline_two);
564 }
565
566
567
568
569 /*************************************************
570 *                   Entry Point                  *
571 *************************************************/
572
573 int main(argc, argv)
574 int argc;
575 char **argv;
576 {
577 int argp = 1;
578 int arg_id = FALSE;
579 int arg_help = FALSE;
580
581 f_out = stdout;
582
583 /* Scan argument strings */
584
585 while (argp < argc)
586   {
587   char  *arg = argv[argp];
588   char **lv_name = (name_one == NULL)? &name_one:&name_two;  /* default for positional */
589   int   *lv_value = NULL;
590   int    value = TRUE;
591
592   if (arg[0] == '-')
593     {                            /* keyed argument */
594     if (EqString(arg,"-help") || EqString(arg, "-h"))
595       { arg_help = TRUE; value = FALSE; }
596     else if (EqString(arg, "-id"))
597       { arg_id = TRUE; value = FALSE; }
598     else if (EqString(arg, "-exact"))
599       { exact = TRUE; value = FALSE; }
600     else if (EqString(arg, "-noecho"))
601       { echo = FALSE; value = FALSE; }
602     else if (EqString(arg, "-to")) lv_name = &to_name;
603     else if (EqString(arg, "-sync") || EqString(arg, "-s"))
604        lv_value = &sync_count;
605     else if (EqString(arg, "-buffer")) lv_value = &storesize;
606     else { printf("Unknown keyword %s\n", arg); exit(99); }
607
608     if (++argp >= argc && value)
609       { printf("Value for keyword %s missing\n", arg); exit(99); }
610     }
611
612   /* Deal with keys that take values */
613
614   if (value)
615     {
616     if (lv_value == &sync_count || lv_value == &storesize)
617       {
618       int ch;
619       int i = 0;
620       char *argval = argv[argp++];
621       *lv_value = 0;
622       while ((ch = argval[i++]) != 0)
623         {
624         if ('0' <= ch && ch <= '9') *lv_value = 10*(*lv_value) + ch - '0'; else
625           {
626           printf("Number expected after \"%s\" but \"%s\" read\n",
627             arg, argval);
628           exit(99);
629           }
630         }
631       }
632
633     else if (*lv_name != NULL)
634       {
635       printf("Keyword expected but \"%s\" read", arg);
636       printf(" - use \"cmp -h\" for help\n");
637       exit(99);
638       }
639     else *lv_name = argv[argp++];
640     }
641   }
642
643 /* Deal with help and id */
644
645 if (arg_id && !arg_help)
646   {
647   printf("PH's CMP v%d\n", version);
648   exit(0);
649   }
650
651 if (arg_help)
652   {
653   givehelp();
654   exit(0);
655   }
656
657 /* Deal with file names */
658
659 if (name_one == NULL || name_two == NULL) moan(3, "");
660
661 if (to_name != NULL)
662   {
663   f_out = fopen(to_name, "w");
664   if (f_out == NULL) moan(1, to_name);
665   }
666
667 /* Further general initialization */
668
669 if (storesize < minstore) storesize = defaultstore;
670 f_one = fopen(name_one, "r");
671 if (f_one == NULL) moan(1, name_one);
672 f_two = fopen(name_two, "r");
673 if (f_two == NULL) moan(1, name_two);
674
675 bufbase_one = (char *)malloc(storesize);
676 buftop_one = bufbase_one + storesize;
677 bufbase_two = (char *)malloc(storesize);
678 buftop_two = bufbase_two + storesize;
679
680 /* Do the job */
681
682 compare();
683
684 /* Final messages */
685
686 if (return_code == 0)
687   fprintf(f_out, "\"%s\" and \"%s\" are identical.\n", name_one, name_two);
688 else
689   {
690   if (echo) rule('=', 15);
691   fprintf(f_out, "%d difference", return_code);
692   if (return_code != 1) fprintf(f_out, "s");
693   fprintf(f_out, " found.\n");
694
695   lines_one -= 1;
696   fprintf(f_out, "\"%s\" contains %d line", name_one, lines_one);
697   if (lines_one != 1) fprintf(f_out, "s");
698
699   lines_two -= 1;
700   fprintf(f_out, "; \"%s\" contains %d line", name_two, lines_two);
701   if (lines_two != 1) fprintf(f_out, "s");
702   fprintf(f_out, ".\n");
703   }
704
705 free(bufbase_one);
706 free(bufbase_two);
707
708 fclose(f_one);
709 fclose(f_two);
710 if (f_out != stdout) fclose(f_out);
711
712 return return_code;
713 }
714
715 /* End of PH-Compare. */