]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/diff/diff.c
This commit was generated by cvs2svn to compensate for changes in r44335,
[FreeBSD/FreeBSD.git] / contrib / diff / diff.c
1 /* GNU DIFF main routine.
2    Copyright (C) 1988, 1989, 1992, 1993, 1994 Free Software Foundation, Inc.
3
4 This file is part of GNU DIFF.
5
6 GNU DIFF is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU DIFF is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU DIFF; see the file COPYING.  If not, write to
18 the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
19
20 /* GNU DIFF was written by Mike Haertel, David Hayes,
21    Richard Stallman, Len Tower, and Paul Eggert.  */
22
23 #define GDIFF_MAIN
24 #include "diff.h"
25 #include <signal.h>
26 #include "getopt.h"
27 #ifdef __FreeBSD__
28 #include <locale.h>
29 #include <fnmatch.h>
30 #else
31 #include "fnmatch.h"
32 #endif
33
34 #ifndef DEFAULT_WIDTH
35 #define DEFAULT_WIDTH 130
36 #endif
37
38 #ifndef GUTTER_WIDTH_MINIMUM
39 #define GUTTER_WIDTH_MINIMUM 3
40 #endif
41
42 static char const *filetype PARAMS((struct stat const *));
43 static char *option_list PARAMS((char **, int));
44 static int add_exclude_file PARAMS((char const *));
45 static int ck_atoi PARAMS((char const *, int *));
46 static int compare_files PARAMS((char const *, char const *, char const *, char const *, int));
47 static int specify_format PARAMS((char **, char *));
48 static void add_exclude PARAMS((char const *));
49 static void add_regexp PARAMS((struct regexp_list **, char const *));
50 static void specify_style PARAMS((enum output_style));
51 static void try_help PARAMS((char const *));
52 static void check_stdout PARAMS((void));
53 static void usage PARAMS((void));
54
55 /* Nonzero for -r: if comparing two directories,
56    compare their common subdirectories recursively.  */
57
58 static int recursive;
59
60 /* For debugging: don't do discard_confusing_lines.  */
61
62 int no_discards;
63
64 #if HAVE_SETMODE
65 /* I/O mode: nonzero only if using binary input/output.  */
66 static int binary_I_O;
67 #endif
68
69 /* Return a string containing the command options with which diff was invoked.
70    Spaces appear between what were separate ARGV-elements.
71    There is a space at the beginning but none at the end.
72    If there were no options, the result is an empty string.
73
74    Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
75    the length of that vector.  */
76
77 static char *
78 option_list (optionvec, count)
79      char **optionvec;  /* Was `vector', but that collides on Alliant.  */
80      int count;
81 {
82   int i;
83   size_t length = 0;
84   char *result;
85
86   for (i = 0; i < count; i++)
87     length += strlen (optionvec[i]) + 1;
88
89   result = xmalloc (length + 1);
90   result[0] = 0;
91
92   for (i = 0; i < count; i++)
93     {
94       strcat (result, " ");
95       strcat (result, optionvec[i]);
96     }
97
98   return result;
99 }
100 \f
101 /* Convert STR to a positive integer, storing the result in *OUT.
102    If STR is not a valid integer, return -1 (otherwise 0). */
103 static int
104 ck_atoi (str, out)
105      char const *str;
106      int *out;
107 {
108   char const *p;
109   for (p = str; *p; p++)
110     if (*p < '0' || *p > '9')
111       return -1;
112
113   *out = atoi (optarg);
114   return 0;
115 }
116 \f
117 /* Keep track of excluded file name patterns.  */
118
119 static char const **exclude;
120 static int exclude_alloc, exclude_count;
121
122 int
123 excluded_filename (f)
124      char const *f;
125 {
126   int i;
127   for (i = 0;  i < exclude_count;  i++)
128     if (fnmatch (exclude[i], f, 0) == 0)
129       return 1;
130   return 0;
131 }
132
133 static void
134 add_exclude (pattern)
135      char const *pattern;
136 {
137   if (exclude_alloc <= exclude_count)
138     exclude = (char const **)
139               (exclude_alloc == 0
140                ? xmalloc ((exclude_alloc = 64) * sizeof (*exclude))
141                : xrealloc (exclude, (exclude_alloc *= 2) * sizeof (*exclude)));
142
143   exclude[exclude_count++] = pattern;
144 }
145
146 static int
147 add_exclude_file (name)
148      char const *name;
149 {
150   struct file_data f;
151   char *p, *q, *lim;
152
153   f.name = optarg;
154   f.desc = (strcmp (optarg, "-") == 0
155             ? STDIN_FILENO
156             : open (optarg, O_RDONLY, 0));
157   if (f.desc < 0 || fstat (f.desc, &f.stat) != 0)
158     return -1;
159
160   sip (&f, 1);
161   slurp (&f);
162
163   for (p = f.buffer, lim = p + f.buffered_chars;  p < lim;  p = q)
164     {
165       q = (char *) memchr (p, '\n', lim - p);
166       if (!q)
167         q = lim;
168       *q++ = 0;
169       add_exclude (p);
170     }
171
172   return close (f.desc);
173 }
174 \f
175 /* The numbers 129- that appear in the fourth element of some entries
176    tell the big switch in `main' how to process those options.  */
177
178 static struct option const longopts[] =
179 {
180   {"ignore-blank-lines", 0, 0, 'B'},
181   {"context", 2, 0, 'C'},
182   {"ifdef", 1, 0, 'D'},
183   {"show-function-line", 1, 0, 'F'},
184   {"speed-large-files", 0, 0, 'H'},
185   {"ignore-matching-lines", 1, 0, 'I'},
186   {"label", 1, 0, 'L'},
187   {"file-label", 1, 0, 'L'},    /* An alias, no longer recommended */
188   {"new-file", 0, 0, 'N'},
189   {"entire-new-file", 0, 0, 'N'},       /* An alias, no longer recommended */
190   {"unidirectional-new-file", 0, 0, 'P'},
191   {"starting-file", 1, 0, 'S'},
192   {"initial-tab", 0, 0, 'T'},
193   {"width", 1, 0, 'W'},
194   {"text", 0, 0, 'a'},
195   {"ascii", 0, 0, 'a'},         /* An alias, no longer recommended */
196   {"ignore-space-change", 0, 0, 'b'},
197   {"minimal", 0, 0, 'd'},
198   {"ed", 0, 0, 'e'},
199   {"forward-ed", 0, 0, 'f'},
200   {"ignore-case", 0, 0, 'i'},
201   {"paginate", 0, 0, 'l'},
202   {"print", 0, 0, 'l'},         /* An alias, no longer recommended */
203   {"rcs", 0, 0, 'n'},
204   {"show-c-function", 0, 0, 'p'},
205   {"brief", 0, 0, 'q'},
206   {"recursive", 0, 0, 'r'},
207   {"report-identical-files", 0, 0, 's'},
208   {"expand-tabs", 0, 0, 't'},
209   {"version", 0, 0, 'v'},
210   {"ignore-all-space", 0, 0, 'w'},
211   {"exclude", 1, 0, 'x'},
212   {"exclude-from", 1, 0, 'X'},
213   {"side-by-side", 0, 0, 'y'},
214   {"unified", 2, 0, 'U'},
215   {"left-column", 0, 0, 129},
216   {"suppress-common-lines", 0, 0, 130},
217   {"sdiff-merge-assist", 0, 0, 131},
218   {"old-line-format", 1, 0, 132},
219   {"new-line-format", 1, 0, 133},
220   {"unchanged-line-format", 1, 0, 134},
221   {"line-format", 1, 0, 135},
222   {"old-group-format", 1, 0, 136},
223   {"new-group-format", 1, 0, 137},
224   {"unchanged-group-format", 1, 0, 138},
225   {"changed-group-format", 1, 0, 139},
226   {"horizon-lines", 1, 0, 140},
227   {"help", 0, 0, 141},
228   {"binary", 0, 0, 142},
229   {0, 0, 0, 0}
230 };
231
232 int
233 main (argc, argv)
234      int argc;
235      char *argv[];
236 {
237   int val;
238   int c;
239   int prev = -1;
240   int width = DEFAULT_WIDTH;
241   int show_c_function = 0;
242
243 #ifdef __FreeBSD__
244   setlocale(LC_ALL, "");
245 #endif
246   /* Do our initializations.  */
247   initialize_main (&argc, &argv);
248   program_name = argv[0];
249   output_style = OUTPUT_NORMAL;
250   context = -1;
251
252   /* Decode the options.  */
253
254   while ((c = getopt_long (argc, argv,
255                            "0123456789abBcC:dD:efF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y",
256                            longopts, 0)) != EOF)
257     {
258       switch (c)
259         {
260           /* All digits combine in decimal to specify the context-size.  */
261         case '1':
262         case '2':
263         case '3':
264         case '4':
265         case '5':
266         case '6':
267         case '7':
268         case '8':
269         case '9':
270         case '0':
271           if (context == -1)
272             context = 0;
273           /* If a context length has already been specified,
274              more digits allowed only if they follow right after the others.
275              Reject two separate runs of digits, or digits after -C.  */
276           else if (prev < '0' || prev > '9')
277             fatal ("context length specified twice");
278
279           context = context * 10 + c - '0';
280           break;
281
282         case 'a':
283           /* Treat all files as text files; never treat as binary.  */
284           always_text_flag = 1;
285           break;
286
287         case 'b':
288           /* Ignore changes in amount of white space.  */
289           ignore_space_change_flag = 1;
290           ignore_some_changes = 1;
291           ignore_some_line_changes = 1;
292           break;
293
294         case 'B':
295           /* Ignore changes affecting only blank lines.  */
296           ignore_blank_lines_flag = 1;
297           ignore_some_changes = 1;
298           break;
299
300         case 'C':               /* +context[=lines] */
301         case 'U':               /* +unified[=lines] */
302           if (optarg)
303             {
304               if (context >= 0)
305                 fatal ("context length specified twice");
306
307               if (ck_atoi (optarg, &context))
308                 fatal ("invalid context length argument");
309             }
310
311           /* Falls through.  */
312         case 'c':
313           /* Make context-style output.  */
314           specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
315           break;
316
317         case 'd':
318           /* Don't discard lines.  This makes things slower (sometimes much
319              slower) but will find a guaranteed minimal set of changes.  */
320           no_discards = 1;
321           break;
322
323         case 'D':
324           /* Make merged #ifdef output.  */
325           specify_style (OUTPUT_IFDEF);
326           {
327             int i, err = 0;
328             static char const C_ifdef_group_formats[] =
329               "#ifndef %s\n%%<#endif /* not %s */\n%c#ifdef %s\n%%>#endif /* %s */\n%c%%=%c#ifndef %s\n%%<#else /* %s */\n%%>#endif /* %s */\n";
330             char *b = xmalloc (sizeof (C_ifdef_group_formats)
331                                + 7 * strlen(optarg) - 14 /* 7*"%s" */
332                                - 8 /* 5*"%%" + 3*"%c" */);
333             sprintf (b, C_ifdef_group_formats,
334                      optarg, optarg, 0,
335                      optarg, optarg, 0, 0,
336                      optarg, optarg, optarg);
337             for (i = 0; i < 4; i++)
338               {
339                 err |= specify_format (&group_format[i], b);
340                 b += strlen (b) + 1;
341               }
342             if (err)
343               error ("conflicting #ifdef formats", 0, 0);
344           }
345           break;
346
347         case 'e':
348           /* Make output that is a valid `ed' script.  */
349           specify_style (OUTPUT_ED);
350           break;
351
352         case 'f':
353           /* Make output that looks vaguely like an `ed' script
354              but has changes in the order they appear in the file.  */
355           specify_style (OUTPUT_FORWARD_ED);
356           break;
357
358         case 'F':
359           /* Show, for each set of changes, the previous line that
360              matches the specified regexp.  Currently affects only
361              context-style output.  */
362           add_regexp (&function_regexp_list, optarg);
363           break;
364
365         case 'h':
366           /* Split the files into chunks of around 1500 lines
367              for faster processing.  Usually does not change the result.
368
369              This currently has no effect.  */
370           break;
371
372         case 'H':
373           /* Turn on heuristics that speed processing of large files
374              with a small density of changes.  */
375           heuristic = 1;
376           break;
377
378         case 'i':
379           /* Ignore changes in case.  */
380           ignore_case_flag = 1;
381           ignore_some_changes = 1;
382           ignore_some_line_changes = 1;
383           break;
384
385         case 'I':
386           /* Ignore changes affecting only lines that match the
387              specified regexp.  */
388           add_regexp (&ignore_regexp_list, optarg);
389           ignore_some_changes = 1;
390           break;
391
392         case 'l':
393           /* Pass the output through `pr' to paginate it.  */
394           paginate_flag = 1;
395 #if !defined(SIGCHLD) && defined(SIGCLD)
396 #define SIGCHLD SIGCLD
397 #endif
398 #ifdef SIGCHLD
399           /* Pagination requires forking and waiting, and
400              System V fork+wait does not work if SIGCHLD is ignored.  */
401           signal (SIGCHLD, SIG_DFL);
402 #endif
403           break;
404
405         case 'L':
406           /* Specify file labels for `-c' output headers.  */
407           if (!file_label[0])
408             file_label[0] = optarg;
409           else if (!file_label[1])
410             file_label[1] = optarg;
411           else
412             fatal ("too many file label options");
413           break;
414
415         case 'n':
416           /* Output RCS-style diffs, like `-f' except that each command
417              specifies the number of lines affected.  */
418           specify_style (OUTPUT_RCS);
419           break;
420
421         case 'N':
422           /* When comparing directories, if a file appears only in one
423              directory, treat it as present but empty in the other.  */
424           entire_new_file_flag = 1;
425           break;
426
427         case 'p':
428           /* Make context-style output and show name of last C function.  */
429           show_c_function = 1;
430           add_regexp (&function_regexp_list, "^[_a-zA-Z$]");
431           break;
432
433         case 'P':
434           /* When comparing directories, if a file appears only in
435              the second directory of the two,
436              treat it as present but empty in the other.  */
437           unidirectional_new_file_flag = 1;
438           break;
439
440         case 'q':
441           no_details_flag = 1;
442           break;
443
444         case 'r':
445           /* When comparing directories,
446              recursively compare any subdirectories found.  */
447           recursive = 1;
448           break;
449
450         case 's':
451           /* Print a message if the files are the same.  */
452           print_file_same_flag = 1;
453           break;
454
455         case 'S':
456           /* When comparing directories, start with the specified
457              file name.  This is used for resuming an aborted comparison.  */
458           dir_start_file = optarg;
459           break;
460
461         case 't':
462           /* Expand tabs to spaces in the output so that it preserves
463              the alignment of the input files.  */
464           tab_expand_flag = 1;
465           break;
466
467         case 'T':
468           /* Use a tab in the output, rather than a space, before the
469              text of an input line, so as to keep the proper alignment
470              in the input line without changing the characters in it.  */
471           tab_align_flag = 1;
472           break;
473
474         case 'u':
475           /* Output the context diff in unidiff format.  */
476           specify_style (OUTPUT_UNIFIED);
477           break;
478
479         case 'v':
480           printf ("diff - GNU diffutils version %s\n", version_string);
481           exit (0);
482
483         case 'w':
484           /* Ignore horizontal white space when comparing lines.  */
485           ignore_all_space_flag = 1;
486           ignore_some_changes = 1;
487           ignore_some_line_changes = 1;
488           break;
489
490         case 'x':
491           add_exclude (optarg);
492           break;
493
494         case 'X':
495           if (add_exclude_file (optarg) != 0)
496             pfatal_with_name (optarg);
497           break;
498
499         case 'y':
500           /* Use side-by-side (sdiff-style) columnar output. */
501           specify_style (OUTPUT_SDIFF);
502           break;
503
504         case 'W':
505           /* Set the line width for OUTPUT_SDIFF.  */
506           if (ck_atoi (optarg, &width) || width <= 0)
507             fatal ("column width must be a positive integer");
508           break;
509
510         case 129:
511           sdiff_left_only = 1;
512           break;
513
514         case 130:
515           sdiff_skip_common_lines = 1;
516           break;
517
518         case 131:
519           /* sdiff-style columns output. */
520           specify_style (OUTPUT_SDIFF);
521           sdiff_help_sdiff = 1;
522           break;
523
524         case 132:
525         case 133:
526         case 134:
527           specify_style (OUTPUT_IFDEF);
528           if (specify_format (&line_format[c - 132], optarg) != 0)
529             error ("conflicting line format", 0, 0);
530           break;
531
532         case 135:
533           specify_style (OUTPUT_IFDEF);
534           {
535             int i, err = 0;
536             for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++)
537               err |= specify_format (&line_format[i], optarg);
538             if (err)
539               error ("conflicting line format", 0, 0);
540           }
541           break;
542
543         case 136:
544         case 137:
545         case 138:
546         case 139:
547           specify_style (OUTPUT_IFDEF);
548           if (specify_format (&group_format[c - 136], optarg) != 0)
549             error ("conflicting group format", 0, 0);
550           break;
551
552         case 140:
553           if (ck_atoi (optarg, &horizon_lines) || horizon_lines < 0)
554             fatal ("horizon must be a nonnegative integer");
555           break;
556
557         case 141:
558           usage ();
559           check_stdout ();
560           exit (0);
561
562         case 142:
563           /* Use binary I/O when reading and writing data.
564              On Posix hosts, this has no effect.  */
565 #if HAVE_SETMODE
566           binary_I_O = 1;
567           setmode (STDOUT_FILENO, O_BINARY);
568 #endif
569           break;
570
571         default:
572           try_help (0);
573         }
574       prev = c;
575     }
576
577   if (argc - optind != 2)
578     try_help (argc - optind < 2 ? "missing operand" : "extra operand");
579
580
581   {
582     /*
583      *  We maximize first the half line width, and then the gutter width,
584      *  according to the following constraints:
585      *  1.  Two half lines plus a gutter must fit in a line.
586      *  2.  If the half line width is nonzero:
587      *      a.  The gutter width is at least GUTTER_WIDTH_MINIMUM.
588      *      b.  If tabs are not expanded to spaces,
589      *          a half line plus a gutter is an integral number of tabs,
590      *          so that tabs in the right column line up.
591      */
592     int t = tab_expand_flag ? 1 : TAB_WIDTH;
593     int off = (width + t + GUTTER_WIDTH_MINIMUM) / (2*t)  *  t;
594     sdiff_half_width = max (0, min (off - GUTTER_WIDTH_MINIMUM, width - off)),
595     sdiff_column2_offset = sdiff_half_width ? off : width;
596   }
597
598   if (show_c_function && output_style != OUTPUT_UNIFIED)
599     specify_style (OUTPUT_CONTEXT);
600
601   if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED)
602     context = 0;
603   else if (context == -1)
604     /* Default amount of context for -c.  */
605     context = 3;
606
607   if (output_style == OUTPUT_IFDEF)
608     {
609       /* Format arrays are char *, not char const *,
610          because integer formats are temporarily modified.
611          But it is safe to assign a constant like "%=" to a format array,
612          since "%=" does not format any integers.  */
613       int i;
614       for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++)
615         if (!line_format[i])
616           line_format[i] = "%l\n";
617       if (!group_format[OLD])
618         group_format[OLD]
619           = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%<";
620       if (!group_format[NEW])
621         group_format[NEW]
622           = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%>";
623       if (!group_format[UNCHANGED])
624         group_format[UNCHANGED] = "%=";
625       if (!group_format[CHANGED])
626         group_format[CHANGED] = concat (group_format[OLD],
627                                         group_format[NEW], "");
628     }
629
630   no_diff_means_no_output =
631     (output_style == OUTPUT_IFDEF ?
632       (!*group_format[UNCHANGED]
633        || (strcmp (group_format[UNCHANGED], "%=") == 0
634            && !*line_format[UNCHANGED]))
635      : output_style == OUTPUT_SDIFF ? sdiff_skip_common_lines : 1);
636
637   switch_string = option_list (argv + 1, optind - 1);
638
639   val = compare_files (0, argv[optind], 0, argv[optind + 1], 0);
640
641   /* Print any messages that were saved up for last.  */
642   print_message_queue ();
643
644   check_stdout ();
645   exit (val);
646   return val;
647 }
648
649 /* Add the compiled form of regexp PATTERN to REGLIST.  */
650
651 static void
652 add_regexp (reglist, pattern)
653      struct regexp_list **reglist;
654      char const *pattern;
655 {
656   struct regexp_list *r;
657   char const *m;
658
659   r = (struct regexp_list *) xmalloc (sizeof (*r));
660   bzero (r, sizeof (*r));
661   r->buf.fastmap = xmalloc (256);
662   m = re_compile_pattern (pattern, strlen (pattern), &r->buf);
663   if (m != 0)
664     error ("%s: %s", pattern, m);
665
666   /* Add to the start of the list, since it's easier than the end.  */
667   r->next = *reglist;
668   *reglist = r;
669 }
670
671 static void
672 try_help (reason)
673      char const *reason;
674 {
675   if (reason)
676     error ("%s", reason, 0);
677   error ("Try `%s --help' for more information.", program_name, 0);
678   exit (2);
679 }
680
681 static void
682 check_stdout ()
683 {
684   if (ferror (stdout) || fclose (stdout) != 0)
685     fatal ("write error");
686 }
687
688 static char const * const option_help[] = {
689 "-i  --ignore-case  Consider upper- and lower-case to be the same.",
690 "-w  --ignore-all-space  Ignore all white space.",
691 "-b  --ignore-space-change  Ignore changes in the amount of white space.",
692 "-B  --ignore-blank-lines  Ignore changes whose lines are all blank.",
693 "-I RE  --ignore-matching-lines=RE  Ignore changes whose lines all match RE.",
694 #if HAVE_SETMODE
695 "--binary  Read and write data in binary mode.",
696 #endif
697 "-a  --text  Treat all files as text.\n",
698 "-c  -C NUM  --context[=NUM]  Output NUM (default 2) lines of copied context.",
699 "-u  -U NUM  --unified[=NUM]  Output NUM (default 2) lines of unified context.",
700 "  -NUM  Use NUM context lines.",
701 "  -L LABEL  --label LABEL  Use LABEL instead of file name.",
702 "  -p  --show-c-function  Show which C function each change is in.",
703 "  -F RE  --show-function-line=RE  Show the most recent line matching RE.",
704 "-q  --brief  Output only whether files differ.",
705 "-e  --ed  Output an ed script.",
706 "-n  --rcs  Output an RCS format diff.",
707 "-y  --side-by-side  Output in two columns.",
708 "  -w NUM  --width=NUM  Output at most NUM (default 130) characters per line.",
709 "  --left-column  Output only the left column of common lines.",
710 "  --suppress-common-lines  Do not output common lines.",
711 "-DNAME  --ifdef=NAME  Output merged file to show `#ifdef NAME' diffs.",
712 "--GTYPE-group-format=GFMT  Similar, but format GTYPE input groups with GFMT.",
713 "--line-format=LFMT  Similar, but format all input lines with LFMT.",
714 "--LTYPE-line-format=LFMT  Similar, but format LTYPE input lines with LFMT.",
715 "  LTYPE is `old', `new', or `unchanged'.  GTYPE is LTYPE or `changed'.",
716 "  GFMT may contain:",
717 "    %<  lines from FILE1",
718 "    %>  lines from FILE2",
719 "    %=  lines common to FILE1 and FILE2",
720 "    %[-][WIDTH][.[PREC]]{doxX}LETTER  printf-style spec for LETTER",
721 "      LETTERs are as follows for new group, lower case for old group:",
722 "        F  first line number",
723 "        L  last line number",
724 "        N  number of lines = L-F+1",
725 "        E  F-1",
726 "        M  L+1",
727 "  LFMT may contain:",
728 "    %L  contents of line",
729 "    %l  contents of line, excluding any trailing newline",
730 "    %[-][WIDTH][.[PREC]]{doxX}n  printf-style spec for input line number",
731 "  Either GFMT or LFMT may contain:",
732 "    %%  %",
733 "    %c'C'  the single character C",
734 "    %c'\\OOO'  the character with octal code OOO\n",
735 "-l  --paginate  Pass the output through `pr' to paginate it.",
736 "-t  --expand-tabs  Expand tabs to spaces in output.",
737 "-T  --initial-tab  Make tabs line up by prepending a tab.\n",
738 "-r  --recursive  Recursively compare any subdirectories found.",
739 "-N  --new-file  Treat absent files as empty.",
740 "-P  --unidirectional-new-file  Treat absent first files as empty.",
741 "-s  --report-identical-files  Report when two files are the same.",
742 "-x PAT  --exclude=PAT  Exclude files that match PAT.",
743 "-X FILE  --exclude-from=FILE  Exclude files that match any pattern in FILE.",
744 "-S FILE  --starting-file=FILE  Start with FILE when comparing directories.\n",
745 "--horizon-lines=NUM  Keep NUM lines of the common prefix and suffix.",
746 "-d  --minimal  Try hard to find a smaller set of changes.",
747 "-H  --speed-large-files  Assume large files and many scattered small changes.\n",
748 "-v  --version  Output version info.",
749 "--help  Output this help.",
750 0
751 };
752
753 static void
754 usage ()
755 {
756   char const * const *p;
757
758   printf ("Usage: %s [OPTION]... FILE1 FILE2\n\n", program_name);
759   for (p = option_help;  *p;  p++)
760     printf ("  %s\n", *p);
761   printf ("\nIf FILE1 or FILE2 is `-', read standard input.\n");
762 }
763
764 static int
765 specify_format (var, value)
766      char **var;
767      char *value;
768 {
769   int err = *var ? strcmp (*var, value) : 0;
770   *var = value;
771   return err;
772 }
773
774 static void
775 specify_style (style)
776      enum output_style style;
777 {
778   if (output_style != OUTPUT_NORMAL
779       && output_style != style)
780     error ("conflicting specifications of output style", 0, 0);
781   output_style = style;
782 }
783 \f
784 static char const *
785 filetype (st)
786      struct stat const *st;
787 {
788   /* See Posix.2 section 4.17.6.1.1 and Table 5-1 for these formats.
789      To keep diagnostics grammatical, the returned string must start
790      with a consonant.  */
791
792   if (S_ISREG (st->st_mode))
793     {
794       if (st->st_size == 0)
795         return "regular empty file";
796       /* Posix.2 section 5.14.2 seems to suggest that we must read the file
797          and guess whether it's C, Fortran, etc., but this is somewhat useless
798          and doesn't reflect historical practice.  We're allowed to guess
799          wrong, so we don't bother to read the file.  */
800       return "regular file";
801     }
802   if (S_ISDIR (st->st_mode)) return "directory";
803
804   /* other Posix.1 file types */
805 #ifdef S_ISBLK
806   if (S_ISBLK (st->st_mode)) return "block special file";
807 #endif
808 #ifdef S_ISCHR
809   if (S_ISCHR (st->st_mode)) return "character special file";
810 #endif
811 #ifdef S_ISFIFO
812   if (S_ISFIFO (st->st_mode)) return "fifo";
813 #endif
814
815   /* other Posix.1b file types */
816 #ifdef S_TYPEISMQ
817   if (S_TYPEISMQ (st)) return "message queue";
818 #endif
819 #ifdef S_TYPEISSEM
820   if (S_TYPEISSEM (st)) return "semaphore";
821 #endif
822 #ifdef S_TYPEISSHM
823   if (S_TYPEISSHM (st)) return "shared memory object";
824 #endif
825
826   /* other popular file types */
827   /* S_ISLNK is impossible with `fstat' and `stat'.  */
828 #ifdef S_ISSOCK
829   if (S_ISSOCK (st->st_mode)) return "socket";
830 #endif
831
832   return "weird file";
833 }
834 \f
835 /* Compare two files (or dirs) with specified names
836    DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion.
837    (if DIR0 is 0, then the name is just NAME0, etc.)
838    This is self-contained; it opens the files and closes them.
839
840    Value is 0 if files are the same, 1 if different,
841    2 if there is a problem opening them.  */
842
843 static int
844 compare_files (dir0, name0, dir1, name1, depth)
845      char const *dir0, *dir1;
846      char const *name0, *name1;
847      int depth;
848 {
849   struct file_data inf[2];
850   register int i;
851   int val;
852   int same_files;
853   int failed = 0;
854   char *free0 = 0, *free1 = 0;
855
856   /* If this is directory comparison, perhaps we have a file
857      that exists only in one of the directories.
858      If so, just print a message to that effect.  */
859
860   if (! ((name0 != 0 && name1 != 0)
861          || (unidirectional_new_file_flag && name1 != 0)
862          || entire_new_file_flag))
863     {
864       char const *name = name0 == 0 ? name1 : name0;
865       char const *dir = name0 == 0 ? dir1 : dir0;
866       message ("Only in %s: %s\n", dir, name);
867       /* Return 1 so that diff_dirs will return 1 ("some files differ").  */
868       return 1;
869     }
870
871   bzero (inf, sizeof (inf));
872
873   /* Mark any nonexistent file with -1 in the desc field.  */
874   /* Mark unopened files (e.g. directories) with -2. */
875
876   inf[0].desc = name0 == 0 ? -1 : -2;
877   inf[1].desc = name1 == 0 ? -1 : -2;
878
879   /* Now record the full name of each file, including nonexistent ones.  */
880
881   if (name0 == 0)
882     name0 = name1;
883   if (name1 == 0)
884     name1 = name0;
885
886   inf[0].name = dir0 == 0 ? name0 : (free0 = dir_file_pathname (dir0, name0));
887   inf[1].name = dir1 == 0 ? name1 : (free1 = dir_file_pathname (dir1, name1));
888
889   /* Stat the files.  Record whether they are directories.  */
890
891   for (i = 0; i <= 1; i++)
892     {
893       if (inf[i].desc != -1)
894         {
895           int stat_result;
896
897           if (i && filename_cmp (inf[i].name, inf[0].name) == 0)
898             {
899               inf[i].stat = inf[0].stat;
900               stat_result = 0;
901             }
902           else if (strcmp (inf[i].name, "-") == 0)
903             {
904               inf[i].desc = STDIN_FILENO;
905               stat_result = fstat (STDIN_FILENO, &inf[i].stat);
906               if (stat_result == 0 && S_ISREG (inf[i].stat.st_mode))
907                 {
908                   off_t pos = lseek (STDIN_FILENO, (off_t) 0, SEEK_CUR);
909                   if (pos == -1)
910                     stat_result = -1;
911                   else
912                     {
913                       if (pos <= inf[i].stat.st_size)
914                         inf[i].stat.st_size -= pos;
915                       else
916                         inf[i].stat.st_size = 0;
917                       /* Posix.2 4.17.6.1.4 requires current time for stdin.  */
918                       time (&inf[i].stat.st_mtime);
919                     }
920                 }
921             }
922           else
923             stat_result = stat (inf[i].name, &inf[i].stat);
924
925           if (stat_result != 0)
926             {
927               perror_with_name (inf[i].name);
928               failed = 1;
929             }
930           else
931             {
932               inf[i].dir_p = S_ISDIR (inf[i].stat.st_mode) && inf[i].desc != 0;
933               if (inf[1 - i].desc == -1)
934                 {
935                   inf[1 - i].dir_p = inf[i].dir_p;
936                   inf[1 - i].stat.st_mode = inf[i].stat.st_mode;
937                 }
938             }
939         }
940     }
941
942   if (! failed && depth == 0 && inf[0].dir_p != inf[1].dir_p)
943     {
944       /* If one is a directory, and it was specified in the command line,
945          use the file in that dir with the other file's basename.  */
946
947       int fnm_arg = inf[0].dir_p;
948       int dir_arg = 1 - fnm_arg;
949       char const *fnm = inf[fnm_arg].name;
950       char const *dir = inf[dir_arg].name;
951       char const *p = filename_lastdirchar (fnm);
952       char const *filename = inf[dir_arg].name
953         = dir_file_pathname (dir, p ? p + 1 : fnm);
954
955       if (strcmp (fnm, "-") == 0)
956         fatal ("can't compare - to a directory");
957
958       if (stat (filename, &inf[dir_arg].stat) != 0)
959         {
960           perror_with_name (filename);
961           failed = 1;
962         }
963       else
964         inf[dir_arg].dir_p = S_ISDIR (inf[dir_arg].stat.st_mode);
965     }
966
967   if (failed)
968     {
969
970       /* If either file should exist but does not, return 2.  */
971
972       val = 2;
973
974     }
975   else if ((same_files = inf[0].desc != -1 && inf[1].desc != -1
976                          && 0 < same_file (&inf[0].stat, &inf[1].stat))
977            && no_diff_means_no_output)
978     {
979       /* The two named files are actually the same physical file.
980          We know they are identical without actually reading them.  */
981
982       val = 0;
983     }
984   else if (inf[0].dir_p & inf[1].dir_p)
985     {
986       if (output_style == OUTPUT_IFDEF)
987         fatal ("-D option not supported with directories");
988
989       /* If both are directories, compare the files in them.  */
990
991       if (depth > 0 && !recursive)
992         {
993           /* But don't compare dir contents one level down
994              unless -r was specified.  */
995           message ("Common subdirectories: %s and %s\n",
996                    inf[0].name, inf[1].name);
997           val = 0;
998         }
999       else
1000         {
1001           val = diff_dirs (inf, compare_files, depth);
1002         }
1003
1004     }
1005   else if ((inf[0].dir_p | inf[1].dir_p)
1006            || (depth > 0
1007                && (! S_ISREG (inf[0].stat.st_mode)
1008                    || ! S_ISREG (inf[1].stat.st_mode))))
1009     {
1010       /* Perhaps we have a subdirectory that exists only in one directory.
1011          If so, just print a message to that effect.  */
1012
1013       if (inf[0].desc == -1 || inf[1].desc == -1)
1014         {
1015           if ((inf[0].dir_p | inf[1].dir_p)
1016               && recursive
1017               && (entire_new_file_flag
1018                   || (unidirectional_new_file_flag && inf[0].desc == -1)))
1019             val = diff_dirs (inf, compare_files, depth);
1020           else
1021             {
1022               char const *dir = (inf[0].desc == -1) ? dir1 : dir0;
1023               /* See Posix.2 section 4.17.6.1.1 for this format.  */
1024               message ("Only in %s: %s\n", dir, name0);
1025               val = 1;
1026             }
1027         }
1028       else
1029         {
1030           /* We have two files that are not to be compared.  */
1031
1032           /* See Posix.2 section 4.17.6.1.1 for this format.  */
1033           message5 ("File %s is a %s while file %s is a %s\n",
1034                     inf[0].name, filetype (&inf[0].stat),
1035                     inf[1].name, filetype (&inf[1].stat));
1036
1037           /* This is a difference.  */
1038           val = 1;
1039         }
1040     }
1041   else if ((no_details_flag & ~ignore_some_changes)
1042            && inf[0].stat.st_size != inf[1].stat.st_size
1043            && (inf[0].desc == -1 || S_ISREG (inf[0].stat.st_mode))
1044            && (inf[1].desc == -1 || S_ISREG (inf[1].stat.st_mode)))
1045     {
1046       message ("Files %s and %s differ\n", inf[0].name, inf[1].name);
1047       val = 1;
1048     }
1049   else
1050     {
1051       /* Both exist and neither is a directory.  */
1052
1053       /* Open the files and record their descriptors.  */
1054
1055       if (inf[0].desc == -2)
1056         if ((inf[0].desc = open (inf[0].name, O_RDONLY, 0)) < 0)
1057           {
1058             perror_with_name (inf[0].name);
1059             failed = 1;
1060           }
1061       if (inf[1].desc == -2)
1062         if (same_files)
1063           inf[1].desc = inf[0].desc;
1064         else if ((inf[1].desc = open (inf[1].name, O_RDONLY, 0)) < 0)
1065           {
1066             perror_with_name (inf[1].name);
1067             failed = 1;
1068           }
1069
1070 #if HAVE_SETMODE
1071       if (binary_I_O)
1072         for (i = 0; i <= 1; i++)
1073           if (0 <= inf[i].desc)
1074             setmode (inf[i].desc, O_BINARY);
1075 #endif
1076
1077       /* Compare the files, if no error was found.  */
1078
1079       val = failed ? 2 : diff_2_files (inf, depth);
1080
1081       /* Close the file descriptors.  */
1082
1083       if (inf[0].desc >= 0 && close (inf[0].desc) != 0)
1084         {
1085           perror_with_name (inf[0].name);
1086           val = 2;
1087         }
1088       if (inf[1].desc >= 0 && inf[0].desc != inf[1].desc
1089           && close (inf[1].desc) != 0)
1090         {
1091           perror_with_name (inf[1].name);
1092           val = 2;
1093         }
1094     }
1095
1096   /* Now the comparison has been done, if no error prevented it,
1097      and VAL is the value this function will return.  */
1098
1099   if (val == 0 && !inf[0].dir_p)
1100     {
1101       if (print_file_same_flag)
1102         message ("Files %s and %s are identical\n",
1103                  inf[0].name, inf[1].name);
1104     }
1105   else
1106     fflush (stdout);
1107
1108   if (free0)
1109     free (free0);
1110   if (free1)
1111     free (free1);
1112
1113   return val;
1114 }