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