home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / gnu / diff-2.3 / diff.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-04-23  |  23.8 KB  |  928 lines

  1. /* GNU DIFF main routine.
  2.    Copyright (C) 1988, 1989, 1992 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 "getopt.h"
  26. #include "fnmatch.h"
  27.  
  28. #ifndef DEFAULT_WIDTH
  29. #define DEFAULT_WIDTH 130
  30. #endif
  31.  
  32. #ifndef GUTTER_WIDTH_MINIMUM
  33. #define GUTTER_WIDTH_MINIMUM 3
  34. #endif
  35.  
  36. int diff_dirs ();
  37. int diff_2_files ();
  38.  
  39. static int compare_files ();
  40. static int specify_format ();
  41. static void add_regexp();
  42. static void specify_style ();
  43. static void usage ();
  44.  
  45. /* Nonzero for -r: if comparing two directories,
  46.    compare their common subdirectories recursively.  */
  47.  
  48. static int recursive;
  49.  
  50. /* For debugging: don't do discard_confusing_lines.  */
  51.  
  52. int no_discards;
  53.  
  54. /* Return a string containing the command options with which diff was invoked.
  55.    Spaces appear between what were separate ARGV-elements.
  56.    There is a space at the beginning but none at the end.
  57.    If there were no options, the result is an empty string.
  58.  
  59.    Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
  60.    the length of that vector.  */
  61.  
  62. static char *
  63. option_list (optionvec, count)
  64.      char **optionvec;  /* Was `vector', but that collides on Alliant.  */
  65.      int count;
  66. {
  67.   int i;
  68.   int length = 0;
  69.   char *result;
  70.  
  71.   for (i = 0; i < count; i++)
  72.     length += strlen (optionvec[i]) + 1;
  73.  
  74.   result = (char *) xmalloc (length + 1);
  75.   result[0] = 0;
  76.  
  77.   for (i = 0; i < count; i++)
  78.     {
  79.       strcat (result, " ");
  80.       strcat (result, optionvec[i]);
  81.     }
  82.  
  83.   return result;
  84. }
  85.  
  86. /* Convert STR to a positive integer, storing the result in *OUT. 
  87.    If STR is not a valid integer, return -1 (otherwise 0). */
  88. static int
  89. ck_atoi (str, out)
  90.      char *str;
  91.      int *out;
  92. {
  93.   char *p;
  94.   for (p = str; *p; p++)
  95.     if (*p < '0' || *p > '9')
  96.       return -1;
  97.  
  98.   *out = atoi (optarg);
  99.   return 0;
  100. }
  101.  
  102. /* Keep track of excluded file name patterns.  */
  103.  
  104. static const char **exclude;
  105. static int exclude_alloc, exclude_count;
  106.  
  107. int
  108. excluded_filename (f)
  109.      const char *f;
  110. {
  111.   int i;
  112.   for (i = 0;  i < exclude_count;  i++)
  113.     if (fnmatch (exclude[i], f, 0) == 0)
  114.       return 1;
  115.   return 0;
  116. }
  117.  
  118. static void
  119. add_exclude (pattern)
  120.      const char *pattern;
  121. {
  122.   if (exclude_alloc <= exclude_count)
  123.     exclude = (const char **)
  124.           (exclude_alloc == 0
  125.            ? xmalloc ((exclude_alloc = 64) * sizeof (*exclude))
  126.            : xrealloc (exclude, (exclude_alloc *= 2) * sizeof (*exclude)));
  127.  
  128.   exclude[exclude_count++] = pattern;
  129. }
  130.  
  131. static int
  132. add_exclude_file (name)
  133.      const char *name;
  134. {
  135.   struct file_data f;
  136.   char *p, *q, *lim;
  137.  
  138.   f.name = optarg;
  139.   f.desc = strcmp (optarg, "-") == 0 ? 0 : open (optarg, O_RDONLY, 0);
  140.   if (f.desc < 0 || fstat (f.desc, &f.stat) != 0)
  141.     return -1;
  142.  
  143.   sip (&f, 1);
  144.   slurp (&f);
  145.  
  146.   for (p = f.buffer, lim = p + f.buffered_chars;  p < lim;  p = q)
  147.     {
  148.       q = memchr (p, '\n', lim - p);
  149.       if (!q)
  150.     q = lim;
  151.       *q++ = 0;
  152.       add_exclude (p);
  153.     }
  154.  
  155.   return close (f.desc);
  156. }
  157.  
  158. /* The numbers 129- that appear in the fourth element of some entries
  159.    tell the big switch in `main' how to process those options.  */
  160.  
  161. static struct option longopts[] =
  162. {
  163.   {"ignore-blank-lines", 0, 0, 'B'},
  164.   {"context", 2, 0, 'C'},
  165.   {"ifdef", 1, 0, 'D'},
  166.   {"show-function-line", 1, 0, 'F'},
  167.   {"speed-large-files", 0, 0, 'H'},
  168.   {"ignore-matching-lines", 1, 0, 'I'},
  169.   {"label", 1, 0, 'L'},
  170.   {"file-label", 1, 0, 'L'},    /* An alias, no longer recommended */
  171.   {"new-file", 0, 0, 'N'},
  172.   {"entire-new-file", 0, 0, 'N'},    /* An alias, no longer recommended */
  173.   {"unidirectional-new-file", 0, 0, 'P'},
  174.   {"starting-file", 1, 0, 'S'},
  175.   {"initial-tab", 0, 0, 'T'},
  176.   {"width", 1, 0, 'W'},
  177.   {"text", 0, 0, 'a'},
  178.   {"ascii", 0, 0, 'a'},        /* An alias, no longer recommended */
  179.   {"ignore-space-change", 0, 0, 'b'},
  180.   {"minimal", 0, 0, 'd'},
  181.   {"ed", 0, 0, 'e'},
  182.   {"forward-ed", 0, 0, 'f'},
  183.   {"ignore-case", 0, 0, 'i'},
  184.   {"paginate", 0, 0, 'l'},
  185.   {"print", 0, 0, 'l'},        /* An alias, no longer recommended */
  186.   {"rcs", 0, 0, 'n'},
  187.   {"show-c-function", 0, 0, 'p'},
  188.   {"binary", 0, 0, 'q'},    /* An alias, no longer recommended */
  189.   {"brief", 0, 0, 'q'},
  190.   {"recursive", 0, 0, 'r'},
  191.   {"report-identical-files", 0, 0, 's'},
  192.   {"expand-tabs", 0, 0, 't'},
  193.   {"version", 0, 0, 'v'},
  194.   {"ignore-all-space", 0, 0, 'w'},
  195.   {"exclude", 1, 0, 'x'},
  196.   {"exclude-from", 1, 0, 'X'},
  197.   {"side-by-side", 0, 0, 'y'},
  198.   {"unified", 2, 0, 'U'},
  199.   {"left-column", 0, 0, 129},
  200.   {"suppress-common-lines", 0, 0, 130},
  201.   {"sdiff-merge-assist", 0, 0, 131},
  202.   {"old-line-format", 1, 0, 132},
  203.   {"new-line-format", 1, 0, 133},
  204.   {"unchanged-line-format", 1, 0, 134},
  205.   {"old-group-format", 1, 0, 135},
  206.   {"new-group-format", 1, 0, 136},
  207.   {"unchanged-group-format", 1, 0, 137},
  208.   {"changed-group-format", 1, 0, 138},
  209.   {"horizon-lines", 1, 0, 139},
  210.   {0, 0, 0, 0}
  211. };
  212.  
  213. int
  214. main (argc, argv)
  215.      int argc;
  216.      char *argv[];
  217. {
  218.   int val;
  219.   int c;
  220.   int prev = -1;
  221.   extern char *version_string;
  222.   int width = DEFAULT_WIDTH;
  223.  
  224.   program = argv[0];
  225.  
  226.   /* Do our initializations. */
  227.   output_style = OUTPUT_NORMAL;
  228.   always_text_flag = FALSE;
  229.   ignore_space_change_flag = FALSE;
  230.   ignore_all_space_flag = FALSE;
  231.   length_varies = FALSE;
  232.   ignore_case_flag = FALSE;
  233.   ignore_blank_lines_flag = FALSE;
  234.   ignore_regexp_list = NULL;
  235.   function_regexp_list = NULL;
  236.   print_file_same_flag = FALSE;
  237.   entire_new_file_flag = FALSE;
  238.   unidirectional_new_file_flag = FALSE;
  239.   no_details_flag = FALSE;
  240.   context = -1;
  241.   line_end_char = '\n';
  242.   tab_align_flag = FALSE;
  243.   tab_expand_flag = FALSE;
  244.   recursive = FALSE;
  245.   paginate_flag = FALSE;
  246.   heuristic = FALSE;
  247.   dir_start_file = NULL;
  248.   msg_chain = NULL;
  249.   msg_chain_end = NULL;
  250.   no_discards = 0;
  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, (int *)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 whitespace.  */
  289.       ignore_space_change_flag = 1;
  290.       length_varies = 1;
  291.       break;
  292.  
  293.     case 'B':
  294.       /* Ignore changes affecting only blank lines.  */
  295.       ignore_blank_lines_flag = 1;
  296.       break;
  297.  
  298.     case 'C':        /* +context[=lines] */
  299.     case 'U':        /* +unified[=lines] */
  300.       if (optarg)
  301.         {
  302.           if (context >= 0)
  303.         fatal ("context length specified twice");
  304.  
  305.           if (ck_atoi (optarg, &context))
  306.         fatal ("invalid context length argument");
  307.         }
  308.  
  309.       /* Falls through.  */
  310.     case 'c':
  311.       /* Make context-style output.  */
  312.       specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
  313.       break;
  314.  
  315.     case 'd':
  316.       /* Don't discard lines.  This makes things slower (sometimes much
  317.          slower) but will find a guaranteed minimal set of changes.  */
  318.       no_discards = 1;
  319.       break;
  320.  
  321.     case 'D':
  322.       /* Make merged #ifdef output.  */
  323.       specify_style (OUTPUT_IFDEF);
  324.       {
  325.         int i, err = 0;
  326.         static const char C_ifdef_group_formats[] =
  327.           "#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";
  328.         char *b = xmalloc (sizeof (C_ifdef_group_formats)
  329.                    + 7 * strlen(optarg) - 14 /* 7*"%s" */
  330.                    - 8 /* 5*"%%" + 3*"%c" */);
  331.         sprintf (b, C_ifdef_group_formats,
  332.              optarg, optarg, 0,
  333.              optarg, optarg, 0, 0,
  334.              optarg, optarg, optarg);
  335.         for (i = 0; i < 4; i++)
  336.           {
  337.         err |= specify_format (&group_format[i], b);
  338.         b += strlen (b) + 1;
  339.           }
  340.         if (err)
  341.           error ("conflicting #ifdef formats", 0, 0);
  342.       }
  343.       break;
  344.  
  345.     case 'e':
  346.       /* Make output that is a valid `ed' script.  */
  347.       specify_style (OUTPUT_ED);
  348.       break;
  349.  
  350.     case 'f':
  351.       /* Make output that looks vaguely like an `ed' script
  352.          but has changes in the order they appear in the file.  */
  353.       specify_style (OUTPUT_FORWARD_ED);
  354.       break;
  355.  
  356.     case 'F':
  357.       /* Show, for each set of changes, the previous line that
  358.          matches the specified regexp.  Currently affects only
  359.          context-style output.  */
  360.       add_regexp (&function_regexp_list, optarg);
  361.       break;
  362.  
  363.     case 'h':
  364.       /* Split the files into chunks of around 1500 lines
  365.          for faster processing.  Usually does not change the result.
  366.  
  367.          This currently has no effect.  */
  368.       break;
  369.  
  370.     case 'H':
  371.       /* Turn on heuristics that speed processing of large files
  372.          with a small density of changes.  */
  373.       heuristic = 1;
  374.       break;
  375.  
  376.     case 'i':
  377.       /* Ignore changes in case.  */
  378.       ignore_case_flag = 1;
  379.       break;
  380.  
  381.     case 'I':
  382.       /* Ignore changes affecting only lines that match the
  383.          specified regexp.  */
  384.       add_regexp (&ignore_regexp_list, optarg);
  385.       break;
  386.  
  387.     case 'l':
  388.       /* Pass the output through `pr' to paginate it.  */
  389.       paginate_flag = 1;
  390.       break;
  391.  
  392.     case 'L':
  393.       /* Specify file labels for `-c' output headers.  */
  394.       if (!file_label[0])
  395.         file_label[0] = optarg;
  396.       else if (!file_label[1])
  397.         file_label[1] = optarg;
  398.       else
  399.         fatal ("too many file label options");
  400.       break;
  401.       
  402.     case 'n':
  403.       /* Output RCS-style diffs, like `-f' except that each command
  404.          specifies the number of lines affected.  */
  405.       specify_style (OUTPUT_RCS);
  406.       break;
  407.  
  408.     case 'N':
  409.       /* When comparing directories, if a file appears only in one
  410.          directory, treat it as present but empty in the other.  */
  411.       entire_new_file_flag = 1;
  412.       break;
  413.  
  414.     case 'p':
  415.       /* Make context-style output and show name of last C function.  */
  416.       specify_style (OUTPUT_CONTEXT);
  417.       add_regexp (&function_regexp_list, "^[_a-zA-Z$]");
  418.       break;
  419.  
  420.     case 'P':
  421.       /* When comparing directories, if a file appears only in
  422.          the second directory of the two,
  423.          treat it as present but empty in the other.  */
  424.       unidirectional_new_file_flag = 1;
  425.       break;
  426.  
  427.     case 'q':
  428.       no_details_flag = 1;
  429.       break;
  430.  
  431.     case 'r':
  432.       /* When comparing directories, 
  433.          recursively compare any subdirectories found.  */
  434.       recursive = 1;
  435.       break;
  436.  
  437.     case 's':
  438.       /* Print a message if the files are the same.  */
  439.       print_file_same_flag = 1;
  440.       break;
  441.  
  442.     case 'S':
  443.       /* When comparing directories, start with the specified
  444.          file name.  This is used for resuming an aborted comparison.  */
  445.       dir_start_file = optarg;
  446.       break;
  447.  
  448.     case 't':
  449.       /* Expand tabs to spaces in the output so that it preserves
  450.          the alignment of the input files.  */
  451.       tab_expand_flag = 1;
  452.       break;
  453.  
  454.     case 'T':
  455.       /* Use a tab in the output, rather than a space, before the
  456.          text of an input line, so as to keep the proper alignment
  457.          in the input line without changing the characters in it.  */
  458.       tab_align_flag = 1;
  459.       break;
  460.  
  461.     case 'u':
  462.       /* Output the context diff in unidiff format.  */
  463.       specify_style (OUTPUT_UNIFIED);
  464.       break;
  465.  
  466.     case 'v':
  467.       fprintf (stderr, "GNU diff version %s\n", version_string);
  468.       break;
  469.  
  470.     case 'w':
  471.       /* Ignore horizontal whitespace when comparing lines.  */
  472.       ignore_all_space_flag = 1;
  473.       length_varies = 1;
  474.       break;
  475.  
  476.     case 'x':
  477.       add_exclude (optarg);
  478.       break;
  479.  
  480.     case 'X':
  481.       if (add_exclude_file (optarg) != 0)
  482.         pfatal_with_name (optarg);
  483.       break;
  484.  
  485.     case 'y':
  486.       /* Use side-by-side (sdiff-style) columnar output. */
  487.       specify_style (OUTPUT_SDIFF);
  488.       break;
  489.  
  490.     case 'W':
  491.       /* Set the line width for OUTPUT_SDIFF.  */
  492.       if (ck_atoi (optarg, &width) || width <= 0)
  493.         fatal ("column width must be a positive integer");
  494.       break;
  495.       
  496.     case 129:
  497.       sdiff_left_only = 1;
  498.       break;
  499.       
  500.     case 130:
  501.       sdiff_skip_common_lines = 1;
  502.       break;
  503.       
  504.     case 131:
  505.       /* sdiff-style columns output. */
  506.       specify_style (OUTPUT_SDIFF);
  507.       sdiff_help_sdiff = 1;
  508.       break;
  509.  
  510.     case 132:
  511.     case 133:
  512.     case 134:
  513.       specify_style (OUTPUT_IFDEF);
  514.       {
  515.         const char **form = &line_format[c - 132];
  516.         if (*form && strcmp (*form, optarg) != 0)
  517.           error ("conflicting line format", 0, 0);
  518.         *form = optarg;
  519.       }
  520.       break;
  521.  
  522.     case 135:
  523.     case 136:
  524.     case 137:
  525.     case 138:
  526.       specify_style (OUTPUT_IFDEF);
  527.       {
  528.         const char **form = &group_format[c - 135];
  529.         if (*form && strcmp (*form, optarg) != 0)
  530.           error ("conflicting group format", 0, 0);
  531.         *form = optarg;
  532.       }
  533.       break;
  534.  
  535.     case 139:
  536.       if (ck_atoi (optarg, &horizon_lines) || horizon_lines < 0)
  537.         fatal ("horizon must be a nonnegative integer");
  538.       break;
  539.  
  540.     default:
  541.       usage ();
  542.     }
  543.       prev = c;
  544.     }
  545.  
  546.   if (optind != argc - 2)
  547.     usage ();
  548.  
  549.  
  550.   {
  551.     /*
  552.      *    We maximize first the half line width, and then the gutter width,
  553.      *    according to the following constraints:
  554.      *    1.  Two half lines plus a gutter must fit in a line.
  555.      *    2.  If the half line width is nonzero:
  556.      *        a.  The gutter width is at least GUTTER_WIDTH_MINIMUM.
  557.      *        b.  If tabs are not expanded to spaces,
  558.      *        a half line plus a gutter is an integral number of tabs,
  559.      *        so that tabs in the right column line up.
  560.      */
  561.     int t = tab_expand_flag ? 1 : TAB_WIDTH;
  562.     int off = (width + t + GUTTER_WIDTH_MINIMUM) / (2*t)  *  t;
  563.     sdiff_half_width = max (0, min (off - GUTTER_WIDTH_MINIMUM, width - off)),
  564.     sdiff_column2_offset = sdiff_half_width ? off : width;
  565.   }
  566.  
  567.   if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED)
  568.     context = 0;
  569.   else if (context == -1)
  570.     /* Default amount of context for -c.  */
  571.     context = 3;
  572.  
  573.   if (output_style == OUTPUT_IFDEF)
  574.     {
  575.       int i;
  576.       for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++)
  577.     if (!line_format[i])
  578.       line_format[i] = "%l\n";
  579.       if (!group_format[OLD])
  580.     group_format[OLD]
  581.       = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%<";
  582.       if (!group_format[NEW])
  583.     group_format[NEW]
  584.       = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%>";
  585.       if (!group_format[UNCHANGED])
  586.     group_format[UNCHANGED] = "%=";
  587.       if (!group_format[CHANGED])
  588.     group_format[CHANGED] = concat (group_format[OLD],
  589.                     group_format[NEW], "");
  590.     }
  591.  
  592.   no_diff_means_no_output =
  593.     (output_style == OUTPUT_IFDEF ?
  594.       (!*group_format[UNCHANGED]
  595.        || (strcmp (group_format[UNCHANGED], "%=") == 0
  596.        && !*line_format[UNCHANGED]))
  597.      : output_style == OUTPUT_SDIFF ? sdiff_skip_common_lines : 1);
  598.  
  599.   switch_string = option_list (argv + 1, optind - 1);
  600.  
  601.   val = compare_files (NULL, argv[optind], NULL, argv[optind + 1], 0);
  602.  
  603.   /* Print any messages that were saved up for last.  */
  604.   print_message_queue ();
  605.  
  606.   if (ferror (stdout) || fclose (stdout) != 0)
  607.     fatal ("write error");
  608.   exit (val);
  609.   return val;
  610. }
  611.  
  612. /* Add the compiled form of regexp PATTERN to REGLIST.  */
  613.  
  614. static void
  615. add_regexp (reglist, pattern)
  616.      struct regexp_list **reglist;
  617.      char *pattern;
  618. {
  619.   struct regexp_list *r;
  620.   const char *m;
  621.  
  622.   r = (struct regexp_list *) xmalloc (sizeof (*r));
  623.   bzero (r, sizeof (*r));
  624.   r->buf.fastmap = (char *) xmalloc (256);
  625.   m = re_compile_pattern (pattern, strlen (pattern), &r->buf);
  626.   if (m != 0)
  627.     error ("%s: %s", pattern, m);
  628.  
  629.   /* Add to the start of the list, since it's easier than the end.  */
  630.   r->next = *reglist;
  631.   *reglist = r;
  632. }
  633.  
  634. static void
  635. usage ()
  636. {
  637.   fprintf (stderr, "Usage: %s [options] from-file to-file\n", program);
  638.   fprintf (stderr, "Options:\n\
  639.        [-abBcdefhHilnNpPqrstTuvwy] [-C lines] [-D name] [-F regexp]\n\
  640.        [-I regexp] [-L from-label [-L to-label]] [-S starting-file] [-U lines]\n\
  641.        [-W columns] [-x pattern] [-X pattern-file] [--exclude=pattern]\n\
  642.        [--exclude-from=pattern-file] [--ignore-blank-lines] [--context[=lines]]\n\
  643.        [--ifdef=name] [--show-function-line=regexp] [--speed-large-files]\n\
  644.        [--label=from-label [--label=to-label]] [--new-file]\n");
  645.   fprintf (stderr, "\
  646.        [--ignore-matching-lines=regexp] [--unidirectional-new-file]\n\
  647.        [--starting-file=starting-file] [--initial-tab] [--width=columns]\n\
  648.        [--text] [--ignore-space-change] [--minimal] [--ed] [--forward-ed]\n\
  649.        [--ignore-case] [--paginate] [--rcs] [--show-c-function] [--brief]\n\
  650.        [--recursive] [--report-identical-files] [--expand-tabs] [--version]\n");
  651.   fprintf (stderr, "\
  652.        [--ignore-all-space] [--side-by-side] [--unified[=lines]]\n\
  653.        [--left-column] [--suppress-common-lines] [--sdiff-merge-assist]\n\
  654.        [--old-line-format=format] [--new-line-format=format]\n\
  655.        [--unchanged-line-format=format]\n\
  656.        [--old-group-format=format] [--new-group-format=format]\n\
  657.        [--unchanged-group-format=format] [--changed-group-format=format]\n\
  658.        [--horizon-lines=lines]\n");
  659.   exit (2);
  660.  
  661. static int
  662. specify_format (var, value)
  663.      const char **var;
  664.      const char *value;
  665. {
  666.   int err = *var ? strcmp (*var, value) : 0;
  667.   *var = value;
  668.   return err;
  669. }
  670.  
  671. static void
  672. specify_style (style)
  673.      enum output_style style;
  674. {
  675.   if (output_style != OUTPUT_NORMAL
  676.       && output_style != style)
  677.     error ("conflicting specifications of output style", 0, 0);
  678.   output_style = style;
  679. }
  680.  
  681. /* Compare two files (or dirs) with specified names
  682.    DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion.
  683.    (if DIR0 is 0, then the name is just NAME0, etc.)
  684.    This is self-contained; it opens the files and closes them.
  685.  
  686.    Value is 0 if files are the same, 1 if different,
  687.    2 if there is a problem opening them.  */
  688.  
  689. static int
  690. compare_files (dir0, name0, dir1, name1, depth)
  691.      char *dir0, *dir1;
  692.      char *name0, *name1;
  693.      int depth;
  694. {
  695.   struct file_data inf[2];
  696.   register int i;
  697.   int val;
  698.   int same_files;
  699.   int errorcount = 0;
  700.  
  701.   /* If this is directory comparison, perhaps we have a file
  702.      that exists only in one of the directories.
  703.      If so, just print a message to that effect.  */
  704.  
  705.   if (! ((name0 != 0 && name1 != 0)
  706.      || (unidirectional_new_file_flag && name1 != 0)
  707.      || entire_new_file_flag))
  708.     {
  709.       char *name = name0 == 0 ? name1 : name0;
  710.       char *dir = name0 == 0 ? dir1 : dir0;
  711.       message ("Only in %s: %s\n", dir, name);
  712.       /* Return 1 so that diff_dirs will return 1 ("some files differ").  */
  713.       return 1;
  714.     }
  715.  
  716.   /* Mark any nonexistent file with -1 in the desc field.  */
  717.   /* Mark unopened files (i.e. directories) with -2. */
  718.  
  719.   inf[0].desc = name0 == 0 ? -1 : -2;
  720.   inf[1].desc = name1 == 0 ? -1 : -2;
  721.  
  722.   /* Now record the full name of each file, including nonexistent ones.  */
  723.  
  724.   if (name0 == 0)
  725.     name0 = name1;
  726.   if (name1 == 0)
  727.     name1 = name0;
  728.  
  729.   inf[0].name = dir0 == 0 ? name0 : concat (dir0, "/", name0);
  730.   inf[1].name = dir1 == 0 ? name1 : concat (dir1, "/", name1);
  731.  
  732.   /* Stat the files.  Record whether they are directories.  */
  733.  
  734.   for (i = 0; i <= 1; i++)
  735.     {
  736.       bzero (&inf[i].stat, sizeof (struct stat));
  737.       inf[i].dir_p = 0;
  738.  
  739.       if (inf[i].desc != -1)
  740.     {
  741.       int stat_result;
  742.  
  743.       if (strcmp (inf[i].name, "-") == 0)
  744.         {
  745.           inf[i].desc = 0;
  746.           inf[i].name = "Standard Input";
  747.           stat_result = fstat (0, &inf[i].stat);
  748.         }
  749.       else
  750.         stat_result = stat (inf[i].name, &inf[i].stat);
  751.  
  752.       if (stat_result != 0)
  753.         {
  754.           perror_with_name (inf[i].name);
  755.           errorcount = 1;
  756.         }
  757.       else
  758.         inf[i].dir_p = S_ISDIR (inf[i].stat.st_mode) && inf[i].desc != 0;
  759.     }
  760.     }
  761.  
  762.   if (name0 == 0)
  763.     inf[0].dir_p = inf[1].dir_p;
  764.   if (name1 == 0)
  765.     inf[1].dir_p = inf[0].dir_p;
  766.  
  767.   if (errorcount == 0 && depth == 0 && inf[0].dir_p != inf[1].dir_p)
  768.     {
  769.       /* If one is a directory, and it was specified in the command line,
  770.      use the file in that dir with the other file's basename.  */
  771.  
  772.       int fnm_arg = inf[0].dir_p;
  773.       int dir_arg = 1 - fnm_arg;
  774.       char *p = rindex (inf[fnm_arg].name, '/');
  775.       char *filename = inf[dir_arg].name
  776.     = concat (inf[dir_arg].name,  "/", (p ? p+1 : inf[fnm_arg].name));
  777.  
  778.       if (inf[fnm_arg].desc == 0)
  779.     fatal ("can't compare - to a directory");
  780.  
  781.       if (stat (filename, &inf[dir_arg].stat) != 0)
  782.     {
  783.       perror_with_name (filename);
  784.       errorcount = 1;
  785.     }
  786.       else
  787.     inf[dir_arg].dir_p = S_ISDIR (inf[dir_arg].stat.st_mode);
  788.     }
  789.  
  790.   if (errorcount)
  791.     {
  792.  
  793.       /* If either file should exist but does not, return 2.  */
  794.  
  795.       val = 2;
  796.  
  797.     }
  798.   else if ((same_files =    inf[0].stat.st_ino == inf[1].stat.st_ino
  799.              && inf[0].stat.st_dev == inf[1].stat.st_dev
  800.              && inf[0].desc != -1
  801.              && inf[1].desc != -1)
  802.        && no_diff_means_no_output)
  803.     {
  804.       /* The two named files are actually the same physical file.
  805.      We know they are identical without actually reading them.  */
  806.  
  807.       val = 0;
  808.     }
  809.   else if (inf[0].dir_p & inf[1].dir_p)
  810.     {
  811.       if (output_style == OUTPUT_IFDEF)
  812.     fatal ("-D option not supported with directories");
  813.  
  814.       /* If both are directories, compare the files in them.  */
  815.  
  816.       if (depth > 0 && !recursive)
  817.     {
  818.       /* But don't compare dir contents one level down
  819.          unless -r was specified.  */
  820.       message ("Common subdirectories: %s and %s\n",
  821.            inf[0].name, inf[1].name);
  822.       val = 0;
  823.     }
  824.       else
  825.     {
  826.       val = diff_dirs (inf, compare_files, depth);
  827.     }
  828.  
  829.     }
  830.   else if (inf[0].dir_p | inf[1].dir_p)
  831.     {
  832.       /* Perhaps we have a subdirectory that exists only in one directory.
  833.      If so, just print a message to that effect.  */
  834.  
  835.       if (inf[0].desc == -1 || inf[1].desc == -1)
  836.     {
  837.       if (recursive
  838.           && (entire_new_file_flag
  839.           || (unidirectional_new_file_flag && inf[0].desc == -1)))
  840.         val = diff_dirs (inf, compare_files, depth);
  841.       else
  842.         {
  843.           char *dir = (inf[0].desc == -1) ? dir1 : dir0;
  844.           message ("Only in %s: %s\n", dir, name0);
  845.           val = 1;
  846.         }
  847.     }
  848.       else
  849.     {
  850.       /* We have a subdirectory in one directory
  851.          and a file in the other.  */
  852.  
  853.       message ("%s is a directory but %s is not\n",
  854.            inf[1 - inf[0].dir_p].name, inf[inf[0].dir_p].name);
  855.  
  856.       /* This is a difference.  */
  857.       val = 1;
  858.     }
  859.     }
  860.   else if (no_details_flag
  861.        && inf[0].stat.st_size != inf[1].stat.st_size
  862.        && (inf[0].desc == -1 || S_ISREG (inf[0].stat.st_mode))
  863.        && (inf[1].desc == -1 || S_ISREG (inf[1].stat.st_mode)))
  864.     {
  865.       message ("Files %s and %s differ\n", inf[0].name, inf[1].name);
  866.       val = 1;
  867.     }
  868.   else
  869.     {
  870.       /* Both exist and neither is a directory.  */
  871.  
  872.       /* Open the files and record their descriptors.  */
  873.  
  874.       if (inf[0].desc == -2)
  875.     if ((inf[0].desc = open (inf[0].name, O_RDONLY, 0)) < 0)
  876.       {
  877.         perror_with_name (inf[0].name);
  878.         errorcount = 1;
  879.       }
  880.       if (inf[1].desc == -2)
  881.     if (same_files)
  882.       inf[1].desc = inf[0].desc;
  883.     else if ((inf[1].desc = open (inf[1].name, O_RDONLY, 0)) < 0)
  884.       {
  885.         perror_with_name (inf[1].name);
  886.         errorcount = 1;
  887.       }
  888.     
  889.       /* Compare the files, if no error was found.  */
  890.  
  891.       val = errorcount ? 2 : diff_2_files (inf, depth);
  892.  
  893.       /* Close the file descriptors.  */
  894.  
  895.       if (inf[0].desc >= 0 && close (inf[0].desc) != 0)
  896.     {
  897.       perror_with_name (inf[0].name);
  898.       val = 2;
  899.     }
  900.       if (inf[1].desc >= 0 && inf[0].desc != inf[1].desc
  901.       && close (inf[1].desc) != 0)
  902.     {
  903.       perror_with_name (inf[1].name);
  904.       val = 2;
  905.     }
  906.     }
  907.  
  908.   /* Now the comparison has been done, if no error prevented it,
  909.      and VAL is the value this function will return.  */
  910.  
  911.   if (val == 0 && !inf[0].dir_p)
  912.     {
  913.       if (print_file_same_flag)
  914.     message ("Files %s and %s are identical\n",
  915.          inf[0].name, inf[1].name);
  916.     }
  917.   else
  918.     fflush (stdout);
  919.  
  920.   if (dir0 != 0)
  921.     free (inf[0].name);
  922.   if (dir1 != 0)
  923.     free (inf[1].name);
  924.  
  925.   return val;
  926. }
  927.